Skip to content

Commit

Permalink
feat: Initial Vector and Tensor representation concepts implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
mpusz committed Sep 22, 2023
1 parent dde5bca commit 31f7a43
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 116 deletions.
3 changes: 2 additions & 1 deletion src/core/include/mp-units/bits/reference_concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ concept ReferenceOf =
(QuantitySpec<std::remove_const_t<decltype(V)>> && implicitly_convertible(get_quantity_spec(T{}), V) &&
!detail::DerivedFromQuantityKindSpecOf<get_quantity_spec(T{}), V> &&
(detail::QuantityKindSpec<std::remove_const_t<decltype(get_quantity_spec(T{}))>> ||
!detail::DerivedFromQuantityKindSpecOf<V, get_quantity_spec(T{})>)));
!detail::DerivedFromQuantityKindSpecOf<V, get_quantity_spec(T{})>)) ||
(std::same_as<std::remove_const_t<decltype(V)>, quantity_character> && (get_quantity_spec(T{}).character == V)));

} // namespace mp_units
91 changes: 72 additions & 19 deletions src/core/include/mp-units/bits/representation_concepts.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,35 +52,88 @@ enum class quantity_character { scalar, vector, tensor };

namespace detail {

template<typename T, typename U>
concept CommonTypeWith =
std::same_as<std::common_type_t<T, U>, std::common_type_t<U, T>> &&
std::constructible_from<std::common_type_t<T, U>, T> && std::constructible_from<std::common_type_t<T, U>, U>;
template<typename T>
concept Scalar = is_scalar<T>;

template<typename T>
concept Vector = is_vector<T>;

template<typename T>
concept Tensor = is_tensor<T>;

template<typename T, typename U = T>
concept ScalableNumber =
std::regular_invocable<std::multiplies<>, T, U> && std::regular_invocable<std::divides<>, T, U>;
template<typename T, quantity_character Ch>
concept IsOfCharacter =
(Ch == quantity_character::scalar && is_scalar<T>) || (Ch == quantity_character::vector && is_vector<T>) ||
(Ch == quantity_character::tensor && is_tensor<T>);

template<typename T>
[[nodiscard]] consteval quantity_character get_character(T)
{
if constexpr (Tensor<T>)
return quantity_character::tensor;
else if constexpr (Vector<T>)
return quantity_character::vector;
else if constexpr (Scalar<T>)
return quantity_character::scalar;
}

// clang-format off
template<typename T>
concept ScalarRepresentation = std::regular<T> && Scalar<T> &&
requires(T a, T b, std::intmax_t i, long double f) {
{ a * i } -> Scalar;
{ i * a } -> Scalar;
{ a / i } -> Scalar;
{ a * f } -> Scalar; // TODO How this affects freestanding?
{ f * a } -> Scalar;
{ a / f } -> Scalar;
{ a + b } -> Scalar;
{ a - b } -> Scalar;
{ a * b } -> Scalar;
{ a / b } -> Scalar;
};

template<typename T>
concept CastableNumber = CommonTypeWith<T, std::intmax_t> && ScalableNumber<std::common_type_t<T, std::intmax_t>>;
concept VectorRepresentation = std::regular<T> && Vector<T> &&
requires(T a, T b, std::intmax_t i, long double f) {
{ a * i } -> Vector;
{ i * a } -> Vector;
{ a / i } -> Vector;
{ a * f } -> Vector;
{ f * a } -> Vector;
{ a / f } -> Vector;
{ a + b } -> Vector;
{ a - b } -> Vector;
{ dot_product(a, b) } -> Scalar;
{ cross_product(a, b) } -> Vector;
{ tensor_product(a, b) } -> Tensor;
{ norm(a) } -> Scalar;
};

// TODO Fix it according to sudo_cast implementation
template<typename T>
concept Scalable =
CastableNumber<T> ||
(requires { typename T::value_type; } && CastableNumber<typename T::value_type> &&
ScalableNumber<T, std::common_type_t<typename T::value_type, std::intmax_t>>) ||
(requires { typename T::element_type; } && CastableNumber<std::remove_reference_t<typename T::element_type>> &&
ScalableNumber<T, std::common_type_t<std::remove_reference_t<typename T::element_type>, std::intmax_t>>);
concept TensorRepresentation = std::regular<T> && Tensor<T> &&
requires(T a, T b, std::intmax_t i, long double f) {
{ a * i } -> Tensor;
{ i * a } -> Tensor;
{ a / i } -> Tensor;
{ a * f } -> Tensor;
{ f * a } -> Tensor;
{ a / f } -> Tensor;
{ a + b } -> Tensor;
{ a - b } -> Tensor;
{ tensor_product(a, b) } -> Tensor;
{ inner_product(a, b) } -> Tensor;
{ scalar_product(a, b) } -> Scalar;
};
// clang-format on

} // namespace detail

