Program Listing for File simdVector.hpp#

Return to documentation for file (librapid/include/librapid/math/simdVector.hpp)

#ifndef LIBRAPID_MATH_SIMD_VECTOR_HPP
#define LIBRAPID_MATH_SIMD_VECTOR_HPP

namespace librapid {
    template<typename Scalar, int64_t Dims = 3>
    class SIMDVector {
    public:
        using StorageType = Vc::SimdArray<Scalar, Dims>;

        SIMDVector() = default;

        explicit SIMDVector(const StorageType &arr);

        template<typename T, typename ABI>
        explicit SIMDVector(const Vc::Vector<T, ABI> &arr);

        template<typename S, int64_t D>
        SIMDVector(const SIMDVector<S, D> &other);

        template<typename... Args, std::enable_if_t<sizeof...(Args) == Dims, int> = 0>
        SIMDVector(Args... args);

        template<typename... Args, int64_t size = sizeof...(Args),
                 typename std::enable_if_t<size != Dims, int> = 0>
        SIMDVector(Args... args);

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        SIMDVector(const std::initializer_list<T> &list);

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        SIMDVector(const std::vector<T> &list);

        SIMDVector(const SIMDVector &other) = default;

        SIMDVector(SIMDVector &&other) noexcept = default;

        SIMDVector &operator=(const SIMDVector &other) = default;

        SIMDVector &operator=(SIMDVector &&other) noexcept = default;

        // Implement conversion to and from GLM data types
#ifdef GLM_VERSION

        template<glm::qualifier p = glm::defaultp>
        SIMDVector(const glm::vec<Dimscalar, p> &vec);

        template<glm::qualifier p = glm::defaultp>
        operator glm::vec<Dimscalar, p>() const;

#endif // GLM_VERSION

        LIBRAPID_NODISCARD const auto operator[](int64_t index) const;

        LIBRAPID_NODISCARD auto operator[](int64_t index);

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector &operator-=(const SIMDVector<T, d> &other);

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector &operator+=(const SIMDVector<T, d> &other);

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector &operator*=(const SIMDVector<T, d> &other);

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector &operator/=(const SIMDVector<T, d> &other);

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector &operator+=(const T &value);

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector &operator-=(const T &value);

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector &operator*=(const T &value);

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector &operator/=(const T &value);

        LIBRAPID_ALWAYS_INLINE SIMDVector operator-() const;

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector cmp(const SIMDVector<T, d> &other,
                                              const char *mode) const;

        template<typename T>
        LIBRAPID_ALWAYS_INLINE SIMDVector cmp(const T &value, const char *mode) const;

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator<(const SIMDVector<T, d> &other) const;

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator<=(const SIMDVector<T, d> &other) const;

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator>(const SIMDVector<T, d> &other) const;

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator>=(const SIMDVector<T, d> &other) const;

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator==(const SIMDVector<T, d> &other) const;

        template<typename T, int64_t d>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator!=(const SIMDVector<T, d> &other) const;

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator<(const T &other) const;

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator<=(const T &other) const;

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator>(const T &other) const;

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator>=(const T &other) const;

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator==(const T &other) const;

        template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int> = 0>
        LIBRAPID_ALWAYS_INLINE SIMDVector operator!=(const T &other) const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Scalar mag2() const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Scalar mag() const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Scalar invMag() const { return 1.0 / mag(); }

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector norm() const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE Scalar dot(const SIMDVector &other) const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector cross(const SIMDVector &other) const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector proj(const SIMDVector &other) const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE explicit operator bool() const;

        LIBRAPID_ALWAYS_INLINE Scalar x() const;

        LIBRAPID_ALWAYS_INLINE Scalar y() const;

        LIBRAPID_ALWAYS_INLINE Scalar z() const;

        LIBRAPID_ALWAYS_INLINE Scalar w() const;

        template<size_t... Indices>
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, sizeof...(Indices)>
        swizzle() const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 2> xy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 2> yx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 2> xz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 2> zx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 2> yz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 2> zy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> xyz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> xzy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> yxz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> yzx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> zxy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> zyx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> xyw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> xwy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> yxw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> ywx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> wxy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> wyx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> xzw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> xwz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> zxw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> zwx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> wxz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> wzx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> yzw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> ywz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> zyw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> zwy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> wyz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 3> wzy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> xyzw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> xywz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> xzyw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> xzwy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> xwyz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> xwzy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> yxzw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> yxwz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> yzxw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> yzwx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> ywxz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> ywzx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> zxyw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> zxwy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> zyxw() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> zywx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> zwxy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> zwyx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> wxyz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> wxzy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> wyxz() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> wyzx() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> wzxy() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, 4> wzyx() const;

        LIBRAPID_ALWAYS_INLINE void x(Scalar val);

        LIBRAPID_ALWAYS_INLINE void y(Scalar val);

