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
24 changes: 14 additions & 10 deletions dwave/optimization/include/dwave-optimization/iterators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
#include <memory>
#include <span>

#include "dwave-optimization/common.hpp"
#include "dwave-optimization/common.hpp" // for ssize_t
#include "dwave-optimization/typing.hpp"

namespace dwave::optimization {
Expand All @@ -38,11 +38,13 @@ concept shape_like = std::ranges::sized_range<T> and std::integral<std::ranges::
/// `To` specifies the `value_type` of the iterator.
/// `From` specifies the type of the underlying buffer. `void` means that the type
/// is specified at run-time.
template <DType To, OptionalDType From = To>
requires requires {
template <typename To, typename From = To>
requires(
DType<std::remove_const_t<To>> and // const is OK
OptionalDType<std::remove_const_t<From>> and // const is OK
// Cannot create an output iterator when From != To
requires std::is_const<To>::value or std::is_same<To, From>::value;
} class BufferIterator {
(std::is_const<To>::value or std::same_as<To, From>)
) class BufferIterator {
public:
// Iterator public types
using difference_type = std::ptrdiff_t;
Expand Down Expand Up @@ -83,7 +85,7 @@ requires requires {
/// Note that the pointer to shape/strides is not owning! The lifespan of those
/// pointers must exceed that of the iterator.
BufferIterator(From* ptr, ssize_t ndim, const ssize_t* shape, const ssize_t* strides) noexcept
requires(DType<From>) :
requires(DType<std::remove_const_t<From>>) :
ptr_(ptr),
format_(format_of<From>()),
ndim_(ndim),
Expand All @@ -101,9 +103,10 @@ requires requires {
}
}
}
template <DType T>
template <typename T>
requires(DType<std::remove_const_t<T>>)
BufferIterator(T* ptr, ssize_t ndim, const ssize_t* shape, const ssize_t* strides) noexcept
requires(not DType<From>) :
requires(not DType<std::remove_const_t<From>>) :
ptr_(ptr),
format_(format_of<T>()),
ndim_(ndim),
Expand All @@ -124,7 +127,8 @@ requires requires {

/// Convenience constructor when using shape/strides specified as spans (or things that can
/// be read using a span).
template <OptionalDType T>
template <typename T>
requires(OptionalDType<std::remove_const_t<T>>)
BufferIterator(T* ptr, std::span<const ssize_t> shape, std::span<const ssize_t> strides) :
BufferIterator(ptr, shape.size(), shape.data(), strides.data()) {
assert(std::ranges::size(shape) == std::ranges::size(strides));
Expand Down Expand Up @@ -434,7 +438,7 @@ requires requires {

/// Return the current value at the pointer as a copy.
value_type value_(From* ptr) const noexcept {
if constexpr (DType<From>) {
if constexpr (DType<std::remove_const_t<From>>) {
// In this case we know at compile-time what type we're converting from
return *ptr;
} else {
Expand Down
68 changes: 45 additions & 23 deletions dwave/optimization/include/dwave-optimization/typing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,50 @@

#include <concepts>
#include <cstdint>
#include <type_traits>

#include "dwave-optimization/type_list.hpp"

namespace dwave::optimization {

// We want to support an enumerated list of data types, corresponding to a
// subset supported by NumPy.
// See https://numpy.org/doc/stable/reference/arrays.dtypes.html for more
// information.

/// Supported dtypes as a type_list
using DTypes = type_list<
float, // np.float32
double, // np.float64
bool, // np.bool_
std::int8_t, // np.int8
std::int16_t, // np.int16
std::int32_t, // np.int32
std::int64_t>; // np.int64

/// The `DType<T>` concept is satisfied if and only if `T` is one of the
/// supported data types. This list is a subset of the `dtype`s supported by
/// NumPy and is designed for consistency/compatibility with NumPy.
/// See https://numpy.org/doc/stable/reference/arrays.dtypes.html for more
/// information.
/// supported dtypes.
template <typename T>
concept DType = std::same_as<const T, const float> || // np.float32
std::same_as<const T, const double> || // np.float64
std::same_as<const T, const bool> || // np.bool
std::same_as<const T, const std::int8_t> || // np.int8
std::same_as<const T, const std::int16_t> || // np.int16
std::same_as<const T, const std::int32_t> || // np.int32
std::same_as<const T, const std::int64_t>; // np.int64
concept DType = std::same_as<T, float> or // np.float32
std::same_as<T, double> or // np.float64
std::same_as<T, bool> or // np.bool_
std::same_as<T, std::int8_t> or // np.int8
std::same_as<T, std::int16_t> or // np.int16
std::same_as<T, std::int32_t> or // np.int32
std::same_as<T, std::int64_t>; // np.int64
// We could have instead done DTypes::contains<T>() but this way the compiler
// error message is a lot clearer

/// The `OptionalDType<T>` concept is satisfied if and only if `T` is `void`
/// or satisfies `DType<T>`.
/// `void` is used to signal that the dtype is unknown at compile-time.
template <typename T>
concept OptionalDType = DType<T> || std::same_as<const T, const void>;
concept OptionalDType = DType<T> or std::same_as<T, void>;

/// The `DTypeLike<T>` concept is satisfied for supported types, as well as
/// const/volatile and references.
template <typename T>
concept DTypeLike = DType<std::remove_cvref_t<T>>;

// Dev note: We'd like to work with `DType` only, but in order to work with the
// buffer protocol (https://docs.python.org/3/c-api/buffer.html) we need to
Expand All @@ -56,21 +78,21 @@ enum class FormatCharacter : char {
signedchar_ = 'b',
signedshort_ = 'h',
signedlong_ = 'l',
signedlonglong_ = 'Q',
signedlonglong_ = 'q',

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.

There isn't really a good test I can do for this because we don't currently expose this stuff to the Python level.

};

/// Get the format character associated with a supported DType.
template <DType T>
template <DTypeLike T>
constexpr FormatCharacter format_of() {
if constexpr (std::same_as<const T, const float>) return FormatCharacter::float_;
if constexpr (std::same_as<const T, const double>) return FormatCharacter::double_;
if constexpr (std::same_as<const T, const bool>) return FormatCharacter::bool_;
if constexpr (std::same_as<const T, const int>) return FormatCharacter::int_;
if constexpr (std::same_as<const T, const signed char>) return FormatCharacter::signedchar_;
if constexpr (std::same_as<const T, const signed short>) return FormatCharacter::signedshort_;
if constexpr (std::same_as<const T, const signed long>) return FormatCharacter::signedlong_;
if constexpr (std::same_as<const T, const signed long long>)
return FormatCharacter::signedlonglong_;
using U = std::remove_cvref_t<T>;
if constexpr (std::same_as<U, float>) return FormatCharacter::float_;
if constexpr (std::same_as<U, double>) return FormatCharacter::double_;
if constexpr (std::same_as<U, bool>) return FormatCharacter::bool_;
if constexpr (std::same_as<U, int>) return FormatCharacter::int_;
if constexpr (std::same_as<U, signed char>) return FormatCharacter::signedchar_;
if constexpr (std::same_as<U, signed short>) return FormatCharacter::signedshort_;
if constexpr (std::same_as<U, signed long>) return FormatCharacter::signedlong_;
if constexpr (std::same_as<U, signed long long>) return FormatCharacter::signedlonglong_;
}

} // namespace dwave::optimization
10 changes: 10 additions & 0 deletions releasenotes/notes/dtype-fixes-a43f6a3f1872beb2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
features:
- Add ``DTypes`` ``type_list``.
- Add ``DTypeLike`` concept for references and ``const``-qualified types.
upgrade:
- The ``DType`` concept is no longer satisfied for ``const`` types.
fixes:
- |
Fix ``FormatCharacter::signedlonglong_`` to correctly use ``q`` rather than ``Q``.
See `#406 <https://github.com/dwavesystems/dwave-optimization/issues/406>`_.
1 change: 1 addition & 0 deletions tests/cpp/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ tests_all = executable(
'test_iterators.cpp',
'test_simplex.cpp',
'test_type_list.cpp',
'test_typing.cpp',
],
dependencies: [
catch2,
Expand Down
33 changes: 33 additions & 0 deletions tests/cpp/test_typing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2026 D-Wave
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <utility>

#include <catch2/catch_test_macros.hpp>

#include "dwave-optimization/typing.hpp"

namespace dwave::optimization {

void forwarding_reference_func(DTypeLike auto&& lhs) { }

TEST_CASE("DTypeLike") {
int a = 1;
forwarding_reference_func(a);
forwarding_reference_func(static_cast<const int>(a));
forwarding_reference_func(static_cast<const int&>(a));
forwarding_reference_func(std::move(a));
}

} // namespace dwave::optimization