template<typename T>
concept Representation = (is_scalar<T> || is_vector<T> || is_tensor<T>)&&std::regular<T> && detail::Scalable<T>;
concept Representation =
detail::ScalarRepresentation<T> || detail::VectorRepresentation<T> || detail::TensorRepresentation<T>;

template<typename T, quantity_character Ch>
concept RepresentationOf = Representation<T> && ((Ch == quantity_character::scalar && is_scalar<T>) ||
(Ch == quantity_character::vector && is_vector<T>) ||
(Ch == quantity_character::tensor && is_tensor<T>));
concept RepresentationOf = detail::IsOfCharacter<T, Ch> && Representation<T>;

} // namespace mp_units
113 changes: 83 additions & 30 deletions src/core/include/mp-units/quantity.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,20 +65,6 @@ concept QuantityConvertibleTo =
// deduced thus the function is evaluated here and may emit truncating conversion or other warnings)
requires(QFrom q) { detail::sudo_cast<QTo>(q); };

template<quantity_character Ch, typename Func, typename T, typename U>
concept InvokeResultOf = std::regular_invocable<Func, T, U> && RepresentationOf<std::invoke_result_t<Func, T, U>, Ch>;

template<typename Func, typename Q1, typename Q2>
concept InvocableQuantities = Quantity<Q1> && Quantity<Q2> &&
InvokeResultOf<common_quantity_spec(Q1::quantity_spec, Q2::quantity_spec).character, Func,
typename Q1::rep, typename Q2::rep> &&
requires { common_reference(Q1::reference, Q2::reference); };

template<typename Func, Quantity Q1, Quantity Q2>
requires detail::InvocableQuantities<Func, Q1, Q2>
using common_quantity_for = quantity<common_reference(Q1::reference, Q2::reference),
std::invoke_result_t<Func, typename Q1::rep, typename Q2::rep>>;

} // namespace detail

