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 <format>
#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;
namespace km = Karana::Math;

namespace Karana::Math {
    /**
     * @struct FloatQuantityHelper
     * @brief Simple wrapper struct to bring Length into pybind11.
     */
    struct FloatQuantityHelper {
        /**
         * @brief Default constructor. Used by pybind11 caster.
         */
        FloatQuantityHelper(){};

        /**
         * @brief Constructor with Length value as a double.
         *
         * @param v The value to store.
         */
        FloatQuantityHelper(double v)
            : value(v){};

        /// The value expressed as a double.
        double value;

        /**
         * @brief Conversion operator to double.
         *
         * @return The internal value.
         */
        operator double() const { return value; }
    };

    /**
     * @struct Length
     * @brief Simple wrapper struct to bring Length into pybind11.
     */
    struct Length : FloatQuantityHelper {};

    /**
     * @struct Angle
     * @brief Simple wrapper struct to bring Angle into pybind11.
     */
    struct Angle : FloatQuantityHelper {};

    /**
     * @struct Vec3QuantityHelper
     * @brief Simple wrapper struct to bring Vec3 quantities into pybind11.
     */
    struct Vec3QuantityHelper {
        /**
         * @brief Default constructor. Used by pybind11 caster.
         */
        Vec3QuantityHelper(){};

        /**
         * @brief Constructor with Length value as a double.
         *
         * @param v The value to store.
         */
        Vec3QuantityHelper(Vec3 v)
            : value(v){};

        /// The value expressed as a double.
        Vec3 value;

        /**
         * @brief Conversion operator to double.
         *
         * @return The internal value.
         */
        operator Vec3() const { return value; }
    };

    /**
     * @struct Length3
     * @brief Simple wrapper struct to bring Length3 into pybind11.
     */
    struct Length3 : Vec3QuantityHelper {};

} // namespace Karana::Math

namespace pybind11::detail {

#define FLOAT_QUANTITY(cpp_type, py_type, quantity_name)                                           \
    template <> struct type_caster<cpp_type> {                                                     \
      public:                                                                                      \
        PYBIND11_TYPE_CASTER(cpp_type,                                                             \
                             pybind11::detail::io_name("Union[typing.SupportsFloat|" #py_type "]", \
                                                       #py_type));                                 \
                                                                                                   \
        /* Conversion from Python -> C++ */                                                        \
        bool load(py::handle src, bool) {                                                          \
            if (!src)                                                                              \
                return false;                                                                      \
                                                                                                   \
            /* Imports for conversion */                                                           \
            py::object py_quantity = py::module_::import("pint").attr("Quantity");                 \
            py::object py_length =                                                                 \
                py::module_::import("Karana.Math.Kquantities").attr(#quantity_name);               \
                                                                                                   \
            py::object obj = py::reinterpret_borrow<py::object>(src);                              \
                                                                                                   \
            if (py::isinstance(obj, py_quantity)) {                                                \
                if (py::cast<bool>(obj.attr("check")(py_length))) {                                \
                    /* It's a quantity, convert to double */                                       \
                    value.value = py::cast<double>(obj.attr("to_base_units")().attr("m"));         \
                } else {                                                                           \
                    throw py::type_error(                                                          \
                        std::format("Expected a " #quantity_name " quantity, but got units of {}", \
                                    py::cast<std::string>(py::str(obj.attr("u")))));               \
                }                                                                                  \
            } else {                                                                               \
                /* Assume it's a regular float */                                                  \
                value.value = py::cast<double>(obj);                                               \
            }                                                                                      \
                                                                                                   \
            return true;                                                                           \
        }                                                                                          \
                                                                                                   \
        /* Conversion C++ -> Python is double -> Quantity */                                       \
        static py::handle cast(cpp_type src, py::return_value_policy, py::handle) {                \
            py::object kq = py::module_::import("Karana.Math.Kquantities");                        \
            py::object res =                                                                       \
                py::float_(src.value) * kq.attr("getDefaultUnits")(kq.attr(#quantity_name));       \
            return res.release();                                                                  \
        }                                                                                          \
    };

    FLOAT_QUANTITY(Karana::Math::Length, Karana.Math.Ktyping.Length, length);
    FLOAT_QUANTITY(Karana::Math::Angle, Karana.Math.Ktyping.Angle, angle);

#define VEC3_QUANTITY(cpp_type, py_type, quantity_name)                                            \
    template <> struct type_caster<Karana::Math::Length3> {                                        \
      public:                                                                                      \
        PYBIND11_TYPE_CASTER(Karana::Math::Length3,                                                \
                             pybind11::detail::io_name("Union[numpy.typing.ArrayLike|" #py_type    \
                                                       "]",                                        \
                                                       #py_type));                                 \
                                                                                                   \
        /* Conversion from Python -> C++ */                                                        \
        bool load(py::handle src, bool) {                                                          \
                                                                                                   \
            if (!src)                                                                              \
                return false;                                                                      \
                                                                                                   \
            /* Imports for conversion */                                                           \
            py::object py_quantity = py::module_::import("pint").attr("Quantity");                 \
            py::object py_length =                                                                 \
                py::module_::import("Karana.Math.Kquantities").attr(#quantity_name);               \
                                                                                                   \
            py::object obj = py::reinterpret_borrow<py::object>(src);                              \
                                                                                                   \
            if (py::isinstance(obj, py_quantity)) {                                                \
                                                                                                   \
                if (py::cast<bool>(obj.attr("check")(py_length))) {                                \
                                                                                                   \
                    /* It's a quantity, convert to double */                                       \
                    value.value = py::cast<km::Vec3>(obj.attr("to_base_units")().attr("m"));       \
                } else {                                                                           \
                                                                                                   \
                    throw py::type_error(                                                          \
                        std::format("Expected a Length3 quantity, but got units of {}",            \
                                    py::cast<std::string>(py::str(obj.attr("u")))));               \
                }                                                                                  \
                                                                                                   \
            } else {                                                                               \
                                                                                                   \
                /* Assume it's a regular Vec3 */                                                   \
                value.value = py::cast<km::Vec3>(obj);                                             \
            }                                                                                      \
            return true;                                                                           \
        }                                                                                          \
                                                                                                   \
        /* Conversion C++ -> Python is Vec3 -> Quantity */                                         \
        static py::handle cast(Karana::Math::Length3 src, py::return_value_policy, py::handle) {   \
                                                                                                   \
            py::object kq = py::module_::import("Karana.Math.Kquantities");                        \
                                                                                                   \
            py::object res =                                                                       \
                py::cast(src.value) * kq.attr("getDefaultUnits")(kq.attr(#quantity_name));         \
            return res.release();                                                                  \
        }                                                                                          \
    };

    VEC3_QUANTITY(Karana::Math::Length3, Karana.Math.Ktyping.Length3, length);

} // namespace pybind11::detail