Program Listing for File pybind11Kquantities.h

Program Listing for File pybind11Kquantities.h#

Return to documentation for file (include/Karana/Math/pybind11Kquantities.h)

/*
 * Copyright (c) 2024-2026 Karana Dynamics Pty Ltd. All rights reserved.
 *
 * NOTICE TO USER:
 *
 * This source code and/or documentation (the "Licensed Materials") is
 * the confidential and proprietary information of Karana Dynamics Inc.
 * Use of these Licensed Materials is governed by the terms and conditions
 * of a separate software license agreement between Karana Dynamics and the
 * Licensee ("License Agreement"). Unless expressly permitted under that
 * agreement, any reproduction, modification, distribution, or disclosure
 * of the Licensed Materials, in whole or in part, to any third party
 * without the prior written consent of Karana Dynamics is strictly prohibited.
 *
 * THE LICENSED MATERIALS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
 * KARANA DYNAMICS DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL KARANA DYNAMICS BE LIABLE FOR ANY DAMAGES WHATSOEVER,
 * INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA, OR USE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, WHETHER IN CONTRACT, TORT,
 * OR OTHERWISE ARISING OUT OF OR IN CONNECTION WITH THE LICENSED MATERIALS.
 *
 * U.S. Government End Users: The Licensed Materials are a "commercial item"
 * as defined at 48 C.F.R. 2.101, and are provided to the U.S. Government
 * only as a commercial end item under the terms of this license.
 *
 * Any use of the Licensed Materials in individual or commercial software must
 * include, in the user documentation and internal source code comments,
 * this Notice, Disclaimer, and U.S. Government Use Provision.
 */

#pragma once

#include "Karana/Math/Defs.h"

#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>

#include <type_traits>
#include <utility>

namespace Karana::Math {

