#pragma once /* MOUNT CONTROL COMPONENTS LIBRARY */ /* A REFERENCE "POINTING-CORRECTION-MODEL" CLASS IMPLEMENTATION */ #include #include "bsplines/mcc_bsplines.h" #include "mcc_generics.h" namespace mcc { enum class MccDefaultPCMErrorCode : int { ERROR_OK, ERROR_INVALID_INPUTS_BISPLEV, ERROR_EXCEED_MAX_ITERS, ERROR_NULLPTR }; /* error category definition */ // error category struct MccDefaultPCMCategory : public std::error_category { MccDefaultPCMCategory() : std::error_category() {} const char* name() const noexcept { return "ADC_GENERIC_DEVICE"; } std::string message(int ec) const { MccDefaultPCMErrorCode err = static_cast(ec); switch (err) { case MccDefaultPCMErrorCode::ERROR_OK: return "OK"; case MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV: return "invalid input arguments for bispev"; case MccDefaultPCMErrorCode::ERROR_EXCEED_MAX_ITERS: return "exceed maximum of iterations number"; case MccDefaultPCMErrorCode::ERROR_NULLPTR: return "nullptr input argument"; default: return "UNKNOWN"; } } static const MccDefaultPCMCategory& get() { static const MccDefaultPCMCategory constInst; return constInst; } }; inline std::error_code make_error_code(MccDefaultPCMErrorCode ec) { return std::error_code(static_cast(ec), MccDefaultPCMCategory::get()); } } // namespace mcc namespace std { template <> class is_error_code_enum : public true_type { }; } // namespace std namespace mcc { // type of PCM corrections (algorithm used): // PCM_TYPE_GEOMETRY - "classic" geometry-based correction coefficients // PCM_TYPE_GEOMETRY_BSPLINE - previous one and additional 2D B-spline corrections // PCM_TYPE_BSPLINE - pure 2D B-spline corrections enum class MccDefaultPCMType { PCM_TYPE_GEOMETRY, PCM_TYPE_GEOMETRY_BSPLINE, PCM_TYPE_BSPLINE }; template class MccDefaultPCM : public mcc_PCM_interface_t { public: static constexpr MccMountType mountType = MOUNT_TYPE; typedef std::error_code error_t; typedef double coord_t; // "classic" geometric PEC coefficients struct pcm_geom_coeffs_t { typedef double coeff_t; coeff_t zeroPointX; coeff_t zeroPointY; coeff_t collimationErr; // tube collimation error coeff_t nonperpendErr; // X-Y axes nonperpendicularity coeff_t misalignErr1; // misalignment of hour-angle/azimuth axis: left-right for equatorial, East-West for // alt-azimuthal coeff_t misalignErr2; // misalignment of hour-angle/azimuth axis: vertical for equatorial, North-South for // alt-azimuthal coeff_t tubeFlexure; coeff_t forkFlexure; coeff_t DECaxisFlexure; // declination axis flexure }; // B-splines related data structure (coefficients, knots ...) struct pcm_bspline_t { typedef double knot_t; typedef double coeff_t; size_t bsplDegreeX = 3; size_t bsplDegreeY = 3; std::vector knotsX{}; std::vector knotsY{}; std::vector coeffsX{}; std::vector coeffsY{}; }; struct pcm_data_t { MccDefaultPCMType type{MccDefaultPCMType::PCM_TYPE_GEOMETRY}; double siteLatitude{0.0}; // in radians pcm_geom_coeffs_t geomCoefficients{}; pcm_bspline_t bspline{}; }; // constructors MccDefaultPCM() : _pcmDataMutex(new std::mutex) {} MccDefaultPCM(pcm_data_t pdata) : MccDefaultPCM() { _pcmData = std::move(pdata); } MccDefaultPCM(MccDefaultPCM&& other) = default; MccDefaultPCM& operator=(MccDefaultPCM&& other) = default; MccDefaultPCM(const MccDefaultPCM&) = delete; MccDefaultPCM& operator=(const MccDefaultPCM&) = delete; virtual ~MccDefaultPCM() = default; void setPCMData(pcm_data_t pdata) { std::lock_guard lock(*_pcmDataMutex); _pcmData = std::move(pdata); } pcm_data_t getPCMData() const { std::lock_guard lock(*_pcmDataMutex); return _pcmData; } void setPCMType(MccDefaultPCMType type) { std::lock_guard lock(*_pcmDataMutex); _pcmData.type = type; } MccDefaultPCMType getPCMType() const { std::lock_guard lock(*_pcmDataMutex); return _pcmData.type; } // The computed PCM quantities must be interpretated as: // apparent_X = encoder_X + res.pcmX // apparent_Y = encoder_Y + res.pcmY // so, input x and y are assumed to be mount axis encoder coordinates template error_t computePCM(mcc_celestial_point_c auto pt, mcc_PCM_c auto* res, T* app_pt = nullptr) requires(mcc_celestial_point_c || mcc_eqt_hrz_coord_c || std::same_as) { if (res == nullptr) { return MccDefaultPCMErrorCode::ERROR_NULLPTR; } std::lock_guard lock(*_pcmDataMutex); res->pcmX = 0.0; res->pcmY = 0.0; if constexpr (mcc_is_equatorial_mount) { // equatorial if (_pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY || _pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE) { const auto cosPhi = std::cos(_pcmData.siteLatitude); const auto sinPhi = std::sin(_pcmData.siteLatitude); const auto tanY = std::tan(pt.Y); const auto sinX = std::sin(pt.X); const auto cosX = std::cos(pt.X); const auto cosY = std::cos(pt.Y); if (utils::isEqual(cosY, 0.0)) { res->pcmX = _pcmData.geomCoefficients.zeroPointX; } else { res->pcmX = _pcmData.geomCoefficients.zeroPointX + _pcmData.geomCoefficients.collimationErr / cosY + _pcmData.geomCoefficients.nonperpendErr * tanY - _pcmData.geomCoefficients.misalignErr1 * cosX * tanY + _pcmData.geomCoefficients.misalignErr2 * sinX * tanY + _pcmData.geomCoefficients.tubeFlexure * cosPhi * sinX / cosY - _pcmData.geomCoefficients.DECaxisFlexure * (cosPhi * cosX + sinPhi * tanY); } res->pcmY = _pcmData.geomCoefficients.zeroPointY + _pcmData.geomCoefficients.misalignErr1 * sinX + _pcmData.geomCoefficients.misalignErr2 * cosX + _pcmData.geomCoefficients.tubeFlexure * (cosPhi * cosX * std::sin(pt.Y) - sinPhi * cosY); if constexpr (mountType == MccMountType::FORK_TYPE) { if (!utils::isEqual(cosX, 0.0)) { res->pcmY += _pcmData.geomCoefficients.forkFlexure / cosX; } } } if (_pcmData.type == MccDefaultPCMType::PCM_TYPE_BSPLINE || _pcmData.type == MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE) { double spl_valX, spl_valY; int ret = bsplines::fitpack_eval_spl2d(_pcmData.bspline.knotsX, _pcmData.bspline.knotsY, _pcmData.bspline.coeffsX, pt.X, pt.Y, spl_valX, _pcmData.bspline.bsplDegreeX, _pcmData.bspline.bsplDegreeY); if (ret) { res->pcmX = std::numeric_limits::quiet_NaN(); res->pcmY = std::numeric_limits::quiet_NaN(); return MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV; } ret = bsplines::fitpack_eval_spl2d(_pcmData.bspline.knotsX, _pcmData.bspline.knotsY, _pcmData.bspline.coeffsY, pt.X, pt.Y, spl_valY, _pcmData.bspline.bsplDegreeX, _pcmData.bspline.bsplDegreeY); if (ret) { res->pcmX = std::numeric_limits::quiet_NaN(); res->pcmY = std::numeric_limits::quiet_NaN(); return MccDefaultPCMErrorCode::ERROR_INVALID_INPUTS_BISPLEV; } res->pcmX += spl_valX; res->pcmY += spl_valY; } } else if constexpr (mcc_is_altaz_mount) { static_assert(false, "NOT IMPLEMENTED!"); } else { static_assert(false, "UNSUPPORTED"); } if constexpr (!std::is_null_pointer_v) { if constexpr (mcc_eqt_hrz_coord_c) { if constexpr (mccIsEquatorialMount(mountType)) { app_pt->HA = pt.X + res->pcmX; app_pt->DEC_APP = pt.Y + res->pcmY; } else if constexpr (mccIsAltAzMount(mountType)) { app_pt->AZ = pt.X + res->pcmX; app_pt->ZD = pt.Y + res->pcmY; } else { static_assert(false, "UNKNOW MOUNT TYPE!"); } } else { app_pt->X = pt.X + res->pcmX; app_pt->Y = pt.Y + res->pcmY; } } return MccDefaultPCMErrorCode::ERROR_OK; } template error_t computeInversePCM(T app_pt, mcc_PCM_result_c auto* result, mcc_celestial_point_c auto* hw_pt = nullptr) requires(mcc_celestial_point_c || mcc_eqt_hrz_coord_c) { // for small corrections only!!! auto ret = computePCM(std::move(app_pt), result); if (ret) { return ret; } result->pcmX = -result->pcmX; result->pcmY = -result->pcmY; if (hw_pt != nullptr) { if constexpr (mcc_eqt_hrz_coord_c) { if constexpr (mccIsEquatorialMount(mountType)) { hw_pt->X = app_pt.HA + result->pcmX; hw_pt->Y = app_pt.DEC_APP + result->pcmY; } else if constexpr (mccIsAltAzMount(mountType)) { hw_pt->X = app_pt.AZ + result->pcmX; hw_pt->Y = app_pt.ZD + result->pcmY; } else { static_assert(false, "UNKNOW MOUNT TYPE!"); } } else { hw_pt->X = app_pt.X + result->pcmX; hw_pt->Y = app_pt.Y + result->pcmY; } } return ret; } private: pcm_data_t _pcmData; std::unique_ptr _pcmDataMutex; }; typedef MccDefaultPCM MccMountDefaultAltAzPec; typedef MccDefaultPCM MccMountDefaultForkPec; static_assert(mcc_PCM_c, ""); static_assert(std::movable); } // namespace mcc