Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions dwave/optimization/include/dwave-optimization/interval.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,10 @@ struct interval {
/// Copy constructor.
interval(const interval&) = default;

/// Create an ``interval<double>`` from another interval.
template <DType U>
requires(std::same_as<T, double>)
interval(const interval<U>& other) noexcept : interval(other.infimum, other.supremum) {}
// dev note: we could expand this. E.g., we could support all promotions
// allowed by NumPy promotion.
/// Create an one interval from another, allowing for safe type promotions
template <can_cast<T> U>
constexpr interval(const interval<U>& other) noexcept :

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No release note needed for this change because the interval class is not yet released.

Also there are other interval methods that might benefit from type promotion, but I've decided to leave those alone for now.

interval(other.infimum, other.supremum) {}

/// Move constructor.
interval(interval&&) = default;
Expand Down
46 changes: 46 additions & 0 deletions dwave/optimization/include/dwave-optimization/typing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,50 @@ constexpr FormatCharacter format_of() {
if constexpr (std::same_as<U, signed long long>) return FormatCharacter::signedlonglong_;
}

/// NumPy's type promotion doesn't match the behavior of C++'s numeric promotions
/// This class is meant to mimic the behavior of the `numpy.promote_types()`
/// function.
template <DTypeLike T, DTypeLike U>
class promote_types {
private:
consteval static auto promote_types_() {
using T_ = std::remove_cvref_t<T>;
using U_ = std::remove_cvref_t<U>;

if constexpr (std::same_as<T_, bool>) {
// bool doesn't promote any other types
return U_();
} else if constexpr (std::same_as<U_, bool>) {
// bool doesn't promote any other types
return T_();
} else if constexpr (std::integral<T_> and std::integral<U_>) {
// If both are integers, promote to the larger type
return std::conditional_t<sizeof(T_) >= sizeof(U_), T_, U_>();
} else if constexpr (std::floating_point<T_> and std::floating_point<U_>) {
// If both are floating point, promote to the larger type
return std::conditional_t<sizeof(T_) >= sizeof(U_), T_, U_>();
} else if constexpr (std::integral<T_>) {
// T_ is an integer and U_ is a floating point, so make sure we return
// a large enough type
return std::conditional_t<sizeof(T_) <= 2, U_, double>();
} else {
// T_ is a floating point and U_ is an integer, so make sure we return
// a large enough type
return std::conditional_t<sizeof(U_) <= 2, T_, double>();
}
}

public:
using type = decltype(promote_types_());
};

template <DTypeLike T, DTypeLike U>
using promote_types_t = typename promote_types<T, U>::type;

/// Test whether `From` can be safely cast to `To` according to NumPy's promotion
/// rules.
/// Note that `np.can_cast(int64, float64, "safe")` is `True` according to NumPy.
template <typename From, typename To>
concept can_cast = DType<From> and DType<To> and std::same_as<promote_types_t<From, To>, To>;

} // namespace dwave::optimization
5 changes: 5 additions & 0 deletions releasenotes/notes/promote-types-eb20e379783baaf6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features:
- |
Add ``promote_types`` and ``can_cast`` to ``dwave-optimization/typing.hpp``
for NumPy-style type promotions.
4 changes: 4 additions & 0 deletions tests/cpp/test_interval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,8 @@ TEMPLATE_LIST_TEST_CASE("interval", "", DTypes) {
}
}

TEST_CASE("interval") {
STATIC_REQUIRE(interval<double>(interval<float>(0, 5)) == interval<double>(0, 5));
}

} // namespace dwave::optimization
148 changes: 147 additions & 1 deletion tests/cpp/test_typing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