    /** Metadata for a length quantity. */
    struct LengthTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Length";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "length";
    };

    /** Metadata for an angle quantity. */
    struct AngleTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Angle";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "angle";
    };

    /** Metadata for a mass quantity. */
    struct MassTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Mass";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "mass";
    };

    /** Metadata for a gravitational-parameter quantity. */
    struct GravitationalParameterTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.GravitationalParameter";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "gravitational_parameter";
    };

    /** Metadata for a linear spring constant quantity. */
    struct LinearSpringConstantTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.LinearSpringConstant";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "linear_spring_constant";
    };

    /** Metadata for a linear damping constant quantity. */
    struct LinearDampingConstantTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.LinearDampingConstant";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "linear_damping_constant";
    };

    /** Metadata for a velocity quantity. */
    struct VelocityTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Velocity";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "velocity";
    };

    /** Metadata for an angular velocity quantity. */
    struct AngularVelocityTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.AngularVelocity";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "angular_velocity";
    };

    /** Metadata for an acceleration quantity. */
    struct AccelerationTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Acceleration";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "acceleration";
    };

    /** Metadata for an angular acceleration quantity. */
    struct AngularAccelerationTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.AngularAcceleration";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "angular_acceleration";
    };

    /** Metadata for a force quantity. */
    struct ForceTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Force";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "force";
    };

    /** Metadata for a torque quantity. */
    struct TorqueTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Torque";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "torque";
    };

    /** Metadata for a momentum quantity. */
    struct MomentumTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Momentum";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "momentum";
    };

    /** Metadata for an angular momentum quantity. */
    struct AngularMomentumTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.AngularMomentum";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "angular_momentum";
    };

    /** Metadata for a modal stiffness quantity. */
    struct ModalStiffnessTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.ModalStiffness";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "modal_stiffness";
    };

    /** Metadata for a modal damping quantity. */
    struct ModalDampingTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.ModalDamping";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "modal_damping";
    };

    /** Metadata for an inertia quantity. */
    struct InertiaTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Inertia";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "inertia";
    };

    /** Metadata for a translational nodal matrix quantity. */
    struct TranslationalNodalMatrixTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.TranslationalNodalMatrix";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "translational_nodal_matrix";
    };

    /** Metadata for a rotational nodal matrix quantity. */
    struct RotationalNodalMatrixTag {
        /// Fully qualified Python typing name for this quantity.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.RotationalNodalMatrix";
        /// Attribute name in `Karana.Math.Kquantities`.
        static constexpr char quantityName[] = "rotational_nodal_matrix";
    };

    /**
     * @struct QuantityValueTyping
     * @brief Describes the Python annotation for a C++ magnitude type.
     *
     * The primary template preserves support for arbitrary magnitude types by
     * allowing the pybind11 caster to supply its normal Python annotation.
     *
     * @tparam Value The C++ type used to store a quantity magnitude.
     */
    template <typename Value> struct QuantityValueTyping {
        /// Whether `pythonTypeName` supplies an explicit bracket annotation.
        static constexpr bool hasPythonTypeName = false;

        /// Whether `pythonTypeName` names a Karana Ktyping alias.
        static constexpr bool hasKtypingName = false;

        /// Explicit Python type name, empty when no name is registered.
        static constexpr char pythonTypeName[] = "";
    };

    /** Python typing metadata for a double-precision scalar magnitude. */
    template <> struct QuantityValueTyping<double> {
        /// Whether `pythonTypeName` supplies an explicit bracket annotation.
        static constexpr bool hasPythonTypeName = true;

        /// Whether `pythonTypeName` names a Karana Ktyping alias.
        static constexpr bool hasKtypingName = false;

        /// Python annotation used inside the quantity generic type.
        static constexpr char pythonTypeName[] = "float";
    };

    /** Ktyping metadata for a fixed-size three-vector magnitude. */
    template <> struct QuantityValueTyping<Vec3> {
        /// Whether `pythonTypeName` supplies an explicit bracket annotation.
        static constexpr bool hasPythonTypeName = true;

        /// Whether `pythonTypeName` names a Karana Ktyping alias.
        static constexpr bool hasKtypingName = true;

        /// Fully qualified Ktyping alias for this magnitude type.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Vec3";
    };

    /** Ktyping metadata for a variable-length vector magnitude. */
    template <> struct QuantityValueTyping<Vec> {
        /// Whether `pythonTypeName` supplies an explicit bracket annotation.
        static constexpr bool hasPythonTypeName = true;

        /// Whether `pythonTypeName` names a Karana Ktyping alias.
        static constexpr bool hasKtypingName = true;

        /// Fully qualified Ktyping alias for this magnitude type.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Vec";
    };

    /** Ktyping metadata for a fixed-size three-by-three matrix magnitude. */
    template <> struct QuantityValueTyping<Mat33> {
        /// Whether `pythonTypeName` supplies an explicit bracket annotation.
        static constexpr bool hasPythonTypeName = true;

        /// Whether `pythonTypeName` names a Karana Ktyping alias.
        static constexpr bool hasKtypingName = true;

        /// Fully qualified Ktyping alias for this magnitude type.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Mat33";
    };

    /** Ktyping metadata for a three-by-N matrix magnitude. */
    template <> struct QuantityValueTyping<Mat3n> {
        /// Whether `pythonTypeName` supplies an explicit bracket annotation.
        static constexpr bool hasPythonTypeName = true;

        /// Whether `pythonTypeName` names a Karana Ktyping alias.
        static constexpr bool hasKtypingName = true;

        /// Fully qualified Ktyping alias for this magnitude type.
        static constexpr char pythonTypeName[] = "Karana.Math.Ktyping.Mat3n";
    };

    /**
     * @struct Quantity
     * @brief Associates a C++ value with physical-quantity metadata for pybind11.
     *
     * @tparam Value The scalar, vector, or matrix type holding the magnitude.
     * @tparam Tag The quantity metadata tag used to select Pint units.
     */
    template <typename Value, typename Tag> struct Quantity {
        /// Type holding the quantity magnitude.
        using ValueType = Value;

        /// Metadata tag describing the physical quantity.
        using TagType = Tag;

        /** @brief Default constructor used by the pybind11 caster. */
        Quantity() = default;

        /**
         * @brief Constructs a quantity from its magnitude.
         * @param magnitude The value to store.
         */
        Quantity(Value magnitude)
            : value(std::move(magnitude)) {}

        /// The magnitude expressed as the selected value type.
        Value value{};

        /**
         * @brief Converts to a mutable magnitude reference.
         * @return The stored magnitude.
         */
        operator Value &() { return value; }

        /**
         * @brief Converts to a constant magnitude reference.
         * @return The stored magnitude.
         */
        operator const Value &() const { return value; }
    };

    /**
     * @brief Length quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Length = Quantity<Value, LengthTag>;
    /**
     * @brief Angle quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Angle = Quantity<Value, AngleTag>;
    /**
     * @brief Mass quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Mass = Quantity<Value, MassTag>;
    /**
     * @brief Gravitational-parameter quantity with a caller-selected magnitude
     * type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value>
    using GravitationalParameter = Quantity<Value, GravitationalParameterTag>;
    /**
     * @brief Linear spring constant quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using LinearSpringConstant = Quantity<Value, LinearSpringConstantTag>;
    /**
     * @brief Linear damping constant quantity with a caller-selected magnitude
     * type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value>
    using LinearDampingConstant = Quantity<Value, LinearDampingConstantTag>;
    /**
     * @brief Velocity quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Velocity = Quantity<Value, VelocityTag>;
    /**
     * @brief Angular velocity quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using AngularVelocity = Quantity<Value, AngularVelocityTag>;
    /**
     * @brief Acceleration quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Acceleration = Quantity<Value, AccelerationTag>;
    /**
     * @brief Angular acceleration quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using AngularAcceleration = Quantity<Value, AngularAccelerationTag>;
    /**
     * @brief Force quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Force = Quantity<Value, ForceTag>;
    /**
     * @brief Torque quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Torque = Quantity<Value, TorqueTag>;
    /**
     * @brief Momentum quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Momentum = Quantity<Value, MomentumTag>;
    /**
     * @brief Angular momentum quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using AngularMomentum = Quantity<Value, AngularMomentumTag>;
    /**
     * @brief Modal stiffness quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using ModalStiffness = Quantity<Value, ModalStiffnessTag>;
    /**
     * @brief Modal damping quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using ModalDamping = Quantity<Value, ModalDampingTag>;
    /**
     * @brief Inertia quantity with a caller-selected magnitude type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value> using Inertia = Quantity<Value, InertiaTag>;
    /**
     * @brief Translational nodal matrix quantity with a caller-selected magnitude
     * type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value>
    using TranslationalNodalMatrix = Quantity<Value, TranslationalNodalMatrixTag>;
    /**
     * @brief Rotational nodal matrix quantity with a caller-selected magnitude
     * type.
     * @tparam Value The type used to store the quantity magnitude.
     */
    template <typename Value>
    using RotationalNodalMatrix = Quantity<Value, RotationalNodalMatrixTag>;

} // namespace Karana::Math