/**
Expand Down Expand Up @@ -341,46 +327,85 @@ explicit quantity(Q) -> quantity<quantity_like_traits<Q>::reference, typename qu

// binary operators on quantities
template<auto R1, typename Rep1, auto R2, typename Rep2>
requires detail::InvocableQuantities<std::plus<>, quantity<R1, Rep1>, quantity<R2, Rep2>>
requires requires(Rep1 a, Rep2 b) {
common_reference(R1, R2);
{
a + b
} -> detail::IsOfCharacter<get_quantity_spec(R1).character>
}
[[nodiscard]] constexpr Quantity auto operator+(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs)
{
using ret = detail::common_quantity_for<std::plus<>, quantity<R1, Rep1>, quantity<R2, Rep2>>;
using ret = quantity<common_reference(R1, R2), decltype(lhs.number() + rhs.number())>;
return make_quantity<ret::reference>(ret(lhs).number() + ret(rhs).number());
}

template<auto R1, typename Rep1, auto R2, typename Rep2>
requires detail::InvocableQuantities<std::minus<>, quantity<R1, Rep1>, quantity<R2, Rep2>>
requires requires(Rep1 a, Rep2 b) {
common_reference(R1, R2);
{
a - b
} -> detail::IsOfCharacter<get_quantity_spec(R1).character>
}
[[nodiscard]] constexpr Quantity auto operator-(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs)
{
using ret = detail::common_quantity_for<std::minus<>, quantity<R1, Rep1>, quantity<R2, Rep2>>;
using ret = quantity<common_reference(R1, R2), decltype(lhs.number() - rhs.number())>;
return make_quantity<ret::reference>(ret(lhs).number() - ret(rhs).number());
}

template<ReferenceOf<quantity_character::scalar> auto R1, typename Rep1,
ReferenceOf<quantity_character::scalar> auto R2, typename Rep2>
requires(!treat_as_floating_point<Rep1>) && (!treat_as_floating_point<Rep2>) && requires(Rep1 a, Rep2 b) {
common_reference(R1, R2);
{
a % b
} -> detail::IsOfCharacter<quantity_character::scalar>
}
[[nodiscard]] constexpr Quantity auto operator%(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs)
{
gsl_ExpectsAudit(rhs.number() != quantity_values<Rep1>::zero());
using ret = quantity<common_reference(R1, R2), decltype(lhs.number() % rhs.number())>;
return make_quantity<ret::reference>(ret(lhs).number() % ret(rhs).number());
}

template<auto R1, typename Rep1, auto R2, typename Rep2>
requires detail::InvokeResultOf<(get_quantity_spec(R1) * get_quantity_spec(R2)).character, std::multiplies<>, Rep1,
Rep2>
requires(ReferenceOf<std::remove_const_t<decltype(R1)>, quantity_character::scalar> ||
ReferenceOf<std::remove_const_t<decltype(R2)>, quantity_character::scalar>) &&
requires(Rep1 a, Rep2 b) {
{
a* b
} -> detail::IsOfCharacter<(get_quantity_spec(R1) * get_quantity_spec(R2)).character>
}
[[nodiscard]] constexpr Quantity auto operator*(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs)
{
return make_quantity<R1 * R2>(lhs.number() * rhs.number());
}

template<auto R, typename Rep, typename Value>
requires(!Quantity<Value>) &&
detail::InvokeResultOf<get_quantity_spec(R).character, std::multiplies<>, Rep, const Value&>
[[nodiscard]] constexpr Quantity auto operator*(const quantity<R, Rep>& q, const Value& v)
(ReferenceOf<std::remove_const_t<decltype(R)>, quantity_character::scalar> || detail::Scalar<Value>) &&
requires(Rep a, Value b) {
{
a* b
} -> detail::IsOfCharacter<(get_quantity_spec(R) * detail::get_character(b)).character>
}

detail::InvokeResultOf<get_quantity_spec(R).character, std::multiplies<>, Rep,
const Value&> [[nodiscard]] constexpr Quantity auto
operator*(const quantity<R, Rep>& q, const Value& v)
{
return make_quantity<R>(q.number() * v);
}

template<typename Value, auto R, typename Rep>
requires(!Quantity<Value>) &&
(ReferenceOf<std::remove_const_t<decltype(R)>, quantity_character::scalar> || detail::Scalar<Value>) &&
detail::InvokeResultOf<get_quantity_spec(R).character, std::multiplies<>, const Value&, Rep>
[[nodiscard]] constexpr Quantity auto operator*(const Value& v, const quantity<R, Rep>& q)
{
return make_quantity<R>(v * q.number());
}

template<auto R1, typename Rep1, auto R2, typename Rep2>
template<auto R1, typename Rep1, ReferenceOf<quantity_character::scalar> auto R2, typename Rep2>
requires detail::InvokeResultOf<(get_quantity_spec(R1) / get_quantity_spec(R2)).character, std::divides<>, Rep1, Rep2>
[[nodiscard]] constexpr Quantity auto operator/(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs)
{
Expand All @@ -405,14 +430,41 @@ template<typename Value, auto R, typename Rep>
return make_quantity<::mp_units::one / R>(v / q.number());
}

template<auto R1, typename Rep1, auto R2, typename Rep2>
requires(!treat_as_floating_point<Rep1>) && (!treat_as_floating_point<Rep2>) &&
detail::InvocableQuantities<std::modulus<>, quantity<R1, Rep1>, quantity<R2, Rep2>>
[[nodiscard]] constexpr Quantity auto operator%(const quantity<R1, Rep1>& lhs, const quantity<R2, Rep2>& rhs)
template<ReferenceOf<quantity_character::vector> auto R1, typename Rep1,
ReferenceOf<quantity_character::vector> auto R2, typename Rep2>
requires requires(Rep1 v1, Rep2 v2) {
{
dot_product(v1, v2)
} -> detail::Vector;
}
[[nodiscard]] QuantityOf<dot_product(get_quantity_spec(R1), get_quantity_spec(R2))> auto dot_product(
const quantity<R1, Rep1>& q1, const quantity<R2, Rep2>& q2)
{
gsl_ExpectsAudit(rhs.number() != quantity_values<Rep1>::zero());
using ret = detail::common_quantity_for<std::modulus<>, quantity<R1, Rep1>, quantity<R2, Rep2>>;
return make_quantity<ret::reference>(ret(lhs).number() % ret(rhs).number());
return make_quantity<dot_product(R1, R2)>(dot_product(q1.number(), q2.number()));
}

template<ReferenceOf<quantity_character::vector> auto R1, typename Rep1,
ReferenceOf<quantity_character::vector> auto R2, typename Rep2>
requires requires(Rep1 v1, Rep2 v2) {
{
cross_product(v1, v2)
} -> detail::Scalar;
}
[[nodiscard]] QuantityOf<cross_product(get_quantity_spec(R1), get_quantity_spec(R2))> auto cross_product(
const quantity<R1, Rep1>& q1, const quantity<R2, Rep2>& q2)
{
return make_quantity<cross_product(R1, R2)>(cross_product(q1.number(), q2.number()));
}

template<ReferenceOf<quantity_character::vector> auto R, typename Rep>
requires requires(Rep v) {
{
norm(v)
} -> detail::Scalar;
}
[[nodiscard]] QuantityOf<norm(get_quantity_spec(R))> auto norm(const quantity<R, Rep>& q)
{
return make_quantity<norm(R)>(norm(q.number()));
}

template<auto R1, typename Rep1, auto R2, typename Rep2>
Expand Down Expand Up @@ -492,6 +544,7 @@ template<auto R, typename Rep, typename Value>
return q <=> make_quantity<::mp_units::one>(v);
}


// make_quantity
template<Reference auto R, typename Rep>
requires quantity<R, std::remove_cvref_t<Rep>>::_rep_safe_constructible_
Expand Down
35 changes: 31 additions & 4 deletions src/core/include/mp-units/quantity_spec.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,19 +275,20 @@ struct quantity_spec<Self, Eq, Args...> : detail::quantity_spec_interface<Self>
* errors. Having them of the same names improves user experience and somehow blurs those separate domains.
*
* @tparam Q quantity specification of a parent quantity
* @tparam Args optionally a value of a `quantity_character` in case the base quantity should not be scalar
* or `is_kind` in case the quantity starts a new hierarchy tree of a kind
* @tparam Args optionally `is_kind` in case the quantity starts a new hierarchy tree of a kind
*/
#ifdef __cpp_explicit_this_parameter
template<detail::NamedQuantitySpec auto QS, one_of<quantity_character, struct is_kind> auto... Args>
template<detail::NamedQuantitySpec auto QS, one_of<struct is_kind> auto... Args>
requires(... && !QuantitySpec<std::remove_const_t<decltype(Args)>>)
struct quantity_spec<QS, Args...> : std::remove_const_t<decltype(QS)> {
#else
// template<typename Self, detail::NamedQuantitySpec auto QS, one_of<struct is_kind> auto... Args>
template<typename Self, detail::NamedQuantitySpec auto QS, one_of<quantity_character, struct is_kind> auto... Args>
requires(... && !QuantitySpec<std::remove_const_t<decltype(Args)>>)
struct quantity_spec<Self, QS, Args...> : std::remove_const_t<decltype(QS)> {
#endif
static constexpr auto _parent_ = QS;
// static constexpr quantity_character character = QS.character;
static constexpr quantity_character character = detail::quantity_character_init<Args...>(QS.character);

#ifndef __cpp_explicit_this_parameter
Expand Down Expand Up @@ -488,6 +489,7 @@ template<QuantitySpec auto... From, QuantitySpec Q>
// Operators

[[nodiscard]] consteval QuantitySpec auto operator*(QuantitySpec auto lhs, QuantitySpec auto rhs)
requires(lhs.character == quantity_character::scalar || rhs.character == quantity_character::scalar)
{
return clone_kind_of<lhs, rhs>(
detail::expr_multiply<derived_quantity_spec, struct dimensionless, detail::type_list_of_quantity_spec_less>(
Expand All @@ -496,13 +498,15 @@ template<QuantitySpec auto... From, QuantitySpec Q>

template<QuantitySpec Lhs, QuantitySpec Rhs>
[[nodiscard]] consteval QuantitySpec auto operator/(Lhs lhs, Rhs rhs)
requires(rhs.character == quantity_character::scalar)
{
return clone_kind_of<lhs, rhs>(
detail::expr_divide<derived_quantity_spec, struct dimensionless, detail::type_list_of_quantity_spec_less>(
remove_kind(lhs), remove_kind(rhs)));
}

[[nodiscard]] consteval QuantitySpec auto operator/(int value, QuantitySpec auto q)
requires(q.character == quantity_character::scalar)
{
gsl_Expects(value == 1);
return clone_kind_of<q>(detail::expr_invert<derived_quantity_spec, struct dimensionless>(q));
Expand All @@ -516,6 +520,28 @@ template<QuantitySpec Lhs, QuantitySpec Rhs>
return is_same_v<Lhs, Rhs>;
}

[[nodiscard]] consteval QuantitySpec auto dot_product(QuantitySpec auto lhs, QuantitySpec auto rhs)
requires(lhs.character == quantity_character::vector && rhs.character == quantity_character::vector)
{
return clone_kind_of<lhs, rhs>(
detail::expr_multiply<derived_quantity_spec, struct dimensionless, detail::type_list_of_quantity_spec_less>(
remove_kind(lhs), remove_kind(rhs)));
}

[[nodiscard]] consteval QuantitySpec auto cross_product(QuantitySpec auto lhs, QuantitySpec auto rhs)
requires(lhs.character == quantity_character::vector && rhs.character == quantity_character::vector)
{
return clone_kind_of<lhs, rhs>(
detail::expr_multiply<derived_quantity_spec, struct dimensionless, detail::type_list_of_quantity_spec_less>(
remove_kind(lhs), remove_kind(rhs)));
}

[[nodiscard]] consteval QuantitySpec auto norm(QuantitySpec auto q)
requires(q.character == quantity_character::vector)
{
return q;
}

template<detail::QuantityKindSpec Lhs, detail::QuantityKindSpec Rhs>
[[nodiscard]] consteval bool operator==(Lhs, Rhs)
{
Expand Down Expand Up @@ -1475,7 +1501,8 @@ template<QuantitySpec Q>

template<QuantitySpec Q1, QuantitySpec Q2>
[[nodiscard]] consteval QuantitySpec auto common_quantity_spec(Q1 q1, Q2 q2)
requires(implicitly_convertible(get_kind(q1), get_kind(q2)) || implicitly_convertible(get_kind(q2), get_kind(q1)))
requires(Q1::character == Q2::character) &&
(implicitly_convertible(get_kind(q1), get_kind(q2)) || implicitly_convertible(get_kind(q2), get_kind(q1)))
{
using QQ1 = std::remove_const_t<decltype(remove_kind(q1))>;
using QQ2 = std::remove_const_t<decltype(remove_kind(q2))>;
Expand Down
Loading

0 comments on commit 31f7a43

Please sign in to comment.