Skip to content
Merged
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,40 @@ normal[0].hm()
[-0.02335746, 0.04858632, -0.03690759, -0.01546811, -0.02868433, 0.03641839]])
```

### A real dtype instead of `dtype=object`

Statically sized scalars back a NumPy dtype, so an array of them stores its data contiguously rather than as pointers to Python objects. NumPy picks it up on its own:

```python
a = np.array(hj.variables([1.0, 2.0, 3.0]))
a.dtype
>>> DD3ScalarDType
a.nbytes # 3 x 80 bytes, contiguous
>>> 240
```

Arithmetic and the mathematical functions then run as compiled loops instead of a Python object loop. Measured over 10 000 elements of `DD3Scalar`:

| | `dtype=object` | dtype | |
|---|---|---|---|
| `a + b` | 349.5 ns | 2.5 ns | 138× |
| `a * b` | 354.3 ns | 3.9 ns | 92× |
| `np.sqrt(a)` | 349.2 ns | 2.8 ns | 123× |
| `np.sin(a)` | 366.4 ns | 7.0 ns | 52× |
| `np.sum(a)` | 353.1 ns | 3.8 ns | 94× |
| `a @ b` | 673.0 ns | 2.7 ns | 246× |

The **dynamic** variants (`DScalar`, `DDScalar`) hold a `std::vector`, so they have no fixed element size and stay object arrays.

Two functions do not work on these arrays: `np.dot` and `np.linalg.norm`. `np.dot` is not a ufunc but an `__array_function__` dispatcher — it looks at the array type rather than at the dtype, and then rejects anything that is not a native or an old-style dtype. Use `@`, `np.matmul` or `np.vecdot` instead; they compute the same thing and work on object arrays too:

```python
a @ a # instead of np.dot(a, a)
np.sqrt(np.vecdot(a, a)) # instead of np.linalg.norm(a)
```

`np.array(a, dtype=object)` converts back at any time, which restores the object behaviour including `np.dot`.

## C++ Usage

HyperJet is a single header-only library. Add `include/` to your include path and use C++23:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["scikit-build-core>=0.10", "pybind11>=2.13"]
requires = ["scikit-build-core>=0.10", "pybind11>=2.13", "numpy>=2.0"]
build-backend = "scikit_build_core.build"

[project]
Expand Down
15 changes: 13 additions & 2 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,17 @@ if(Eigen_ADDED)
target_include_directories(Eigen INTERFACE ${Eigen_SOURCE_DIR})
endif()

# pybind11 is provided by scikit-build-core build requirements
# pybind11 and numpy are provided by scikit-build-core build requirements
find_package(pybind11 CONFIG REQUIRED)

# the dtype is written against the numpy C API, so its headers are needed
execute_process(
COMMAND "${Python_EXECUTABLE}" -c "import numpy; print(numpy.get_include())"
OUTPUT_VARIABLE numpy_include
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)

if(TEST_INSTALLED_VERSION)
find_package(HyperJet REQUIRED)
else()
Expand Down Expand Up @@ -81,7 +89,10 @@ file(GLOB sources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
pybind11_add_module(HyperJetPython ${sources} ${generated_sources})

# the generated units include "common.h" from src/
target_include_directories(HyperJetPython PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(
HyperJetPython PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_include_directories(HyperJetPython SYSTEM PRIVATE ${numpy_include})

target_link_libraries(HyperJetPython PRIVATE hyperjet::hyperjet Eigen)

Expand Down
7 changes: 5 additions & 2 deletions python/src/bind_ddscalar.cpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
void bind_ddscalar_@HJ_TAG@(pybind11::module &m) {
// A nested hj::DDScalar<..., hj::SScalar<double>, ...> would go here to
// combine indexed and named variables.
bind<hj::DDScalar<1, double, @HJ_SIZE@>>(m, "D@HJ_NAME@Scalar");
bind<hj::DDScalar<2, double, @HJ_SIZE@>>(m, "DD@HJ_NAME@Scalar");
auto d = bind<hj::DDScalar<1, double, @HJ_SIZE@>>(m, "D@HJ_NAME@Scalar");
auto dd = bind<hj::DDScalar<2, double, @HJ_SIZE@>>(m, "DD@HJ_NAME@Scalar");

hyperjet_dtype::bind_dtype_if_static<hj::DDScalar<1, double, @HJ_SIZE@>>(m, d);
hyperjet_dtype::bind_dtype_if_static<hj::DDScalar<2, double, @HJ_SIZE@>>(m, dd);
}

// The signature is wrapped so that the layout does not depend on the length of
Expand Down
2 changes: 2 additions & 0 deletions python/src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>

#include "dtype.h"

namespace hj = hyperjet;

namespace py = pybind11;
Expand Down
Loading
Loading