namespace dwave::optimization {

void forwarding_reference_func(DTypeLike auto&&) { }
void forwarding_reference_func(DTypeLike auto&&) {}

TEST_CASE("DTypeLike") {
int a = 1;
Expand All @@ -30,4 +30,150 @@ TEST_CASE("DTypeLike") {
forwarding_reference_func(std::move(a));
}

TEST_CASE("can_cast") {
// import itertools

// import numpy as np

// dtypes = {
// np.float32: "float",
// np.float64: "double",
// np.bool: "bool",
// np.int8: "std::int8_t",
// np.int16: "std::int16_t",
// np.int32: "std::int32_t",
// np.int64: "std::int64_t",
// }

// for dt0, dt1 in itertools.product(dtypes, repeat=2):
// From = dtypes[dt0]
// To = dtypes[dt1]
// if np.can_cast(dt0, dt1):
// print(f"STATIC_REQUIRE(can_cast<{From}, {To}>);")
// else:
// print(f"STATIC_REQUIRE(not can_cast<{From}, {To}>);")

STATIC_REQUIRE(can_cast<float, float>);
STATIC_REQUIRE(can_cast<float, double>);
STATIC_REQUIRE(not can_cast<float, bool>);
STATIC_REQUIRE(not can_cast<float, std::int8_t>);
STATIC_REQUIRE(not can_cast<float, std::int16_t>);
STATIC_REQUIRE(not can_cast<float, std::int32_t>);
STATIC_REQUIRE(not can_cast<float, std::int64_t>);
STATIC_REQUIRE(not can_cast<double, float>);
STATIC_REQUIRE(can_cast<double, double>);
STATIC_REQUIRE(not can_cast<double, bool>);
STATIC_REQUIRE(not can_cast<double, std::int8_t>);
STATIC_REQUIRE(not can_cast<double, std::int16_t>);
STATIC_REQUIRE(not can_cast<double, std::int32_t>);
STATIC_REQUIRE(not can_cast<double, std::int64_t>);
STATIC_REQUIRE(can_cast<bool, float>);
STATIC_REQUIRE(can_cast<bool, double>);
STATIC_REQUIRE(can_cast<bool, bool>);
STATIC_REQUIRE(can_cast<bool, std::int8_t>);
STATIC_REQUIRE(can_cast<bool, std::int16_t>);
STATIC_REQUIRE(can_cast<bool, std::int32_t>);
STATIC_REQUIRE(can_cast<bool, std::int64_t>);
STATIC_REQUIRE(can_cast<std::int8_t, float>);
STATIC_REQUIRE(can_cast<std::int8_t, double>);
STATIC_REQUIRE(not can_cast<std::int8_t, bool>);
STATIC_REQUIRE(can_cast<std::int8_t, std::int8_t>);
STATIC_REQUIRE(can_cast<std::int8_t, std::int16_t>);
STATIC_REQUIRE(can_cast<std::int8_t, std::int32_t>);
STATIC_REQUIRE(can_cast<std::int8_t, std::int64_t>);
STATIC_REQUIRE(can_cast<std::int16_t, float>);
STATIC_REQUIRE(can_cast<std::int16_t, double>);
STATIC_REQUIRE(not can_cast<std::int16_t, bool>);
STATIC_REQUIRE(not can_cast<std::int16_t, std::int8_t>);
STATIC_REQUIRE(can_cast<std::int16_t, std::int16_t>);
STATIC_REQUIRE(can_cast<std::int16_t, std::int32_t>);
STATIC_REQUIRE(can_cast<std::int16_t, std::int64_t>);
STATIC_REQUIRE(not can_cast<std::int32_t, float>);
STATIC_REQUIRE(can_cast<std::int32_t, double>);
STATIC_REQUIRE(not can_cast<std::int32_t, bool>);
STATIC_REQUIRE(not can_cast<std::int32_t, std::int8_t>);
STATIC_REQUIRE(not can_cast<std::int32_t, std::int16_t>);
STATIC_REQUIRE(can_cast<std::int32_t, std::int32_t>);
STATIC_REQUIRE(can_cast<std::int32_t, std::int64_t>);
STATIC_REQUIRE(not can_cast<std::int64_t, float>);
STATIC_REQUIRE(can_cast<std::int64_t, double>);
STATIC_REQUIRE(not can_cast<std::int64_t, bool>);
STATIC_REQUIRE(not can_cast<std::int64_t, std::int8_t>);
STATIC_REQUIRE(not can_cast<std::int64_t, std::int16_t>);
STATIC_REQUIRE(not can_cast<std::int64_t, std::int32_t>);
STATIC_REQUIRE(can_cast<std::int64_t, std::int64_t>);
}

TEST_CASE("promote_types") {
// import itertools

// import numpy as np

// dtypes = {
// np.float32: "float",
// np.float64: "double",
// np.bool: "bool",
// np.int8: "std::int8_t",
// np.int16: "std::int16_t",
// np.int32: "std::int32_t",
// np.int64: "std::int64_t",
// }

// for dt0, dt1 in itertools.product(dtypes, repeat=2):
// lhs = dtypes[dt0]
// rhs = dtypes[dt1]
// result = dtypes[np.promote_types(dt0, dt1).type]
// print(f"STATIC_REQUIRE(std::same_as<promote_types<{lhs}, {rhs}>::type, {result}>);")

STATIC_REQUIRE(std::same_as<promote_types<float, float>::type, float>);
STATIC_REQUIRE(std::same_as<promote_types<float, double>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<float, bool>::type, float>);
STATIC_REQUIRE(std::same_as<promote_types<float, std::int8_t>::type, float>);
STATIC_REQUIRE(std::same_as<promote_types<float, std::int16_t>::type, float>);
STATIC_REQUIRE(std::same_as<promote_types<float, std::int32_t>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<float, std::int64_t>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<double, float>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<double, double>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<double, bool>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<double, std::int8_t>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<double, std::int16_t>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<double, std::int32_t>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<double, std::int64_t>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<bool, float>::type, float>);
STATIC_REQUIRE(std::same_as<promote_types<bool, double>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<bool, bool>::type, bool>);
STATIC_REQUIRE(std::same_as<promote_types<bool, std::int8_t>::type, std::int8_t>);
STATIC_REQUIRE(std::same_as<promote_types<bool, std::int16_t>::type, std::int16_t>);
STATIC_REQUIRE(std::same_as<promote_types<bool, std::int32_t>::type, std::int32_t>);
STATIC_REQUIRE(std::same_as<promote_types<bool, std::int64_t>::type, std::int64_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int8_t, float>::type, float>);
STATIC_REQUIRE(std::same_as<promote_types<std::int8_t, double>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<std::int8_t, bool>::type, std::int8_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int8_t, std::int8_t>::type, std::int8_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int8_t, std::int16_t>::type, std::int16_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int8_t, std::int32_t>::type, std::int32_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int8_t, std::int64_t>::type, std::int64_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int16_t, float>::type, float>);
STATIC_REQUIRE(std::same_as<promote_types<std::int16_t, double>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<std::int16_t, bool>::type, std::int16_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int16_t, std::int8_t>::type, std::int16_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int16_t, std::int16_t>::type, std::int16_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int16_t, std::int32_t>::type, std::int32_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int16_t, std::int64_t>::type, std::int64_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int32_t, float>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<std::int32_t, double>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<std::int32_t, bool>::type, std::int32_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int32_t, std::int8_t>::type, std::int32_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int32_t, std::int16_t>::type, std::int32_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int32_t, std::int32_t>::type, std::int32_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int32_t, std::int64_t>::type, std::int64_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int64_t, float>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<std::int64_t, double>::type, double>);
STATIC_REQUIRE(std::same_as<promote_types<std::int64_t, bool>::type, std::int64_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int64_t, std::int8_t>::type, std::int64_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int64_t, std::int16_t>::type, std::int64_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int64_t, std::int32_t>::type, std::int64_t>);
STATIC_REQUIRE(std::same_as<promote_types<std::int64_t, std::int64_t>::type, std::int64_t>);
}

} // namespace dwave::optimization