        LIBRAPID_ALWAYS_INLINE void z(Scalar val);

        LIBRAPID_ALWAYS_INLINE void w(Scalar val);

        LIBRAPID_ALWAYS_INLINE void xy(const SIMDVector<Scalar, 2> &v);
        LIBRAPID_ALWAYS_INLINE void yx(const SIMDVector<Scalar, 2> &v);
        LIBRAPID_ALWAYS_INLINE void xz(const SIMDVector<Scalar, 2> &v);
        LIBRAPID_ALWAYS_INLINE void zx(const SIMDVector<Scalar, 2> &v);
        LIBRAPID_ALWAYS_INLINE void yz(const SIMDVector<Scalar, 2> &v);
        LIBRAPID_ALWAYS_INLINE void zy(const SIMDVector<Scalar, 2> &v);
        LIBRAPID_ALWAYS_INLINE void xyz(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void xzy(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void yxz(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void yzx(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void zxy(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void zyx(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void xyw(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void xwy(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void yxw(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void ywx(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void wxy(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void wyx(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void xzw(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void xwz(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void zxw(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void zwx(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void wxz(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void wzx(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void yzw(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void ywz(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void zyw(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void zwy(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void wyz(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void wzy(const SIMDVector<Scalar, 3> &v);
        LIBRAPID_ALWAYS_INLINE void xyzw(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void xywz(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void xzyw(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void xzwy(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void xwyz(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void xwzy(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void yxzw(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void yxwz(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void yzxw(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void yzwx(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void ywxz(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void ywzx(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void zxyw(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void zxwy(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void zyxw(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void zywx(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void zwxy(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void zwyx(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void wxyz(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void wxzy(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void wyxz(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void wyzx(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void wzxy(const SIMDVector<Scalar, 4> &v);
        LIBRAPID_ALWAYS_INLINE void wzyx(const SIMDVector<Scalar, 4> &v);

        LIBRAPID_ALWAYS_INLINE const StorageType &data() const;

        LIBRAPID_ALWAYS_INLINE StorageType &data();

        LIBRAPID_NODISCARD std::string str(const std::string &formatString = "{}") const;

    protected:
        StorageType m_data {};
    };

    template<typename Scalar, int64_t Dims>
    SIMDVector<Scalar, Dims>::SIMDVector(const StorageType &arr) : m_data {arr} {}

    template<typename Scalar, int64_t Dims>
    template<typename T, typename ABI>
    SIMDVector<Scalar, Dims>::SIMDVector(const Vc::Vector<T, ABI> &arr) : m_data {arr} {}

    template<typename Scalar, int64_t Dims>
    template<typename S, int64_t D>
    SIMDVector<Scalar, Dims>::SIMDVector(const SIMDVector<S, D> &other) {
        for (int64_t i = 0; i < min(Dims, D); ++i) { m_data[i] = static_cast<Scalar>(other[i]); }
    }

    template<typename Scalar, int64_t Dims>
    template<typename... Args, std::enable_if_t<sizeof...(Args) == Dims, int>>
    SIMDVector<Scalar, Dims>::SIMDVector(Args... args) : m_data {static_cast<Scalar>(args)...} {
        static_assert(sizeof...(Args) <= Dims, "Invalid number of arguments");
    }

    template<typename Scalar, int64_t Dims>
    template<typename... Args, int64_t size, std::enable_if_t<size != Dims, int>>
    SIMDVector<Scalar, Dims>::SIMDVector(Args... args) {
        static_assert(sizeof...(Args) <= Dims, "Invalid number of arguments");
        if constexpr (size == 1) {
            m_data = StorageType(static_cast<Scalar>(args)...);
        } else {
            const Scalar expanded[] = {static_cast<Scalar>(args)...};
            for (int64_t i = 0; i < size; i++) { m_data[i] = expanded[i]; }
        }
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    SIMDVector<Scalar, Dims>::SIMDVector(const std::initializer_list<T> &list) {
        LIBRAPID_ASSERT(list.size() <= Dims,
                        "Cannot initialize {}-dimensional vector with {} elements",
                        Dims,
                        list.size());
        int64_t i = 0;
        for (const Scalar &val : list) { m_data[i++] = val; }
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    SIMDVector<Scalar, Dims>::SIMDVector(const std::vector<T> &list) {
        LIBRAPID_ASSERT(list.size() <= Dims,
                        "Cannot initialize {} dimensional vector with {} elements",
                        Dims,
                        list.size());
        int64_t i = 0;
        for (const Scalar &val : list) { m_data[i++] = val; }
    }

    template<typename Scalar, int64_t Dims1, int64_t Dims2>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims1>
    operator&(const SIMDVector<Scalar, Dims1> &vec, const SIMDVector<Scalar, Dims2> &mask) {
        constexpr int64_t m = Dims1 < Dims2 ? Dims1 : Dims2;
        SIMDVector<Scalar, m> res(vec);
        for (size_t i = 0; i < m; ++i)
            if (!mask[i]) res[i] = 0;
        return res;
    }

#ifdef GLM_VERSION

    template<typename Scalar, int64_t Dims>
    template<glm::qualifier p>
    SIMDVector<Scalar, Dims>::SIMDVector(const glm::vec<Dimscalar, p> &vec) {
        for (size_t i = 0; i < Dims; ++i) { m_data[i] = vec[i]; }
    }

    template<typename Scalar, int64_t Dims>
    template<glm::qualifier p>
    SIMDVector<Scalar, Dims>::operator glm::vec<Dimscalar, p>() const {
        glm::vec<Dimscalar, p> res;
        for (size_t i = 0; i < Dims; ++i) { res[i] = m_data[i]; }
        return res;
    }

#endif // GLM_VERSION

    template<typename Scalar, int64_t Dims>
    const auto SIMDVector<Scalar, Dims>::operator[](int64_t index) const {
        LIBRAPID_ASSERT(0 <= index && index < Dims,
                        "Index {} out of range for vector with {} dimensions",
                        index,
                        Dims);
        return m_data[index];
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::operator[](int64_t index) {
        LIBRAPID_ASSERT(0 <= index && index < Dims,
                        "Index {} out of range for vector with {} dimensions",
                        index,
                        Dims);
        return m_data[index];
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator+=(const SIMDVector<T, d> &other) -> SIMDVector & {
        static_assert(d <= Dims, "Invalid number of dimensions");
        if constexpr (Dims == d) {
            m_data += other.m_data;
        } else {
            constexpr int64_t m = d < Dims ? d : Dims;
            for (int64_t i = 0; i < m; ++i) { m_data[i] += other[i]; }
        }
        return *this;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator-=(const SIMDVector<T, d> &other) -> SIMDVector & {
        static_assert(d <= Dims, "Invalid number of dimensions");
        if constexpr (Dims == d) {
            m_data -= other.m_data;
        } else {
            constexpr int64_t m = d < Dims ? d : Dims;
            for (int64_t i = 0; i < m; ++i) { m_data[i] -= other[i]; }
        }
        return *this;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator*=(const SIMDVector<T, d> &other) -> SIMDVector & {
        static_assert(d <= Dims, "Invalid number of dimensions");
        if constexpr (Dims == d) {
            m_data *= other.m_data;
        } else {
            constexpr int64_t m = d < Dims ? d : Dims;
            for (int64_t i = 0; i < m; ++i) { m_data[i] *= other[i]; }
        }
        return *this;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator/=(const SIMDVector<T, d> &other) -> SIMDVector & {
        static_assert(d <= Dims, "Invalid number of dimensions");
        if constexpr (Dims == d) {
            m_data /= other.m_data;
        } else {
            constexpr int64_t m = d < Dims ? d : Dims;
            for (int64_t i = 0; i < m; ++i) { m_data[i] /= other[i]; }
        }
        return *this;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator+=(const T &value) -> SIMDVector & {
        m_data += static_cast<Scalar>(value);
        return *this;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator-=(const T &value) -> SIMDVector & {
        m_data -= static_cast<Scalar>(value);
        return *this;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator*=(const T &value) -> SIMDVector & {
        m_data *= static_cast<Scalar>(value);
        return *this;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator/=(const T &value) -> SIMDVector & {
        m_data /= static_cast<Scalar>(value);
        return *this;
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::operator-() const -> SIMDVector {
        SIMDVector res(*this);
        res *= -1;
        return res;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::cmp(const SIMDVector<T, d> &other, const char *mode) const
      -> SIMDVector {
        SIMDVector res(*this);
        int16_t modeInt = *(int16_t *)mode;
        for (size_t i = 0; i < Dims; ++i) {
            switch (modeInt) {
                case 'e' | ('q' << 8):
                    if (res[i] == other[i])
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'n' | ('e' << 8):
                    if (res[i] != other[i])
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'l' | ('t' << 8):
                    if (res[i] < other[i])
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'l' | ('e' << 8):
                    if (res[i] <= other[i])
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'g' | ('t' << 8):
                    if (res[i] > other[i])
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'g' | ('e' << 8):
                    if (res[i] >= other[i])
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                default: LIBRAPID_ASSERT(false, "Invalid mode {}", mode);
            }
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T>
    auto SIMDVector<Scalar, Dims>::cmp(const T &value, const char *mode) const -> SIMDVector {
        // Mode:
        // 0: ==    1: !=
        // 2: <     3: <=
        // 4: >     5: >=

        SIMDVector res(*this);
        int16_t modeInt = *(int16_t *)mode;
        for (size_t i = 0; i < Dims; ++i) {
            switch (modeInt) {
                case 'e' | ('q' << 8):
                    if (res[i] == Scalar(value))
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'n' | ('e' << 8):
                    if (res[i] != Scalar(value))
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'l' | ('t' << 8):
                    if (res[i] < Scalar(value))
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'l' | ('e' << 8):
                    if (res[i] <= Scalar(value))
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'g' | ('t' << 8):
                    if (res[i] > Scalar(value))
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                case 'g' | ('e' << 8):
                    if (res[i] >= Scalar(value))
                        res[i] = Scalar(1);
                    else
                        res[i] = Scalar(0);
                    break;
                default: LIBRAPID_ASSERT(false, "Invalid mode {}", mode);
            }
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator<(const SIMDVector<T, d> &other) const -> SIMDVector {
        return cmp(other, "lt");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator<=(const SIMDVector<T, d> &other) const -> SIMDVector {
        return cmp(other, "le");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator>(const SIMDVector<T, d> &other) const -> SIMDVector {
        return cmp(other, "gt");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator>=(const SIMDVector<T, d> &other) const -> SIMDVector {
        return cmp(other, "ge");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator==(const SIMDVector<T, d> &other) const -> SIMDVector {
        return cmp(other, "eq");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, int64_t d>
    auto SIMDVector<Scalar, Dims>::operator!=(const SIMDVector<T, d> &other) const -> SIMDVector {
        return cmp(other, "ne");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator<(const T &other) const -> SIMDVector {
        return cmp(other, "lt");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator<=(const T &other) const -> SIMDVector {
        return cmp(other, "le");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator>(const T &other) const -> SIMDVector {
        return cmp(other, "gt");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator>=(const T &other) const -> SIMDVector {
        return cmp(other, "ge");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator==(const T &other) const -> SIMDVector {
        return cmp(other, "eq");
    }

    template<typename Scalar, int64_t Dims>
    template<typename T, std::enable_if_t<std::is_convertible_v<T, Scalar>, int>>
    auto SIMDVector<Scalar, Dims>::operator!=(const T &other) const -> SIMDVector {
        return cmp(other, "ne");
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::mag2() const -> Scalar {
        return (m_data * m_data).sum();
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::mag() const -> Scalar {
        return sqrt(mag2());
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::norm() const -> SIMDVector {
        SIMDVector res(*this);
        res /= mag();
        return res;
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::dot(const SIMDVector &other) const -> Scalar {
        return (m_data * other.m_data).sum();
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::cross(const SIMDVector &other) const -> SIMDVector {
        static_assert(Dims == 3, "Cross product is only defined for 3D Vectors");
        return SIMDVector(y() * other.z() - z() * other.y(),
                          z() * other.x() - x() * other.z(),
                          x() * other.y() - y() * other.x());
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::proj(const SIMDVector &other) const -> SIMDVector {
        return other * (other.dot(*this) / other.mag2());
    }

    template<typename Scalar, int64_t Dims>
    SIMDVector<Scalar, Dims>::operator bool() const {
        for (size_t i = 0; i < Dims; ++i)
            if (m_data[i] != 0) return true;
        return false;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator+(const SIMDVector<Scalar, Dims> &lhs,
                                                              const SIMDVector<Scalar, Dims> &rhs) {
        SIMDVector<Scalar, Dims> res(lhs);
        res += rhs;
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator-(const SIMDVector<Scalar, Dims> &lhs,
                                                              const SIMDVector<Scalar, Dims> &rhs) {
        SIMDVector<Scalar, Dims> res(lhs);
        res -= rhs;
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator*(const SIMDVector<Scalar, Dims> &lhs,
                                                              const SIMDVector<Scalar, Dims> &rhs) {
        SIMDVector<Scalar, Dims> res(lhs);
        res *= rhs;
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator/(const SIMDVector<Scalar, Dims> &lhs,
                                                              const SIMDVector<Scalar, Dims> &rhs) {
        SIMDVector<Scalar, Dims> res(lhs);
        res /= rhs;
        return res;
    }

    template<typename Scalar, int64_t Dims, typename S>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator+(const SIMDVector<Scalar, Dims> &lhs,
                                                              const S &rhs) {
        SIMDVector<Scalar, Dims> res(lhs);
        res += rhs;
        return res;
    }

    template<typename Scalar, int64_t Dims, typename S>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator-(const SIMDVector<Scalar, Dims> &lhs,
                                                              const S &rhs) {
        SIMDVector<Scalar, Dims> res(lhs);
        res -= rhs;
        return res;
    }

    template<typename Scalar, int64_t Dims, typename S>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator*(const SIMDVector<Scalar, Dims> &lhs,
                                                              const S &rhs) {
        SIMDVector<Scalar, Dims> res(lhs);
        res *= rhs;
        return res;
    }

    template<typename Scalar, int64_t Dims, typename S>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator/(const SIMDVector<Scalar, Dims> &lhs,
                                                              const S &rhs) {
        SIMDVector<Scalar, Dims> res(lhs);
        res /= rhs;
        return res;
    }

    template<typename S, typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator+(const S &lhs,
                                                              const SIMDVector<Scalar, Dims> &rhs) {
        using StorageType = typename SIMDVector<Scalar, Dims>::StorageType;
        SIMDVector<Scalar, Dims> res(StorageType(static_cast<Scalar>(lhs)));
        res += rhs;
        return res;
    }

    template<typename S, typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator-(const S &lhs,
                                                              const SIMDVector<Scalar, Dims> &rhs) {
        using StorageType = typename SIMDVector<Scalar, Dims>::StorageType;
        SIMDVector<Scalar, Dims> res(StorageType(static_cast<Scalar>(lhs)));
        res -= rhs;
        return res;
    }

    template<typename S, typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator*(const S &lhs,
                                                              const SIMDVector<Scalar, Dims> &rhs) {
        using StorageType = typename SIMDVector<Scalar, Dims>::StorageType;
        SIMDVector<Scalar, Dims> res(StorageType(static_cast<Scalar>(lhs)));
        res *= rhs;
        return res;
    }

    template<typename S, typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> operator/(const S &lhs,
                                                              const SIMDVector<Scalar, Dims> &rhs) {
        using StorageType = typename SIMDVector<Scalar, Dims>::StorageType;
        SIMDVector<Scalar, Dims> res(StorageType(static_cast<Scalar>(lhs)));
        res /= rhs;
        return res;
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::x() const -> Scalar {
        if constexpr (Dims < 1)
            return 0;
        else
            return m_data[0];
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::y() const -> Scalar {
        if constexpr (Dims < 2)
            return 0;
        else
            return m_data[1];
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::z() const -> Scalar {
        if constexpr (Dims < 3)
            return 0;
        else
            return m_data[2];
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::w() const -> Scalar {
        if constexpr (Dims < 4)
            return 0;
        else
            return m_data[3];
    }

    template<typename Scalar, int64_t Dims>
    template<size_t... Indices>
    auto SIMDVector<Scalar, Dims>::swizzle() const -> SIMDVector<Scalar, sizeof...(Indices)> {
        static_assert(sizeof...(Indices) <= Dims, "Too many indices for swizzle");
        return {m_data[Indices]...};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xy() const -> SIMDVector<Scalar, 2> {
        return {x(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yx() const -> SIMDVector<Scalar, 2> {
        return {y(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xz() const -> SIMDVector<Scalar, 2> {
        return {x(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zx() const -> SIMDVector<Scalar, 2> {
        return {z(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yz() const -> SIMDVector<Scalar, 2> {
        return {y(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zy() const -> SIMDVector<Scalar, 2> {
        return {z(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xyz() const -> SIMDVector<Scalar, 3> {
        return {x(), y(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xzy() const -> SIMDVector<Scalar, 3> {
        return {x(), z(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yxz() const -> SIMDVector<Scalar, 3> {
        return {y(), x(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yzx() const -> SIMDVector<Scalar, 3> {
        return {y(), z(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zxy() const -> SIMDVector<Scalar, 3> {
        return {z(), x(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zyx() const -> SIMDVector<Scalar, 3> {
        return {z(), y(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xyw() const -> SIMDVector<Scalar, 3> {
        return {x(), y(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xwy() const -> SIMDVector<Scalar, 3> {
        return {x(), w(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yxw() const -> SIMDVector<Scalar, 3> {
        return {y(), x(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::ywx() const -> SIMDVector<Scalar, 3> {
        return {y(), w(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wxy() const -> SIMDVector<Scalar, 3> {
        return {w(), x(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wyx() const -> SIMDVector<Scalar, 3> {
        return {w(), y(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xzw() const -> SIMDVector<Scalar, 3> {
        return {x(), z(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xwz() const -> SIMDVector<Scalar, 3> {
        return {x(), w(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zxw() const -> SIMDVector<Scalar, 3> {
        return {z(), x(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zwx() const -> SIMDVector<Scalar, 3> {
        return {z(), w(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wxz() const -> SIMDVector<Scalar, 3> {
        return {w(), x(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wzx() const -> SIMDVector<Scalar, 3> {
        return {w(), z(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yzw() const -> SIMDVector<Scalar, 3> {
        return {y(), z(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::ywz() const -> SIMDVector<Scalar, 3> {
        return {y(), w(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zyw() const -> SIMDVector<Scalar, 3> {
        return {z(), y(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zwy() const -> SIMDVector<Scalar, 3> {
        return {z(), w(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wyz() const -> SIMDVector<Scalar, 3> {
        return {w(), y(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wzy() const -> SIMDVector<Scalar, 3> {
        return {w(), z(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xyzw() const -> SIMDVector<Scalar, 4> {
        return {x(), y(), z(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xywz() const -> SIMDVector<Scalar, 4> {
        return {x(), y(), w(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xzyw() const -> SIMDVector<Scalar, 4> {
        return {x(), z(), y(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xzwy() const -> SIMDVector<Scalar, 4> {
        return {x(), z(), w(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xwyz() const -> SIMDVector<Scalar, 4> {
        return {x(), w(), y(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::xwzy() const -> SIMDVector<Scalar, 4> {
        return {x(), w(), z(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yxzw() const -> SIMDVector<Scalar, 4> {
        return {y(), x(), z(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yxwz() const -> SIMDVector<Scalar, 4> {
        return {y(), x(), w(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yzxw() const -> SIMDVector<Scalar, 4> {
        return {y(), z(), x(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::yzwx() const -> SIMDVector<Scalar, 4> {
        return {y(), z(), w(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::ywxz() const -> SIMDVector<Scalar, 4> {
        return {y(), w(), x(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::ywzx() const -> SIMDVector<Scalar, 4> {
        return {y(), w(), z(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zxyw() const -> SIMDVector<Scalar, 4> {
        return {z(), x(), y(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zxwy() const -> SIMDVector<Scalar, 4> {
        return {z(), x(), w(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zyxw() const -> SIMDVector<Scalar, 4> {
        return {z(), y(), x(), w()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zywx() const -> SIMDVector<Scalar, 4> {
        return {z(), y(), w(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zwxy() const -> SIMDVector<Scalar, 4> {
        return {z(), w(), x(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::zwyx() const -> SIMDVector<Scalar, 4> {
        return {z(), w(), y(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wxyz() const -> SIMDVector<Scalar, 4> {
        return {w(), x(), y(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wxzy() const -> SIMDVector<Scalar, 4> {
        return {w(), x(), z(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wyxz() const -> SIMDVector<Scalar, 4> {
        return {w(), y(), x(), z()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wyzx() const -> SIMDVector<Scalar, 4> {
        return {w(), y(), z(), x()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wzxy() const -> SIMDVector<Scalar, 4> {
        return {w(), z(), x(), y()};
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::wzyx() const -> SIMDVector<Scalar, 4> {
        return {w(), z(), y(), x()};
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::x(Scalar val) {
        if constexpr (Dims >= 1) m_data[0] = val;
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::y(Scalar val) {
        if constexpr (Dims >= 2) m_data[1] = val;
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::z(Scalar val) {
        if constexpr (Dims >= 3) m_data[2] = val;
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::w(Scalar val) {
        if constexpr (Dims >= 4) m_data[3] = val;
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xy(const SIMDVector<Scalar, 2> &v) {
        x(v.x());
        y(v.y());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yx(const SIMDVector<Scalar, 2> &v) {
        y(v.x());
        x(v.y());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xz(const SIMDVector<Scalar, 2> &v) {
        x(v.x());
        z(v.y());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zx(const SIMDVector<Scalar, 2> &v) {
        z(v.x());
        x(v.y());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yz(const SIMDVector<Scalar, 2> &v) {
        y(v.x());
        z(v.y());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zy(const SIMDVector<Scalar, 2> &v) {
        z(v.x());
        y(v.y());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xyz(const SIMDVector<Scalar, 3> &v) {
        x(v.x());
        y(v.y());
        z(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xzy(const SIMDVector<Scalar, 3> &v) {
        x(v.x());
        z(v.y());
        y(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yxz(const SIMDVector<Scalar, 3> &v) {
        y(v.x());
        x(v.y());
        z(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yzx(const SIMDVector<Scalar, 3> &v) {
        y(v.x());
        z(v.y());
        x(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zxy(const SIMDVector<Scalar, 3> &v) {
        z(v.x());
        x(v.y());
        y(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zyx(const SIMDVector<Scalar, 3> &v) {
        z(v.x());
        y(v.y());
        x(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xyw(const SIMDVector<Scalar, 3> &v) {
        x(v.x());
        y(v.y());
        w(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xwy(const SIMDVector<Scalar, 3> &v) {
        x(v.x());
        w(v.y());
        y(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yxw(const SIMDVector<Scalar, 3> &v) {
        y(v.x());
        x(v.y());
        w(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::ywx(const SIMDVector<Scalar, 3> &v) {
        y(v.x());
        w(v.y());
        x(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wxy(const SIMDVector<Scalar, 3> &v) {
        w(v.x());
        x(v.y());
        y(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wyx(const SIMDVector<Scalar, 3> &v) {
        w(v.x());
        y(v.y());
        x(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xzw(const SIMDVector<Scalar, 3> &v) {
        x(v.x());
        z(v.y());
        w(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xwz(const SIMDVector<Scalar, 3> &v) {
        x(v.x());
        w(v.y());
        z(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zxw(const SIMDVector<Scalar, 3> &v) {
        z(v.x());
        x(v.y());
        w(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zwx(const SIMDVector<Scalar, 3> &v) {
        z(v.x());
        w(v.y());
        x(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wxz(const SIMDVector<Scalar, 3> &v) {
        w(v.x());
        x(v.y());
        z(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wzx(const SIMDVector<Scalar, 3> &v) {
        w(v.x());
        z(v.y());
        x(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yzw(const SIMDVector<Scalar, 3> &v) {
        y(v.x());
        z(v.y());
        w(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::ywz(const SIMDVector<Scalar, 3> &v) {
        y(v.x());
        w(v.y());
        z(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zyw(const SIMDVector<Scalar, 3> &v) {
        z(v.x());
        y(v.y());
        w(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zwy(const SIMDVector<Scalar, 3> &v) {
        z(v.x());
        w(v.y());
        y(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wyz(const SIMDVector<Scalar, 3> &v) {
        w(v.x());
        y(v.y());
        z(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wzy(const SIMDVector<Scalar, 3> &v) {
        w(v.x());
        z(v.y());
        y(v.z());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xyzw(const SIMDVector<Scalar, 4> &v) {
        x(v.x());
        y(v.y());
        z(v.z());
        w(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xywz(const SIMDVector<Scalar, 4> &v) {
        x(v.x());
        y(v.y());
        w(v.z());
        z(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xzyw(const SIMDVector<Scalar, 4> &v) {
        x(v.x());
        z(v.y());
        y(v.z());
        w(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xzwy(const SIMDVector<Scalar, 4> &v) {
        x(v.x());
        z(v.y());
        w(v.z());
        y(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xwyz(const SIMDVector<Scalar, 4> &v) {
        x(v.x());
        w(v.y());
        y(v.z());
        z(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::xwzy(const SIMDVector<Scalar, 4> &v) {
        x(v.x());
        w(v.y());
        z(v.z());
        y(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yxzw(const SIMDVector<Scalar, 4> &v) {
        y(v.x());
        x(v.y());
        z(v.z());
        w(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yxwz(const SIMDVector<Scalar, 4> &v) {
        y(v.x());
        x(v.y());
        w(v.z());
        z(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yzxw(const SIMDVector<Scalar, 4> &v) {
        y(v.x());
        z(v.y());
        x(v.z());
        w(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::yzwx(const SIMDVector<Scalar, 4> &v) {
        y(v.x());
        z(v.y());
        w(v.z());
        x(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::ywxz(const SIMDVector<Scalar, 4> &v) {
        y(v.x());
        w(v.y());
        x(v.z());
        z(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::ywzx(const SIMDVector<Scalar, 4> &v) {
        y(v.x());
        w(v.y());
        z(v.z());
        x(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zxyw(const SIMDVector<Scalar, 4> &v) {
        z(v.x());
        x(v.y());
        y(v.z());
        w(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zxwy(const SIMDVector<Scalar, 4> &v) {
        z(v.x());
        x(v.y());
        w(v.z());
        y(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zyxw(const SIMDVector<Scalar, 4> &v) {
        z(v.x());
        y(v.y());
        x(v.z());
        w(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zywx(const SIMDVector<Scalar, 4> &v) {
        z(v.x());
        y(v.y());
        w(v.z());
        x(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zwxy(const SIMDVector<Scalar, 4> &v) {
        z(v.x());
        w(v.y());
        x(v.z());
        y(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::zwyx(const SIMDVector<Scalar, 4> &v) {
        z(v.x());
        w(v.y());
        y(v.z());
        x(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wxyz(const SIMDVector<Scalar, 4> &v) {
        w(v.x());
        x(v.y());
        y(v.z());
        z(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wxzy(const SIMDVector<Scalar, 4> &v) {
        w(v.x());
        x(v.y());
        z(v.z());
        y(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wyxz(const SIMDVector<Scalar, 4> &v) {
        w(v.x());
        y(v.y());
        x(v.z());
        z(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wyzx(const SIMDVector<Scalar, 4> &v) {
        w(v.x());
        y(v.y());
        z(v.z());
        x(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wzxy(const SIMDVector<Scalar, 4> &v) {
        w(v.x());
        z(v.y());
        x(v.z());
        y(v.w());
    }

    template<typename Scalar, int64_t Dims>
    void SIMDVector<Scalar, Dims>::wzyx(const SIMDVector<Scalar, 4> &v) {
        w(v.x());
        z(v.y());
        y(v.z());
        x(v.w());
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::data() const -> const StorageType & {
        return m_data;
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::data() -> StorageType & {
        return m_data;
    }

    template<typename Scalar, int64_t Dims>
    auto SIMDVector<Scalar, Dims>::str(const std::string &formatString) const -> std::string {
        std::string res = "(";
        for (size_t i = 0; i < Dims; ++i) {
            res += fmt::format(formatString, m_data[i]);
            if (i < Dims - 1) res += ", ";
        }
        return res + ")";
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE Scalar dist2(const SIMDVector<Scalar, Dims> &lhs,
                                        const SIMDVector<Scalar, Dims> &rhs) {
        return (lhs - rhs).mag2();
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE Scalar dist(const SIMDVector<Scalar, Dims> &lhs,
                                       const SIMDVector<Scalar, Dims> &rhs) {
        return (lhs - rhs).mag();
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> sin(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return SIMDVector<Scalar, Dims>(Vc::sin(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> cos(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return SIMDVector<Scalar, Dims>(Vc::cos(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> tan(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return sin(vec) / cos(vec);
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> asin(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return SIMDVector<Scalar, Dims>(Vc::asin(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> acos(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        // Vc doesn't have acos
        return Scalar(HALFPI) - asin(vec);
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> atan(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return SIMDVector<Scalar, Dims>(Vc::atan(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> atan2(const SIMDVector<Scalar, Dims> &lhs,
                                                          const SIMDVector<Scalar, Dims> &rhs) {
        using Type = SIMDVector<Scalar, Dims>;
        return SIMDVector<Scalar, Dims>(Vc::atan2(lhs.data(), rhs.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> sinh(const SIMDVector<Scalar, Dims> &vec) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) {
            res[i] = ::librapid::sinh(static_cast<Scalar>(vec[i]));
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> cosh(const SIMDVector<Scalar, Dims> &vec) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) {
            res[i] = ::librapid::cosh(static_cast<Scalar>(vec[i]));
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> tanh(const SIMDVector<Scalar, Dims> &vec) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) {
            res[i] = ::librapid::tanh(static_cast<Scalar>(vec[i]));
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> asinh(const SIMDVector<Scalar, Dims> &vec) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) {
            res[i] = ::librapid::asinh(static_cast<Scalar>(vec[i]));
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> acosh(const SIMDVector<Scalar, Dims> &vec) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) {
            res[i] = ::librapid::acosh(static_cast<Scalar>(vec[i]));
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> atanh(const SIMDVector<Scalar, Dims> &vec) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) {
            res[i] = ::librapid::atanh(static_cast<Scalar>(vec[i]));
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> exp(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return Type(Vc::exp(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> log(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return Type(Vc::log(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> log10(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return Type(Vc::log10(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> log2(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return Type(Vc::log2(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> sqrt(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return Type(Vc::sqrt(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> pow(const SIMDVector<Scalar, Dims> &vec,
                                                        const SIMDVector<Scalar, Dims> &exp) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) {
            res[i] = ::librapid::pow(static_cast<Scalar>(vec[i]), static_cast<Scalar>(exp[i]));
        }
        return res;
    }

    template<typename Scalar, int64_t Dims, typename T>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> pow(const SIMDVector<Scalar, Dims> &vec,
                                                        T exp) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) {
            res[i] =
              static_cast<Scalar>(pow(static_cast<Scalar>(vec[i]), static_cast<Scalar>(exp)));
        }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> pow(Scalar vec,
                                                        const SIMDVector<Scalar, Dims> &exp) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) { res[i] = ::librapid::pow(vec, exp[i]); }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> cbrt(const SIMDVector<Scalar, Dims> &vec) {
        SIMDVector<Scalar, Dims> res;
        for (size_t i = 0; i < Dims; ++i) { res[i] = ::librapid::cbrt(vec[i]); }
        return res;
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> abs(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return Type(Vc::abs(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> floor(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return Type(Vc::floor(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_ALWAYS_INLINE SIMDVector<Scalar, Dims> ceil(const SIMDVector<Scalar, Dims> &vec) {
        using Type = SIMDVector<Scalar, Dims>;
        return Type(Vc::ceil(vec.data()));
    }

    template<typename Scalar, int64_t Dims>
    LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE bool isClose(const SIMDVector<Scalar, Dims> &a,
                                                           const SIMDVector<Scalar, Dims> &b,
                                                           double tolerance = -1) {
        if (tolerance < 0) {
            if constexpr (std::is_same_v<Scalar, double>) {
                tolerance = 1e-12;
            } else if constexpr (std::is_same_v<Scalar, float>) {
                tolerance = 1e-6f;
            } else if constexpr (std::is_floating_point_v<Scalar>) {
                tolerance = 1e-4;
            } else {
                tolerance = 0;
            }
        };

        return (a - b).mag2() <= tolerance;
    }

    template<typename Scalar, int64_t Dims>
    std::ostream &operator<<(std::ostream &os, const SIMDVector<Scalar, Dims> &vec) {
        os << vec.str();
        return os;
    }
} // namespace librapid

#ifdef FMT_API
template<typename Scalar, int64_t D>
struct fmt::formatter<librapid::SIMDVector<Scalar, D>> {
    template<typename ParseContext>
    constexpr auto parse(ParseContext &ctx) {
        return ctx.begin();
    }

    template<typename FormatContext>
    auto format(const librapid::SIMDVector<Scalar, D> &arr, FormatContext &ctx) {
        return fmt::format_to(ctx.out(), arr.str());
    }
};
#endif // FMT_API

#endif // LIBRAPID_MATH_VECTOR_HPP