namespace pybind11::detail {

    /**
     * @brief Converts every Karana quantity value between C++ and Pint.
     *
     * @tparam Value The scalar, vector, or matrix magnitude type.
     * @tparam Tag The quantity metadata tag selecting the Pint dimension and units.
     */
    template <typename Value, typename Tag> struct type_caster<Karana::Math::Quantity<Value, Tag>> {
      private:
        /// Concrete quantity type handled by this caster specialization.
        using type = Karana::Math::Quantity<Value, Tag>;

        /// Underlying pybind11 caster for the quantity magnitude.
        using value_caster = make_caster<Value>;

        /// Python typing metadata for the quantity magnitude.
        using value_typing = Karana::Math::QuantityValueTyping<Value>;

        /// Python annotation for the magnitude type.
        static constexpr auto valueTypeName = const_name<value_typing::hasPythonTypeName>(
            const_name(value_typing::pythonTypeName), value_caster::name);

        /// Python quantity annotation parameterized by the magnitude annotation.
        static constexpr auto quantityTypeName =
            const_name(Tag::pythonTypeName) + const_name("[") + valueTypeName + const_name("]");

        /// Input and output annotation used for generated Python signatures.
        static constexpr auto casterName = const_name<value_typing::hasKtypingName>(
            io_name(value_typing::pythonTypeName, "") + io_name(" | ", "") + quantityTypeName,
            const_name<std::is_arithmetic_v<Value>>(
                io_name("typing.SupportsFloat | typing.SupportsIndex | ", ""),
                io_name("numpy.typing.ArrayLike | ", "")) +
                quantityTypeName);

      public:
        /// Type caster for pint quantities and associated C++ values
        PYBIND11_TYPE_CASTER(type, casterName);

        /**
         * @brief Converts a Pint quantity or bare Python magnitude to C++.
         * @param src The source Python object.
         * @param convert Whether implicit conversions are allowed.
         * @return True when the source has the expected dimension and value shape.
         */
        bool load(handle src, bool convert) {
            if (!src) {
                return false;
            }

            object magnitude = reinterpret_borrow<object>(src);
            const object pintQuantity = module_::import("pint").attr("Quantity");

            if (isinstance(magnitude, pintQuantity)) {
                const object kquantities = module_::import("Karana.Math.Kquantities");
                const object expectedQuantity = kquantities.attr(Tag::quantityName);

                if (!pybind11::cast<bool>(magnitude.attr("check")(expectedQuantity))) {
                    return false;
                }

                // Rebuild with Karana's registry before converting to avoid Pint
                // registry mismatches for quantities created by another registry.
                magnitude = kquantities.attr("ureg")
                                .attr("Quantity")
                                .attr("from_tuple")(magnitude.attr("to_tuple")())
                                .attr("to_base_units")()
                                .attr("m");
            }

            value_caster magnitudeCaster;
            if (!magnitudeCaster.load(magnitude, convert)) {
                return false;
            }

            value.value = cast_op<Value &&>(std::move(magnitudeCaster));
            return true;
        }

        /**
         * @brief Converts a C++ quantity to a Pint quantity in default units.
         * @param src The source C++ quantity.
         * @param policy The pybind11 return-value policy for the magnitude.
         * @param parent The parent Python object, when applicable.
         * @return A new reference to the resulting Pint quantity.
         */
        static handle cast(const type &src, return_value_policy policy, handle parent) {
            const object kquantities = module_::import("Karana.Math.Kquantities");
            object magnitude =
                reinterpret_steal<object>(value_caster::cast(src.value, policy, parent));
            object result = magnitude * kquantities.attr("getDefaultUnits")(
                                            kquantities.attr(Tag::quantityName));
            return result.release();
        }
    };

} // namespace pybind11::detail