28 #include "gsl/gsl_interp.h"
29 #include "gsl/gsl_interp2d.h"
30 #include "gsl/gsl_errno.h"
38 using namespace std::string_literals;
40 #define LSST_CHECK_GSL(type, status) \
41 if (status) throw LSST_EXCEPT(type, "GSL error: "s + ::gsl_strerror(status))
58 class IdentityTransmissionCurve :
public TransmissionCurve {
60 static constexpr
char const* NAME =
"IdentityTransmissionCurve";
74 void sampleAt(
lsst::geom::Point2D const&, ndarray::Array<double const, 1, 1>
const& wavelengths,
75 ndarray::Array<double, 1, 1>
const& out)
const override {
79 bool isPersistable()
const noexcept
override {
return true; }
85 return shared_from_this();
94 std::string getPersistenceName()
const override {
return NAME; }
96 void write(OutputArchiveHandle& handle)
const override { handle.saveEmpty(); }
98 class Factory :
public table::io::PersistableFactory {
101 CatalogVector
const& catalogs)
const override {
106 Factory() : table::io::PersistableFactory(NAME) {}
112 IdentityTransmissionCurve() =
default;
138 template <
typename T>
141 template <
typename T>
142 GslPtr<T> makeGslPtr(T* p,
void (*
free)(T*)) {
146 return GslPtr<T>(p,
free);
157 static constexpr
bool isSpatiallyConstant =
true;
158 static constexpr
char const* NAME =
"SpatiallyConstantTransmissionCurve";
163 Impl1d(ndarray::Array<double, 1, 1>
const& throughput, ndarray::Array<double, 1, 1>
const& wavelengths)
164 : _throughput(throughput),
165 _wavelengths(wavelengths),
166 _interp(makeGslPtr(::gsl_interp_alloc(::gsl_interp_linear, _wavelengths.size()),
167 &::gsl_interp_free)) {
168 int status = ::gsl_interp_init(_interp.get(), _wavelengths.getData(), _throughput.getData(),
169 _wavelengths.size());
177 static void setupPersistence(table::Schema&
schema, ArrayKeyVector&
keys) {
178 keys.push_back(
schema.addField<table::Array<double>>(
"throughput",
"array of known throughput values",
180 keys.push_back(
schema.addField<table::Array<double>>(
181 "wavelengths",
"array of known wavelength values",
"angstrom", 0));
184 void persist(table::BaseRecord& record, ArrayKeyVector
const&
keys)
const {
185 record.set(
keys[0], _throughput);
186 record.set(
keys[1], _wavelengths);
189 static Impl1d unpersist(table::BaseRecord& record, ArrayKeyVector
const&
keys) {
190 return Impl1d(record[
keys[0]], record[
keys[1]]);
197 : _accel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)) {}
199 double operator()(Impl1d
const& parent,
double wavelength) {
201 int status = ::gsl_interp_eval_e(parent._interp.get(), parent._wavelengths.getData(),
202 parent._throughput.getData(), wavelength, _accel.get(), &
result);
208 GslPtr<::gsl_interp_accel> _accel;
212 ndarray::Array<double, 1, 1> _throughput;
213 ndarray::Array<double, 1, 1> _wavelengths;
214 GslPtr<::gsl_interp> _interp;
223 static constexpr
bool isSpatiallyConstant =
false;
224 static constexpr
char const* NAME =
"RadialTransmissionCurve";
226 Impl2d(ndarray::Array<double, 1, 1>
const& throughput, ndarray::Array<double, 1, 1>
const& wavelengths,
227 ndarray::Array<double, 1, 1>
const& radii)
228 : _throughput(throughput),
229 _wavelengths(wavelengths),
232 ::gsl_interp2d_alloc(::gsl_interp2d_bilinear, _wavelengths.size(), _radii.size()),
233 &::gsl_interp2d_free)) {
234 int status = ::gsl_interp2d_init(_interp.get(), _wavelengths.getData(), _radii.getData(),
235 _throughput.getData(), _wavelengths.size(), _radii.size());
243 static void setupPersistence(table::Schema&
schema, ArrayKeyVector&
keys) {
244 keys.push_back(
schema.addField<table::Array<double>>(
246 "flattenned 2-d array of known throughput values, with radius dimension fastest",
"", 0));
247 keys.push_back(
schema.addField<table::Array<double>>(
248 "wavelengths",
"array of known wavelength values",
"angstrom", 0));
249 keys.push_back(
schema.addField<table::Array<double>>(
"radii",
"array of known radius values",
"", 0));
252 void persist(table::BaseRecord& record, ArrayKeyVector
const&
keys)
const {
253 record.set(
keys[0], _throughput);
254 record.set(
keys[1], _wavelengths);
255 record.set(
keys[2], _radii);
258 static Impl2d unpersist(table::BaseRecord& record, ArrayKeyVector
const&
keys) {
259 return Impl2d(record[
keys[0]], record[
keys[1]], record[
keys[2]]);
266 : _radius(point.asEigen().norm()),
267 _radiusAccel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)),
268 _wavelengthAccel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)) {
269 _radius =
std::max(_radius, parent._radii.front());
270 _radius =
std::min(_radius, parent._radii.back());
273 double operator()(Impl2d
const& parent,
double wavelength) {
276 ::gsl_interp2d_eval_e(parent._interp.get(), parent._wavelengths.getData(),
277 parent._radii.getData(), parent._throughput.getData(), wavelength,
278 _radius, _radiusAccel.get(), _wavelengthAccel.get(), &
result);
285 GslPtr<::gsl_interp_accel> _radiusAccel;
286 GslPtr<::gsl_interp_accel> _wavelengthAccel;
290 ndarray::Array<double, 1, 1> _throughput;
291 ndarray::Array<double, 1, 1> _wavelengths;
292 ndarray::Array<double, 1, 1> _radii;
293 GslPtr<::gsl_interp2d> _interp;
296 template <
typename Impl>
297 class InterpolatedTransmissionCurve :
public TransmissionCurve {
300 : _atBounds(throughputAtBounds), _impl(
std::move(impl)) {
302 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
303 "Throughput values at bounds must be finite");
311 void sampleAt(
lsst::geom::Point2D const& point, ndarray::Array<double const, 1, 1>
const& wavelengths,
312 ndarray::Array<double, 1, 1>
const& out)
const override {
313 LSST_THROW_IF_NE(wavelengths.getSize<0>(), out.getSize<0>(), pex::exceptions::LengthError,
314 "Length of wavelength array (%d) does not match size of output array (%d)");
315 typename Impl::Functor functor(_impl, point);
316 auto bounds = _impl.getWavelengthBounds();
317 auto wlIter = wavelengths.begin();
318 for (
auto outIter = out.begin(); outIter != out.end(); ++outIter, ++wlIter) {
319 double&
y = *outIter;
320 if (*wlIter < bounds.first) {
322 }
else if (*wlIter > bounds.second) {
323 y = _atBounds.second;
325 y = functor(_impl, *wlIter);
330 bool isPersistable()
const noexcept
override {
return true; }
335 if (_impl.isSpatiallyConstant) {
336 return shared_from_this();
338 return TransmissionCurve::_transformedByImpl(
transform);
342 std::string getPersistenceName()
const override {
return Impl::NAME; }
344 struct PersistenceHelper {
350 static PersistenceHelper
const& get() {
351 static PersistenceHelper
const instance;
359 schema.addField<double>(
"throughputAtMin",
"throughput below minimum wavelength")),
361 schema.addField<double>(
"throughputAtMax",
"throughput above minimum wavelength")) {
366 void write(OutputArchiveHandle& handle)
const override {
367 auto const&
keys = PersistenceHelper::get();
368 auto catalog = handle.makeCatalog(
keys.schema);
369 auto record = catalog.addNew();
370 record->set(
keys.throughputAtMin, _atBounds.first);
371 record->set(
keys.throughputAtMax, _atBounds.second);
372 _impl.persist(*record,
keys.arrays);
373 handle.saveCatalog(catalog);
376 class Factory :
public table::io::PersistableFactory {
379 CatalogVector
const& catalogs)
const override {
380 auto const&
keys = PersistenceHelper::get();
383 auto& record = catalogs.front().front();
384 return std::make_shared<InterpolatedTransmissionCurve>(
385 Impl::unpersist(record,
keys.arrays),
389 Factory() : table::io::PersistableFactory(Impl::NAME) {}
399 template <
typename Impl>
402 template class InterpolatedTransmissionCurve<Impl1d>;
403 template class InterpolatedTransmissionCurve<Impl2d>;
411 class ProductTransmissionCurve :
public TransmissionCurve {
413 static constexpr
char const* NAME =
"ProductTransmissionCurve";
420 auto aWavelengthBounds = _a->getWavelengthBounds();
421 auto bWavelengthBounds = _b->getWavelengthBounds();
422 auto aThroughputAtBounds = _a->getThroughputAtBounds();
423 auto bThroughputAtBounds = _b->getThroughputAtBounds();
425 auto determineWavelengthBound = [](
double aWavelength,
double bWavelength,
double aThroughput,
426 double bThroughput,
auto isFirstOuter) ->
double {
430 if (isFirstOuter(aWavelength, bWavelength)) {
431 return (bThroughput == 0.0) ? bWavelength : aWavelength;
433 return (aThroughput == 0.0) ? aWavelength : bWavelength;
437 return std::make_pair(determineWavelengthBound(aWavelengthBounds.first, bWavelengthBounds.first,
438 aThroughputAtBounds.first, bThroughputAtBounds.first,
440 determineWavelengthBound(aWavelengthBounds.second, bWavelengthBounds.second,
441 aThroughputAtBounds.second, bThroughputAtBounds.second,
446 auto aAtBounds = _a->getThroughputAtBounds();
447 auto bAtBounds = _b->getThroughputAtBounds();
448 return std::make_pair(aAtBounds.first * bAtBounds.first, aAtBounds.second * bAtBounds.second);
451 void sampleAt(
lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths,
452 ndarray::Array<double, 1, 1>
const& out)
const override {
453 _a->sampleAt(position, wavelengths, out);
454 ndarray::Array<double, 1, 1> tmp = ndarray::allocate(wavelengths.getSize<0>());
455 _b->sampleAt(position, wavelengths, tmp);
459 bool isPersistable() const noexcept
override {
return _a->isPersistable() && _b->isPersistable(); }
462 std::string getPersistenceName()
const override {
return NAME; }
464 struct PersistenceHelper {
469 static PersistenceHelper
const& get() {
470 static PersistenceHelper
const instance;
477 a(
schema.addField<int>(
"a",
"archive ID of first operand")),
478 b(
schema.addField<int>(
"b",
"archive ID of second operand")) {}
481 class Factory :
public table::io::PersistableFactory {
484 CatalogVector
const& catalogs)
const override {
485 auto const&
keys = PersistenceHelper::get();
488 auto const& record = catalogs.front().front();
489 return std::make_shared<ProductTransmissionCurve>(
490 archive.get<TransmissionCurve>(record.get(
keys.a)),
491 archive.get<TransmissionCurve>(record.get(
keys.b)));
494 Factory() : table::io::PersistableFactory(NAME) {}
497 void write(OutputArchiveHandle& handle)
const override {
498 auto const&
keys = PersistenceHelper::get();
499 auto catalog = handle.makeCatalog(
keys.schema);
500 auto record = catalog.addNew();
501 record->set(
keys.a, handle.put(_a));
502 record->set(
keys.b, handle.put(_b));
503 handle.saveCatalog(catalog);
521 class TransformedTransmissionCurve :
public TransmissionCurve {
523 static constexpr
char const* NAME =
"TransformedTransmissionCurve";
532 return _nested->getThroughputAtBounds();
535 void sampleAt(
lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths,
536 ndarray::Array<double, 1, 1>
const& out)
const override {
537 return _nested->sampleAt(_transform->applyInverse(position), wavelengths, out);
540 bool isPersistable() const noexcept
override {
541 return _nested->isPersistable() && _transform->isPersistable();
548 return std::make_shared<TransformedTransmissionCurve>(_nested,
transform->then(*_transform));
551 std::string getPersistenceName()
const override {
return NAME; }
553 struct PersistenceHelper {
558 static PersistenceHelper
const& get() {
559 static PersistenceHelper
const instance;
566 nested(
schema.addField<int>(
"nested",
"archive ID of the nested TransmissionCurve")),
567 transform(
schema.addField<int>(
"transform",
"archive ID of the coordinate transform")) {}
570 void write(OutputArchiveHandle& handle)
const override {
571 auto const&
keys = PersistenceHelper::get();
572 auto catalog = handle.makeCatalog(
keys.schema);
573 auto record = catalog.addNew();
574 record->set(
keys.nested, handle.put(_nested));
575 record->set(
keys.transform, handle.put(_transform));
576 handle.saveCatalog(catalog);
579 class Factory :
public table::io::PersistableFactory {
582 CatalogVector
const& catalogs)
const override {
583 auto const&
keys = PersistenceHelper::get();
586 auto const& record = catalogs.front().front();
587 return std::make_shared<TransformedTransmissionCurve>(
588 archive.get<TransmissionCurve>(record.get(
keys.nested)),
592 Factory() : table::io::PersistableFactory(NAME) {}
611 return IdentityTransmissionCurve::get();
615 ndarray::Array<double const, 1>
const& throughput, ndarray::Array<double const, 1>
const& wavelengths,
617 ::gsl_set_error_handler_off();
619 "Length of wavelength array (%d) does not match size of throughput array (%d)");
620 return std::make_shared<InterpolatedTransmissionCurve<Impl1d>>(
621 Impl1d(ndarray::copy(throughput), ndarray::copy(wavelengths)),
626 ndarray::Array<double const, 2>
const& throughput, ndarray::Array<double const, 1>
const& wavelengths,
628 ::gsl_set_error_handler_off();
631 "Length of wavelength array (%d) does not match first dimension of of throughput array (%d)");
634 "Length of radii array (%d) does not match second dimension of of throughput array (%d)");
638 ndarray::Array<double, 2, 2> throughputTransposed = ndarray::allocate(throughput.getShape().reverse());
639 throughputTransposed.transpose() = throughput;
640 ndarray::Array<double, 1, 1> throughputFlat = ndarray::flatten<1>(throughputTransposed);
641 return std::make_shared<InterpolatedTransmissionCurve<Impl2d>>(
642 Impl2d(throughputFlat, ndarray::copy(wavelengths), ndarray::copy(radii)),
648 auto a = shared_from_this();
649 auto b =
other.shared_from_this();
650 auto result =
a->_multipliedByImpl(
b);
665 ndarray::Array<double, 1, 1> TransmissionCurve::sampleAt(
666 lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths)
const {
667 ndarray::Array<double, 1, 1> out = ndarray::allocate(wavelengths.getSize<0>());
668 sampleAt(position, wavelengths, out);
674 return std::make_shared<TransformedTransmissionCurve>(shared_from_this(),
std::move(
transform));
682 std::string TransmissionCurve::getPythonModule()
const {
return "lsst.afw.image"; }