28#include "gsl/gsl_interp.h"
29#include "gsl/gsl_interp2d.h"
30#include "gsl/gsl_errno.h"
37using namespace std::string_literals;
39#define LSST_CHECK_GSL(type, status) \
40 if (status) throw LSST_EXCEPT(type, "GSL error: "s + ::gsl_strerror(status))
57class IdentityTransmissionCurve :
public TransmissionCurve {
59 static constexpr char const* NAME =
"IdentityTransmissionCurve";
73 void sampleAt(
lsst::geom::Point2D const&, ndarray::Array<double const, 1, 1>
const& wavelengths,
74 ndarray::Array<double, 1, 1>
const& out)
const override {
78 bool isPersistable()
const noexcept override {
return true; }
84 return shared_from_this();
93 std::string getPersistenceName()
const override {
return NAME; }
95 void write(OutputArchiveHandle& handle)
const override { handle.saveEmpty(); }
97 class Factory :
public table::io::PersistableFactory {
100 CatalogVector
const& catalogs)
const override {
105 Factory() : table::io::PersistableFactory(NAME) {}
108 static Factory registration;
111 IdentityTransmissionCurve() =
default;
114IdentityTransmissionCurve::Factory IdentityTransmissionCurve::registration;
141GslPtr<T> makeGslPtr(T* p,
void (*free)(T*)) {
145 return GslPtr<T>(p, free);
156 static constexpr bool isSpatiallyConstant =
true;
157 static constexpr char const* NAME =
"SpatiallyConstantTransmissionCurve";
162 Impl1d(ndarray::Array<double, 1, 1>
const& throughput, ndarray::Array<double, 1, 1>
const& wavelengths)
163 : _throughput(throughput),
164 _wavelengths(wavelengths),
165 _interp(makeGslPtr(::gsl_interp_alloc(::gsl_interp_linear, _wavelengths.size()),
166 &::gsl_interp_free)) {
167 int status = ::gsl_interp_init(_interp.get(), _wavelengths.getData(), _throughput.getData(),
168 _wavelengths.size());
176 static void setupPersistence(table::Schema&
schema, ArrayKeyVector& keys) {
177 keys.push_back(
schema.addField<table::Array<double>>(
"throughput",
"array of known throughput values",
179 keys.push_back(
schema.addField<table::Array<double>>(
180 "wavelengths",
"array of known wavelength values",
"angstrom", 0));
183 void persist(table::BaseRecord& record, ArrayKeyVector
const& keys)
const {
184 record.set(keys[0], _throughput);
185 record.set(keys[1], _wavelengths);
188 static Impl1d unpersist(table::BaseRecord& record, ArrayKeyVector
const& keys) {
189 return Impl1d(record[keys[0]], record[keys[1]]);
196 : _accel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)) {}
198 double operator()(Impl1d
const& parent,
double wavelength) {
200 int status = ::gsl_interp_eval_e(parent._interp.get(), parent._wavelengths.getData(),
201 parent._throughput.getData(), wavelength, _accel.get(), &
result);
207 GslPtr<::gsl_interp_accel> _accel;
211 ndarray::Array<double, 1, 1> _throughput;
212 ndarray::Array<double, 1, 1> _wavelengths;
213 GslPtr<::gsl_interp> _interp;
222 static constexpr bool isSpatiallyConstant =
false;
223 static constexpr char const* NAME =
"RadialTransmissionCurve";
225 Impl2d(ndarray::Array<double, 1, 1>
const& throughput, ndarray::Array<double, 1, 1>
const& wavelengths,
226 ndarray::Array<double, 1, 1>
const& radii)
227 : _throughput(throughput),
228 _wavelengths(wavelengths),
231 ::gsl_interp2d_alloc(::gsl_interp2d_bilinear, _wavelengths.size(), _radii.size()),
232 &::gsl_interp2d_free)) {
233 int status = ::gsl_interp2d_init(_interp.get(), _wavelengths.getData(), _radii.getData(),
234 _throughput.getData(), _wavelengths.size(), _radii.size());
242 static void setupPersistence(table::Schema&
schema, ArrayKeyVector& keys) {
243 keys.push_back(
schema.addField<table::Array<double>>(
245 "flattenned 2-d array of known throughput values, with radius dimension fastest",
"", 0));
246 keys.push_back(
schema.addField<table::Array<double>>(
247 "wavelengths",
"array of known wavelength values",
"angstrom", 0));
248 keys.push_back(
schema.addField<table::Array<double>>(
"radii",
"array of known radius values",
"", 0));
251 void persist(table::BaseRecord& record, ArrayKeyVector
const& keys)
const {
252 record.set(keys[0], _throughput);
253 record.set(keys[1], _wavelengths);
254 record.set(keys[2], _radii);
257 static Impl2d unpersist(table::BaseRecord& record, ArrayKeyVector
const& keys) {
258 return Impl2d(record[keys[0]], record[keys[1]], record[keys[2]]);
265 : _radius(point.
asEigen().norm()),
266 _radiusAccel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)),
267 _wavelengthAccel(makeGslPtr(::gsl_interp_accel_alloc(), &gsl_interp_accel_free)) {
268 _radius =
std::max(_radius, parent._radii.front());
269 _radius =
std::min(_radius, parent._radii.back());
272 double operator()(Impl2d
const& parent,
double wavelength) {
275 ::gsl_interp2d_eval_e(parent._interp.get(), parent._wavelengths.getData(),
276 parent._radii.getData(), parent._throughput.getData(), wavelength,
277 _radius, _radiusAccel.get(), _wavelengthAccel.get(), &
result);
284 GslPtr<::gsl_interp_accel> _radiusAccel;
285 GslPtr<::gsl_interp_accel> _wavelengthAccel;
289 ndarray::Array<double, 1, 1> _throughput;
290 ndarray::Array<double, 1, 1> _wavelengths;
291 ndarray::Array<double, 1, 1> _radii;
292 GslPtr<::gsl_interp2d> _interp;
295template <
typename Impl>
296class InterpolatedTransmissionCurve :
public TransmissionCurve {
299 : _atBounds(throughputAtBounds), _impl(
std::move(impl)) {
301 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
302 "Throughput values at bounds must be finite");
310 void sampleAt(
lsst::geom::Point2D const& point, ndarray::Array<double const, 1, 1>
const& wavelengths,
311 ndarray::Array<double, 1, 1>
const& out)
const override {
312 LSST_THROW_IF_NE(wavelengths.getSize<0>(), out.getSize<0>(), pex::exceptions::LengthError,
313 "Length of wavelength array (%d) does not match size of output array (%d)");
314 typename Impl::Functor functor(_impl, point);
315 auto bounds = _impl.getWavelengthBounds();
316 auto wlIter = wavelengths.begin();
317 for (
auto outIter = out.begin(); outIter != out.end(); ++outIter, ++wlIter) {
318 double&
y = *outIter;
319 if (*wlIter < bounds.first) {
321 }
else if (*wlIter > bounds.second) {
322 y = _atBounds.second;
324 y = functor(_impl, *wlIter);
329 bool isPersistable()
const noexcept override {
return true; }
334 if (_impl.isSpatiallyConstant) {
335 return shared_from_this();
341 std::string getPersistenceName()
const override {
return Impl::NAME; }
343 struct PersistenceHelper {
349 static PersistenceHelper
const& get() {
350 static PersistenceHelper
const instance;
358 schema.addField<double>(
"throughputAtMin",
"throughput below minimum wavelength")),
360 schema.addField<double>(
"throughputAtMax",
"throughput above minimum wavelength")) {
361 Impl::setupPersistence(schema, arrays);
365 void write(OutputArchiveHandle& handle)
const override {
366 auto const&
keys = PersistenceHelper::get();
368 auto record =
catalog.addNew();
369 record->set(
keys.throughputAtMin, _atBounds.first);
370 record->set(
keys.throughputAtMax, _atBounds.second);
371 _impl.persist(*record,
keys.arrays);
372 handle.saveCatalog(catalog);
375 class Factory :
public table::io::PersistableFactory {
378 CatalogVector
const& catalogs)
const override {
379 auto const&
keys = PersistenceHelper::get();
382 auto& record =
catalogs.front().front();
383 return std::make_shared<InterpolatedTransmissionCurve>(
384 Impl::unpersist(record,
keys.arrays),
388 Factory() :
table::io::PersistableFactory(Impl::NAME) {}
398template <
typename Impl>
399typename InterpolatedTransmissionCurve<Impl>::Factory InterpolatedTransmissionCurve<Impl>::registration;
401template class InterpolatedTransmissionCurve<Impl1d>;
402template class InterpolatedTransmissionCurve<Impl2d>;
410class ProductTransmissionCurve :
public TransmissionCurve {
412 static constexpr char const* NAME =
"ProductTransmissionCurve";
419 auto aWavelengthBounds = _a->getWavelengthBounds();
420 auto bWavelengthBounds = _b->getWavelengthBounds();
421 auto aThroughputAtBounds = _a->getThroughputAtBounds();
422 auto bThroughputAtBounds = _b->getThroughputAtBounds();
424 auto determineWavelengthBound = [](
double aWavelength,
double bWavelength,
double aThroughput,
425 double bThroughput,
auto isFirstOuter) ->
double {
429 if (isFirstOuter(aWavelength, bWavelength)) {
430 return (bThroughput == 0.0) ? bWavelength : aWavelength;
432 return (aThroughput == 0.0) ? aWavelength : bWavelength;
436 return std::make_pair(determineWavelengthBound(aWavelengthBounds.first, bWavelengthBounds.first,
437 aThroughputAtBounds.first, bThroughputAtBounds.first,
439 determineWavelengthBound(aWavelengthBounds.second, bWavelengthBounds.second,
440 aThroughputAtBounds.second, bThroughputAtBounds.second,
445 auto aAtBounds = _a->getThroughputAtBounds();
446 auto bAtBounds = _b->getThroughputAtBounds();
447 return std::make_pair(aAtBounds.first * bAtBounds.first, aAtBounds.second * bAtBounds.second);
450 void sampleAt(
lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths,
451 ndarray::Array<double, 1, 1>
const& out)
const override {
452 _a->sampleAt(position, wavelengths, out);
453 ndarray::Array<double, 1, 1> tmp = ndarray::allocate(wavelengths.getSize<0>());
454 _b->sampleAt(position, wavelengths, tmp);
458 bool isPersistable() const noexcept
override {
return _a->isPersistable() && _b->isPersistable(); }
461 std::string getPersistenceName()
const override {
return NAME; }
463 struct PersistenceHelper {
468 static PersistenceHelper
const& get() {
469 static PersistenceHelper
const instance;
476 a(
schema.addField<int>(
"a",
"archive ID of first operand")),
477 b(
schema.addField<int>(
"b",
"archive ID of second operand")) {}
480 class Factory :
public table::io::PersistableFactory {
483 CatalogVector
const& catalogs)
const override {
484 auto const&
keys = PersistenceHelper::get();
487 auto const& record =
catalogs.front().front();
488 return std::make_shared<ProductTransmissionCurve>(
489 archive.get<TransmissionCurve>(record.get(
keys.a)),
490 archive.get<TransmissionCurve>(record.get(
keys.b)));
496 void write(OutputArchiveHandle& handle)
const override {
497 auto const&
keys = PersistenceHelper::get();
499 auto record =
catalog.addNew();
500 record->set(
keys.a, handle.put(_a));
501 record->set(
keys.b, handle.put(_b));
502 handle.saveCatalog(catalog);
512ProductTransmissionCurve::Factory ProductTransmissionCurve::registration;
520class TransformedTransmissionCurve :
public TransmissionCurve {
522 static constexpr char const* NAME =
"TransformedTransmissionCurve";
531 return _nested->getThroughputAtBounds();
534 void sampleAt(
lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths,
535 ndarray::Array<double, 1, 1>
const& out)
const override {
536 return _nested->sampleAt(_transform->applyInverse(position), wavelengths, out);
539 bool isPersistable() const noexcept
override {
540 return _nested->isPersistable() && _transform->isPersistable();
547 return std::make_shared<TransformedTransmissionCurve>(_nested,
transform->then(*_transform));
550 std::string getPersistenceName()
const override {
return NAME; }
552 struct PersistenceHelper {
557 static PersistenceHelper
const& get() {
558 static PersistenceHelper
const instance;
565 nested(
schema.addField<int>(
"nested",
"archive ID of the nested TransmissionCurve")),
566 transform(
schema.addField<int>(
"transform",
"archive ID of the coordinate transform")) {}
569 void write(OutputArchiveHandle& handle)
const override {
570 auto const&
keys = PersistenceHelper::get();
572 auto record =
catalog.addNew();
573 record->set(
keys.nested, handle.put(_nested));
574 record->set(
keys.transform, handle.put(_transform));
575 handle.saveCatalog(catalog);
578 class Factory :
public table::io::PersistableFactory {
581 CatalogVector
const& catalogs)
const override {
582 auto const&
keys = PersistenceHelper::get();
585 auto const& record =
catalogs.front().front();
586 return std::make_shared<TransformedTransmissionCurve>(
587 archive.get<TransmissionCurve>(record.get(
keys.nested)),
588 archive.get<geom::TransformPoint2ToPoint2>(record.get(
keys.transform)));
601TransformedTransmissionCurve::Factory TransformedTransmissionCurve::registration;
610 return IdentityTransmissionCurve::get();
614 ndarray::Array<double const, 1>
const& throughput, ndarray::Array<double const, 1>
const& wavelengths,
616 ::gsl_set_error_handler_off();
618 "Length of wavelength array (%d) does not match size of throughput array (%d)");
619 return std::make_shared<InterpolatedTransmissionCurve<Impl1d>>(
620 Impl1d(ndarray::copy(throughput), ndarray::copy(wavelengths)),
625 ndarray::Array<double const, 2>
const& throughput, ndarray::Array<double const, 1>
const& wavelengths,
627 ::gsl_set_error_handler_off();
630 "Length of wavelength array (%d) does not match first dimension of of throughput array (%d)");
633 "Length of radii array (%d) does not match second dimension of of throughput array (%d)");
637 ndarray::Array<double, 2, 2> throughputTransposed = ndarray::allocate(throughput.getShape().reverse());
638 throughputTransposed.transpose() = throughput;
639 ndarray::Array<double, 1, 1> throughputFlat = ndarray::flatten<1>(throughputTransposed);
640 return std::make_shared<InterpolatedTransmissionCurve<Impl2d>>(
641 Impl2d(throughputFlat, ndarray::copy(wavelengths), ndarray::copy(radii)),
649 auto result =
a->_multipliedByImpl(
b);
665 lsst::geom::Point2D const& position, ndarray::Array<double const, 1, 1>
const& wavelengths)
const {
666 ndarray::Array<double, 1, 1> out = ndarray::allocate(wavelengths.getSize<0>());
667 sampleAt(position, wavelengths, out);
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
#define LSST_CHECK_GSL(type, status)
table::Key< double > throughputAtMax
table::Key< double > throughputAtMin
#define LSST_ARCHIVE_ASSERT(EXPR)
An assertion macro used to validate the structure of an InputArchive.
#define LSST_THROW_IF_NE(N1, N2, EXC_CLASS, MSG)
Check whether the given values are equal, and throw an LSST Exception if they are not.
A spatially-varying transmission curve as a function of wavelength.
static std::shared_ptr< TransmissionCurve const > makeRadial(ndarray::Array< double const, 2 > const &throughput, ndarray::Array< double const, 1 > const &wavelengths, ndarray::Array< double const, 1 > const &radii, double throughputAtMin=0.0, double throughputAtMax=0.0)
Create a new TransmissionCurve with throughput varying as function of radius.
virtual void sampleAt(lsst::geom::Point2D const &position, ndarray::Array< double const, 1, 1 > const &wavelengths, ndarray::Array< double, 1, 1 > const &out) const =0
Evaluate the throughput at a position into a provided output array.
std::shared_ptr< TransmissionCurve const > multipliedBy(TransmissionCurve const &other) const
Return a new TransmissionCurve that simply multiplies the values of two others.
virtual std::shared_ptr< TransmissionCurve const > _transformedByImpl(std::shared_ptr< geom::TransformPoint2ToPoint2 > transform) const
Polymorphic implementation for transformedBy().
std::string getPythonModule() const override
Return the fully-qualified Python module that should be imported to guarantee that its factory is reg...
std::shared_ptr< TransmissionCurve const > transformedBy(std::shared_ptr< geom::TransformPoint2ToPoint2 > transform) const
Return a view of a TransmissionCurve in a different coordinate system.
static std::shared_ptr< TransmissionCurve const > makeSpatiallyConstant(ndarray::Array< double const, 1 > const &throughput, ndarray::Array< double const, 1 > const &wavelengths, double throughputAtMin=0.0, double throughputAtMax=0.0)
Create a new TransmissionCurve with spatially-constant throughput.
virtual std::shared_ptr< TransmissionCurve const > _multipliedByImpl(std::shared_ptr< TransmissionCurve const > other) const
One-way polymorphic implementation for multipliedBy().
static std::shared_ptr< TransmissionCurve const > makeIdentity()
Create a new TranmissionCurve that has unit thoughput at all wavelengths everywhere.
EigenVector const & asEigen() const noexcept(IS_ELEMENT_NOTHROW_COPYABLE)
Return a fixed-size Eigen representation of the coordinate object.
Reports attempts to exceed implementation-defined length limits for some classes.
T shared_from_this(T... args)
std::shared_ptr< table::io::Persistable > read(table::io::InputArchive const &archive, table::io::CatalogVector const &catalogs) const override