LSST Applications g0fba68d861+539c3c8198,g1ec0fe41b4+f0b7a83aa5,g1fd858c14a+78b1f303e5,g22210bfa2a+8e34e7f00e,g2440f9efcc+8c5ae1fdc5,g3533f9d6cb+f4db8b1b3b,g35bb328faa+8c5ae1fdc5,g3fc7d4f5b9+a4d3a2d8bb,g40d01f57dd+ad4ae80abb,g4178042926+abd409f097,g53246c7159+8c5ae1fdc5,g548d740b8c+47e4a7beb3,g60b5630c4e+f4db8b1b3b,g663da51e9b+e0f2ae43b2,g67b6fd64d1+c104138150,g78460c75b0+7e33a9eb6d,g786e29fd12+668abc6043,g8352419a5c+8c5ae1fdc5,g8852436030+d435302cbd,g89139ef638+c104138150,g8b40312ef5+5b6744d273,g90aefe88b0+c7f866738c,g989de1cb63+c104138150,g9f33ca652e+0b88989aa6,ga2f891cd6c+f4db8b1b3b,gabe3b4be73+8856018cbb,gabf8522325+5cb9e9d408,gb1101e3267+78d08b86ce,gb89ab40317+c104138150,gcf25f946ba+d435302cbd,gd6cbbdb0b4+be834e5da7,gde0f65d7ad+a7114e3dba,ge278dab8ac+fa35eb453c,ge410e46f29+c104138150,gf35d7ec915+97dd712d81,gf5e32f922b+8c5ae1fdc5,gf67bdafdda+c104138150,gffe7e49bb4+f4db8b1b3b,v29.1.0.rc2
LSST Data Management Base Package
Loading...
Searching...
No Matches
ChebyshevBoundedField.cc
Go to the documentation of this file.
1// -*- LSST-C++ -*-
2/*
3 * LSST Data Management System
4 * Copyright 2008-2014 LSST Corporation.
5 *
6 * This product includes software developed by the
7 * LSST Project (http://www.lsst.org/).
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the LSST License Statement and
20 * the GNU General Public License along with this program. If not,
21 * see <http://www.lsstcorp.org/LegalNotices/>.
22 */
23
24#include <memory>
25
26#include "ndarray/eigen.h"
34
35namespace lsst {
36namespace afw {
37
38template std::shared_ptr<math::ChebyshevBoundedField> table::io::PersistableFacade<
39 math::ChebyshevBoundedField>::dynamicCast(std::shared_ptr<table::io::Persistable> const&);
40
41namespace math {
42
44
45// ------------------ Constructors and helpers ---------------------------------------------------------------
46
47namespace {
48
49// Compute an affine transform that maps an arbitrary box to [-1,1]x[-1,1]
50lsst::geom::AffineTransform makeChebyshevRangeTransform(lsst::geom::Box2D const bbox) {
52 lsst::geom::LinearTransform::makeScaling(2.0 / bbox.getWidth(), 2.0 / bbox.getHeight()),
53 lsst::geom::Extent2D(-(2.0 * bbox.getCenterX()) / bbox.getWidth(),
54 -(2.0 * bbox.getCenterY()) / bbox.getHeight()));
55}
56
57} // namespace
58
60 ndarray::Array<double const, 2, 2> const& coefficients)
61 : BoundedField(bbox),
62 _toChebyshevRange(makeChebyshevRangeTransform(lsst::geom::Box2D(bbox))),
63 _coefficients(coefficients) {}
64
66 : BoundedField(bbox), _toChebyshevRange(makeChebyshevRangeTransform(lsst::geom::Box2D(bbox))) {}
67
68ChebyshevBoundedField::ChebyshevBoundedField(ChebyshevBoundedField const&) = default;
69ChebyshevBoundedField::ChebyshevBoundedField(ChebyshevBoundedField&&) = default;
71
72// ------------------ fit() and helpers ---------------------------------------------------------------------
73
74namespace {
75
76using Control = ChebyshevBoundedField::Control;
77using Packer = detail::TrapezoidalPacker;
78
79// fill an array with 1-d Chebyshev functions of the 1st kind T(x), evaluated at the given point x
80void evaluateBasis1d(ndarray::Array<double, 1, 1> const& t, double x) {
81 int const n = t.getSize<0>();
82 if (n > 0) {
83 t[0] = 1.0;
84 }
85 if (n > 1) {
86 t[1] = x;
87 }
88 for (int i = 2; i < n; ++i) {
89 t[i] = 2.0 * x * t[i - 1] - t[i - 2];
90 }
91}
92
93// Create a matrix of 2-d Chebyshev functions evaluated at a set of positions, with
94// Chebyshev order along columns and evaluation positions along rows. We pack the
95// 2-d functions using the TrapezoidalPacker class, because we don't want any columns
96// that correspond to coefficients that should be set to zero.
97ndarray::Array<double, 2, 2> makeMatrix(ndarray::Array<double const, 1> const& x,
98 ndarray::Array<double const, 1> const& y,
99 lsst::geom::AffineTransform const& toChebyshevRange,
100 Packer const& packer, Control const& ctrl) {
101 int const nPoints = x.getSize<0>();
102 ndarray::Array<double, 1, 1> tx = ndarray::allocate(packer.nx);
103 ndarray::Array<double, 1, 1> ty = ndarray::allocate(packer.ny);
104 ndarray::Array<double, 2, 2> out = ndarray::allocate(nPoints, packer.size);
105 // Loop over x and y together, computing T_i(x) and T_j(y) arrays for each point,
106 // then packing them together.
107 for (int p = 0; p < nPoints; ++p) {
108 lsst::geom::Point2D sxy = toChebyshevRange(lsst::geom::Point2D(x[p], y[p]));
109 evaluateBasis1d(tx, sxy.getX());
110 evaluateBasis1d(ty, sxy.getY());
111 packer.pack(out[p], tx, ty); // this sets a row of out to the packed outer product of tx and ty
112 }
113 return out;
114}
115
116// Create a matrix of 2-d Chebyshev functions evaluated on a grid of positions, with
117// Chebyshev order along columns and evaluation positions along rows. We pack the
118// 2-d functions using the TrapezoidalPacker class, because we don't want any columns
119// that correspond to coefficients that should be set to zero.
120ndarray::Array<double, 2, 2> makeMatrix(lsst::geom::Box2I const& bbox,
121 lsst::geom::AffineTransform const& toChebyshevRange,
122 Packer const& packer, Control const& ctrl) {
123 // Create a 2-d array that contains T_j(x) for each x value, with x values in rows and j in columns
124 ndarray::Array<double, 2, 2> tx = ndarray::allocate(bbox.getWidth(), packer.nx);
125 for (int x = bbox.getBeginX(), p = 0; p < bbox.getWidth(); ++p, ++x) {
126 evaluateBasis1d(tx[p], toChebyshevRange[lsst::geom::AffineTransform::XX] * x +
127 toChebyshevRange[lsst::geom::AffineTransform::X]);
128 }
129
130 // Loop over y values, and at each point, compute T_i(y), then loop over x and multiply by the T_j(x)
131 // we already computed and stored above.
132 ndarray::Array<double, 2, 2> out = ndarray::allocate(bbox.getArea(), packer.size);
133 ndarray::Array<double, 2, 2>::Iterator outIter = out.begin();
134 ndarray::Array<double, 1, 1> ty = ndarray::allocate(ctrl.orderY + 1);
135 for (int y = bbox.getBeginY(), i = 0; i < bbox.getHeight(); ++i, ++y) {
136 evaluateBasis1d(ty, toChebyshevRange[lsst::geom::AffineTransform::YY] * y +
137 toChebyshevRange[lsst::geom::AffineTransform::Y]);
138 for (int j = 0; j < bbox.getWidth(); ++j, ++outIter) {
139 // this sets a row of out to the packed outer product of tx and ty
140 packer.pack(*outIter, tx[j], ty);
141 }
142 }
143 return out;
144}
145
146} // namespace
147
149 ndarray::Array<double const, 1> const& x,
150 ndarray::Array<double const, 1> const& y,
151 ndarray::Array<double const, 1> const& z,
152 Control const& ctrl) {
153 // Initialize the result object, so we can make use of the AffineTransform it builds
155 // This packer object knows how to map the 2-d Chebyshev functions onto a 1-d array,
156 // using only those that the control says should have nonzero coefficients.
157 Packer const packer(ctrl);
158 // Create a "design matrix" for the linear least squares problem (A in min||Ax-b||)
159 ndarray::Array<double, 2, 2> matrix = makeMatrix(x, y, result->_toChebyshevRange, packer, ctrl);
160 // Solve the linear least squares problem.
162 // Unpack the solution into a 2-d matrix, with zeros for values we didn't fit.
163 result->_coefficients = packer.unpack(lstsq.getSolution());
164 return result;
165}
166
168 ndarray::Array<double const, 1> const& x,
169 ndarray::Array<double const, 1> const& y,
170 ndarray::Array<double const, 1> const& z,
171 ndarray::Array<double const, 1> const& w,
172 Control const& ctrl) {
173 // Initialize the result object, so we can make use of the AffineTransform it builds
175 // This packer object knows how to map the 2-d Chebyshev functions onto a 1-d array,
176 // using only those that the control says should have nonzero coefficients.
177 Packer const packer(ctrl);
178 // Create a "design matrix" for the linear least squares problem ('A' in min||Ax-b||)
179 ndarray::Array<double, 2, 2> matrix = makeMatrix(x, y, result->_toChebyshevRange, packer, ctrl);
180 // We want to do weighted least squares, so we multiply both the data vector 'b' and the
181 // matrix 'A' by the weights.
182 ndarray::asEigenArray(matrix).colwise() *= ndarray::asEigenArray(w);
183 ndarray::Array<double, 1, 1> wz = ndarray::copy(z);
184 ndarray::asEigenArray(wz) *= ndarray::asEigenArray(w);
185 // Solve the linear least squares problem.
187 // Unpack the solution into a 2-d matrix, with zeros for values we didn't fit.
188 result->_coefficients = packer.unpack(lstsq.getSolution());
189 return result;
190}
191
192ndarray::Array<double, 2, 2> ChebyshevBoundedField::makeFitMatrix(
193 lsst::geom::Box2I const& bbox,
194 ndarray::Array<double const, 1> const& x,
195 ndarray::Array<double const, 1> const& y,
196 Control const& ctrl
197) {
198 // Initialize a temporary result object, so we can make use of the AffineTransform it builds
200 // This packer object knows how to map the 2-d Chebyshev functions onto a 1-d array,
201 // using only those that the control says should have nonzero coefficients.
202 Packer const packer(ctrl);
203 // Create a "design matrix" for the linear least squares problem (A in min||Ax-b||)
204 return makeMatrix(x, y, result->_toChebyshevRange, packer, ctrl);
205}
206
207template <typename T>
209 Control const& ctrl) {
210 // Initialize the result object, so we can make use of the AffineTransform it builds
213 // This packer object knows how to map the 2-d Chebyshev functions onto a 1-d array,
214 // using only those that the control says should have nonzero coefficients.
215 Packer const packer(ctrl);
216 ndarray::Array<double, 2, 2> matrix = makeMatrix(bbox, result->_toChebyshevRange, packer, ctrl);
217 // Flatten the data image into a 1-d vector.
218 ndarray::Array<double, 2, 2> imgCopy = ndarray::allocate(img.getArray().getShape());
219 imgCopy.deep() = img.getArray();
220 ndarray::Array<double const, 1, 1> z = ndarray::flatten<1>(imgCopy);
221 // Solve the linear least squares problem.
223 // Unpack the solution into a 2-d matrix, with zeros for values we didn't fit.
224 result->_coefficients = packer.unpack(lstsq.getSolution());
225 return result;
226}
227
228// ------------------ modifier factories ---------------------------------------------------------------
229
231 if (static_cast<std::size_t>(ctrl.orderX) >= _coefficients.getSize<1>()) {
233 (boost::format("New x order (%d) exceeds old x order (%d)") % ctrl.orderX %
234 (_coefficients.getSize<1>() - 1))
235 .str());
236 }
237 if (static_cast<std::size_t>(ctrl.orderY) >= _coefficients.getSize<0>()) {
239 (boost::format("New y order (%d) exceeds old y order (%d)") % ctrl.orderY %
240 (_coefficients.getSize<0>() - 1))
241 .str());
242 }
243 ndarray::Array<double, 2, 2> coefficients = ndarray::allocate(ctrl.orderY + 1, ctrl.orderX + 1);
244 coefficients.deep() = _coefficients[ndarray::view(0, ctrl.orderY + 1)(0, ctrl.orderX + 1)];
245 if (ctrl.triangular) {
246 Packer packer(ctrl);
247 ndarray::Array<double, 1, 1> packed = ndarray::allocate(packer.size);
248 packer.pack(packed, coefficients);
249 packer.unpack(coefficients, packed);
250 }
251 return std::make_shared<ChebyshevBoundedField>(getBBox(), coefficients);
252}
253
257
258// ------------------ evaluate() and helpers ---------------------------------------------------------------
259
260namespace {
261
262// To evaluate a 1-d Chebyshev function without needing to have workspace, we use the
263// Clenshaw algorith, which is like going through the recurrence relation in reverse.
264// The CoeffGetter argument g is something that behaves like an array, providing access
265// to the coefficients.
266template <typename CoeffGetter>
267double evaluateFunction1d(CoeffGetter g, double x, int size) {
268 double b_kp2 = 0.0, b_kp1 = 0.0;
269 for (int k = (size - 1); k > 0; --k) {
270 double b_k = g[k] + 2 * x * b_kp1 - b_kp2;
271 b_kp2 = b_kp1;
272 b_kp1 = b_k;
273 }
274 return g[0] + x * b_kp1 - b_kp2;
275}
276
277// This class imitates a 1-d array, by running evaluateFunction1d on a nested dimension;
278// this lets us reuse the logic in evaluateFunction1d for both dimensions. Essentially,
279// we run evaluateFunction1d on a column of coefficients to evaluate T_i(x), then pass
280// the result of that to evaluateFunction1d with the results as the "coefficients" associated
281// with the T_j(y) functions.
282struct RecursionArrayImitator {
283 double operator[](int i) const {
284 return evaluateFunction1d(coefficients[i], x, coefficients.getSize<1>());
285 }
286
287 RecursionArrayImitator(ndarray::Array<double const, 2, 2> const& coefficients_, double x_)
288 : coefficients(coefficients_), x(x_) {}
289
290 ndarray::Array<double const, 2, 2> coefficients;
291 double x;
292};
293
294} // namespace
295
297 lsst::geom::Point2D p = _toChebyshevRange(position);
298 return evaluateFunction1d(RecursionArrayImitator(_coefficients, p.getX()), p.getY(),
299 _coefficients.getSize<0>());
300}
301
302// The integral of T_n(x) over [-1,1]:
303// https://en.wikipedia.org/wiki/Chebyshev_polynomials#Differentiation_and_integration
304double integrateTn(int n) {
305 if (n % 2 == 1)
306 return 0;
307 else
308 return 2.0 / (1.0 - double(n * n));
309}
310
312 double result = 0;
313 double determinant = getBBox().getArea() / 4.0;
314 for (ndarray::Size j = 0; j < _coefficients.getSize<0>(); j++) {
315 for (ndarray::Size i = 0; i < _coefficients.getSize<1>(); i++) {
316 result += _coefficients[j][i] * integrateTn(i) * integrateTn(j);
317 }
318 }
319 return result * determinant;
320}
321
322double ChebyshevBoundedField::mean() const { return integrate() / getBBox().getArea(); }
323
324// ------------------ persistence ---------------------------------------------------------------------------
325
326namespace {
327
328struct PersistenceHelper {
329 table::Schema schema;
330 table::Key<int> orderX;
331 table::Box2IKey bbox;
332 table::Key<table::Array<double> > coefficients;
333
334 PersistenceHelper(int nx, int ny)
335 : schema(),
336 orderX(schema.addField<int>("order_x", "maximum Chebyshev function order in x")),
337 bbox(table::Box2IKey::addFields(schema, "bbox", "bounding box", "pixel")),
338 coefficients(schema.addField<table::Array<double> >(
339 "coefficients", "Chebyshev function coefficients, ordered by y then x", nx * ny)) {}
340
341 PersistenceHelper(table::Schema const& s)
342 : schema(s), orderX(s["order_x"]), bbox(s["bbox"]), coefficients(s["coefficients"]) {}
343};
344
345class ChebyshevBoundedFieldFactory : public table::io::PersistableFactory {
346public:
347 explicit ChebyshevBoundedFieldFactory(std::string const& name)
348 : afw::table::io::PersistableFactory(name) {}
349
350 std::shared_ptr<table::io::Persistable> read(InputArchive const& archive,
351 CatalogVector const& catalogs) const override {
352 LSST_ARCHIVE_ASSERT(catalogs.size() == 1u);
353 LSST_ARCHIVE_ASSERT(catalogs.front().size() == 1u);
354 table::BaseRecord const& record = catalogs.front().front();
355 PersistenceHelper const keys(record.getSchema());
356 lsst::geom::Box2I bbox(record.get(keys.bbox));
357 std::size_t nx = record.get(keys.orderX) + 1;
358 std::size_t ny = keys.coefficients.getSize() / nx;
359 LSST_ARCHIVE_ASSERT(nx * ny == keys.coefficients.getSize());
360 ndarray::Array<double, 2, 2> coefficients = ndarray::allocate(ny, nx);
361 ndarray::flatten<1>(coefficients) = record.get(keys.coefficients);
362 return std::make_shared<ChebyshevBoundedField>(bbox, coefficients);
363 }
364};
365
366std::string getChebyshevBoundedFieldPersistenceName() { return "ChebyshevBoundedField"; }
367
368ChebyshevBoundedFieldFactory registration(getChebyshevBoundedFieldPersistenceName());
369
370} // namespace
371
373 return getChebyshevBoundedFieldPersistenceName();
374}
375
376std::string ChebyshevBoundedField::getPythonModule() const { return "lsst.afw.math"; }
377
379 PersistenceHelper const keys(_coefficients.getSize<1>(), _coefficients.getSize<0>());
380 table::BaseCatalog catalog = handle.makeCatalog(keys.schema);
381 std::shared_ptr<table::BaseRecord> record = catalog.addNew();
382 record->set(keys.orderX, _coefficients.getSize<1>() - 1);
383 record->set(keys.bbox, getBBox());
384 (*record)[keys.coefficients].deep() = ndarray::flatten<1>(_coefficients);
385 handle.saveCatalog(catalog);
386}
387
388// ------------------ operators -----------------------------------------------------------------------------
389
393
395 auto rhsCasted = dynamic_cast<ChebyshevBoundedField const*>(&rhs);
396 if (!rhsCasted) return false;
397
398 return (getBBox() == rhsCasted->getBBox()) &&
399 (_coefficients.getShape() == rhsCasted->_coefficients.getShape()) &&
400 all(equal(_coefficients, rhsCasted->_coefficients));
401}
402
405 os << "ChebyshevBoundedField (" << _coefficients.getShape() << " coefficients in y,x)";
406 return os.str();
407}
408
409// ------------------ explicit instantiation ----------------------------------------------------------------
410
411#ifndef DOXYGEN
412
413#define INSTANTIATE(T) \
414 template std::shared_ptr<ChebyshevBoundedField> ChebyshevBoundedField::fit(image::Image<T> const& image, \
415 Control const& ctrl)
416
417INSTANTIATE(float);
418INSTANTIATE(double);
419
420#endif
421} // namespace math
422} // namespace afw
423} // namespace lsst
#define INSTANTIATE(FROMSYS, TOSYS)
Definition Detector.cc:509
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Definition Exception.h:48
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
Definition Persistable.h:48
lsst::geom::Box2I getBBox(ImageOrigin origin=PARENT) const
Definition ImageBase.h:445
A class to represent a 2-dimensional array of pixels.
Definition Image.h:51
An abstract base class for 2-d functions defined on an integer bounding boxes.
lsst::geom::Box2I getBBox() const
Return the bounding box that defines the region where the field is valid.
BoundedField(BoundedField const &)=default
int computeSize() const
Return the number of nonzero coefficients in the Chebyshev function defined by this object.
bool triangular
"if true, only include terms where the sum of the x and y order " "is less than or equal to max(order...
int orderY
"maximum Chebyshev function order in y" ;
int orderX
"maximum Chebyshev function order in x" ;
A BoundedField based on 2-d Chebyshev polynomials of the first kind.
static std::shared_ptr< ChebyshevBoundedField > fit(lsst::geom::Box2I const &bbox, ndarray::Array< double const, 1 > const &x, ndarray::Array< double const, 1 > const &y, ndarray::Array< double const, 1 > const &z, Control const &ctrl)
Fit a Chebyshev approximation to non-gridded data with equal weights.
std::shared_ptr< ChebyshevBoundedField > relocate(lsst::geom::Box2I const &bbox) const
Return a new ChebyshevBoundedField with domain set to the given bounding box.
bool operator==(BoundedField const &rhs) const override
BoundedFields (of the same sublcass) are equal if their bounding boxes and parameters are equal.
ndarray::Array< double const, 2, 2 > getCoefficients() const
Return the coefficient matrix.
std::shared_ptr< BoundedField > operator*(double const scale) const override
Return a scaled BoundedField.
void write(OutputArchiveHandle &handle) const override
Write the object to one or more catalogs.
static ndarray::Array< double, 2, 2 > makeFitMatrix(lsst::geom::Box2I const &bbox, ndarray::Array< double const, 1 > const &x, ndarray::Array< double const, 1 > const &y, Control const &ctrl)
Construct a matrix that can be used to fit or evaluate a Chebyshev polynomial at a set of points.
std::string getPythonModule() const override
Return the fully-qualified Python module that should be imported to guarantee that its factory is reg...
double evaluate(lsst::geom::Point2D const &position) const override
Evaluate the field at the given point.
double mean() const override
Compute the mean of this function over its bounding-box.
std::shared_ptr< ChebyshevBoundedField > truncate(Control const &ctrl) const
Return a new ChebyshevBoudedField with maximum orders set by the given control object.
std::string getPersistenceName() const override
Return the unique name used to persist this object and look up its factory.
ChebyshevBoundedField(lsst::geom::Box2I const &bbox, ndarray::Array< double const, 2, 2 > const &coefficients)
Initialize the field from its bounding box an coefficients.
double integrate() const override
Compute the integral of this function over its bounding-box.
Solver for linear least-squares problems.
ndarray::Array< double const, 1, 1 > getSolution()
Return the vector solution to the least squares problem.
static LeastSquares fromDesignMatrix(ndarray::Array< T1, 2, C1 > const &design, ndarray::Array< T2, 1, C2 > const &data, Factorization factorization=NORMAL_EIGENSYSTEM)
Initialize from the design matrix and data vector given as ndarrays.
@ NORMAL_EIGENSYSTEM
Use the normal equations with a symmetric Eigensystem decomposition.
A class used as a handle to a particular field in a table.
Definition Key.h:53
Defines the fields and offsets for a table.
Definition Schema.h:51
void saveCatalog(BaseCatalog const &catalog)
Save a catalog in the archive.
BaseCatalog makeCatalog(Schema const &schema)
Return a new, empty catalog with the given schema.
A CRTP facade class for subclasses of Persistable.
io::OutputArchiveHandle OutputArchiveHandle
An affine coordinate transformation consisting of a linear transformation and an offset.
A floating-point coordinate rectangle geometry.
Definition Box.h:413
An integer coordinate rectangle.
Definition Box.h:55
int getArea() const
Definition Box.h:189
static LinearTransform makeScaling(double s) noexcept
Reports attempts to exceed implementation-defined length limits for some classes.
Definition Runtime.h:76
T make_shared(T... args)
CatalogT< BaseRecord > BaseCatalog
Definition fwd.h:72
BoxKey< lsst::geom::Box2I > Box2IKey
Definition aggregates.h:283
Extent< double, 2 > Extent2D
Definition Extent.h:400
Point< double, 2 > Point2D
Definition Point.h:324
decltype(sizeof(void *)) size_t
Definition doctest.h:524
T str(T... args)
A helper class ChebyshevBoundedField, for mapping trapezoidal matrices to 1-d arrays.
std::shared_ptr< table::io::Persistable > read(table::io::InputArchive const &archive, table::io::CatalogVector const &catalogs) const override