From c9c0ce8284f7ec9e2b85cda00f2519783eb7b8de Mon Sep 17 00:00:00 2001 From: DiegoDeGusem Date: Thu, 30 Apr 2026 11:58:40 +0200 Subject: [PATCH 1/3] add the strayfield kernel to strayfield in python --- mumaxplus/strayfield.py | 5 +++++ src/bindings/wrap_strayfield.cpp | 3 ++- src/physics/strayfield.cpp | 4 ++++ src/physics/strayfield.hpp | 6 ++++++ src/physics/strayfieldbrute.cu | 4 ++++ src/physics/strayfieldbrute.hpp | 2 ++ src/physics/strayfieldfft.cu | 4 ++++ src/physics/strayfieldfft.hpp | 2 ++ test/test_demag.py | 29 +++++++++++++++++++++++------ 9 files changed, 52 insertions(+), 7 deletions(-) diff --git a/mumaxplus/strayfield.py b/mumaxplus/strayfield.py index 2e82d435..a8e9c239 100644 --- a/mumaxplus/strayfield.py +++ b/mumaxplus/strayfield.py @@ -99,3 +99,8 @@ def switch_radius(self): @switch_radius.setter def switch_radius(self, value=-1): self._impl.switching_radius = value + + @property + def kernel(self): + """Return the StrayFieldKernel.""" + return self._impl.kernel diff --git a/src/bindings/wrap_strayfield.cpp b/src/bindings/wrap_strayfield.cpp index 03beb074..3619468d 100644 --- a/src/bindings/wrap_strayfield.cpp +++ b/src/bindings/wrap_strayfield.cpp @@ -24,5 +24,6 @@ void wrap_strayfield(py::module& m) { }) .def_property("order", &StrayField::order, &StrayField::setOrder) .def_property("epsilon", &StrayField::eps, &StrayField::setEps) - .def_property("switching_radius", &StrayField::switchingradius, &StrayField::setSwitchingradius); + .def_property("switching_radius", &StrayField::switchingradius, &StrayField::setSwitchingradius) + .def_property_readonly("kernel", [](const StrayField& sf) {return fieldToArray(sf.kernel());}); } diff --git a/src/physics/strayfield.cpp b/src/physics/strayfield.cpp index 196da723..8e844c6b 100644 --- a/src/physics/strayfield.cpp +++ b/src/physics/strayfield.cpp @@ -136,3 +136,7 @@ bool StrayField::assuredZero() const { "a Ferromagnet, a (non-collinear) " "Antiferromagnet/Ferrimagnet, nor an Altermagnet."); } + +Field StrayField::kernel() const { + return executor_->kernel(); +} \ No newline at end of file diff --git a/src/physics/strayfield.hpp b/src/physics/strayfield.hpp index 8607ccf0..eab92a5d 100644 --- a/src/physics/strayfield.hpp +++ b/src/physics/strayfield.hpp @@ -65,6 +65,9 @@ class StrayFieldExecutor { /** Return the switching radius of the executor. */ virtual double switchingradius() const = 0; + /** Return the strayfieldkernel as a field */ + virtual Field kernel() const = 0; + protected: /** Source of the stray field*/ const Magnet* magnet_; @@ -152,6 +155,9 @@ class StrayField : public FieldQuantity { /** Return true if one can be sure that the stray field is exactly zero. */ bool assuredZero() const; + /** Return the strayfieldkernel as a field */ + Field kernel() const; + private: std::shared_ptr system_; const Magnet* magnet_; diff --git a/src/physics/strayfieldbrute.cu b/src/physics/strayfieldbrute.cu index e6113955..ddd1db3e 100644 --- a/src/physics/strayfieldbrute.cu +++ b/src/physics/strayfieldbrute.cu @@ -74,3 +74,7 @@ Field StrayFieldBruteExecutor::exec() const { } return h; } + +Field StrayFieldBruteExecutor::kernel() const { + return kernel_.field(); +} diff --git a/src/physics/strayfieldbrute.hpp b/src/physics/strayfieldbrute.hpp index eb966b34..087bb469 100644 --- a/src/physics/strayfieldbrute.hpp +++ b/src/physics/strayfieldbrute.hpp @@ -41,6 +41,8 @@ class StrayFieldBruteExecutor : public StrayFieldExecutor { /** Return the switching radius. */ double switchingradius() const { return kernel_.switchingradius();} + Field kernel() const; + private: StrayFieldKernel kernel_; }; diff --git a/src/physics/strayfieldfft.cu b/src/physics/strayfieldfft.cu index 1b04ad72..cfff5cdd 100644 --- a/src/physics/strayfieldfft.cu +++ b/src/physics/strayfieldfft.cu @@ -239,3 +239,7 @@ Field StrayFieldFFTExecutor::exec() const { cudaLaunch(h.grid().ncells(), k_unpad, h.cu(), mpad->cu()); return h; } + +Field StrayFieldFFTExecutor::kernel() const { + return kernel_.field(); +} diff --git a/src/physics/strayfieldfft.hpp b/src/physics/strayfieldfft.hpp index 8068a9e4..3d02fa39 100644 --- a/src/physics/strayfieldfft.hpp +++ b/src/physics/strayfieldfft.hpp @@ -45,6 +45,8 @@ class StrayFieldFFTExecutor : public StrayFieldExecutor { /** Return the switching radius. */ double switchingradius() const { return kernel_.switchingradius();} + Field kernel() const; + private: StrayFieldKernel kernel_; int3 fftSize; diff --git a/test/test_demag.py b/test/test_demag.py index 54553460..5260cd6c 100644 --- a/test/test_demag.py +++ b/test/test_demag.py @@ -1,5 +1,5 @@ import numpy as np -from mumaxplus import Ferromagnet, Grid, World, _cpp +from mumaxplus import Ferromagnet, Grid, World from mumaxplus.util import MU0 from pathlib import Path @@ -11,7 +11,10 @@ def relative_error(result, wanted): return np.abs((wanted - result) / result) def demag_field_py(magnet): - kernel = _cpp._demag_kernel(magnet._impl, order, eps, R) + magnet.demag_field.order = order + magnet.demag_field.epsilon = eps + magnet.demag_field.switch_radius = R + kernel = magnet.demag_field.kernel mag = magnet.msat.average() * magnet.magnetization.get() # add padding to the magnetization so that the size of magnetization # matches the size of the kernel @@ -49,12 +52,18 @@ def setup_class(self): world = World((1e-9, 1e-9, 1e-9)) magnet = Ferromagnet(world, Grid((nx, ny, nz))) - self.kernel = _cpp._demag_kernel(magnet._impl, order, eps, R) + magnet.demag_field.order = order + magnet.demag_field.epsilon = eps + magnet.demag_field.switch_radius = R + self.kernel = magnet.demag_field.kernel # in the aspect tests, the cellsizes are different world = World((1e-9, 1.27e-9, 1.13e-9)) magnet = Ferromagnet(world, Grid((nx_aspect, ny_aspect, nz_aspect))) - self.kernel_aspect = _cpp._demag_kernel(magnet._impl, order, eps, R) + magnet.demag_field.order = order + magnet.demag_field.epsilon = eps + magnet.demag_field.switch_radius = R + self.kernel_aspect = magnet.demag_field.kernel # open all files script_dir = Path(__file__).resolve().parent @@ -81,7 +90,10 @@ def test_Nxx_radius(self): nx, ny, nz = 126, 64, 8 world = World((1e-9, 1e-9, 1e-9)) magnet = Ferromagnet(world, Grid((nx, ny, nz))) - mumaxplus_result = _cpp._demag_kernel(magnet._impl, 11, 5e-10, 5e-9)[0,nz:,ny:, nx:] # Nxx component + magnet.demag_field.order = 11 + magnet.demag_field.epsilon = 5e-10 + magnet.demag_field.switch_radius = 5e-9 + mumaxplus_result = magnet.demag_field.kernel[0,nz:,ny:,nx:] # avoid fake errors when both values are super small mask = ~((np.abs(self.exact_Nxx) < 5e-15) & (np.abs(mumaxplus_result) < 5e-15)) @@ -137,4 +149,9 @@ def test_Nxy_aspect(self): mumaxplus_result = self.kernel_aspect[3,:,ny_aspect+1:, nx_aspect+1:] # Nxy component err = np.max(relative_error(mumaxplus_result, self.exact_aspect_Nxy)) - assert err < 1e-5 \ No newline at end of file + assert err < 1e-5 + + +world = World((1e-9, 1e-9, 1e-9)) +magnet = Ferromagnet(world, Grid((nx, ny, nz))) +print(magnet.demag_field.kernel) \ No newline at end of file From 81637be0e89c10a11c8fc7b303ca3ccc22f24a71 Mon Sep 17 00:00:00 2001 From: DiegoDeGusem Date: Thu, 30 Apr 2026 16:42:58 +0200 Subject: [PATCH 2/3] Remove leftover prints --- test/test_demag.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/test_demag.py b/test/test_demag.py index 5260cd6c..1cb7d851 100644 --- a/test/test_demag.py +++ b/test/test_demag.py @@ -150,8 +150,3 @@ def test_Nxy_aspect(self): err = np.max(relative_error(mumaxplus_result, self.exact_aspect_Nxy)) assert err < 1e-5 - - -world = World((1e-9, 1e-9, 1e-9)) -magnet = Ferromagnet(world, Grid((nx, ny, nz))) -print(magnet.demag_field.kernel) \ No newline at end of file From 4ee1c56bd627ac6689b885bb24082844a744274b Mon Sep 17 00:00:00 2001 From: DiegoDeGusem Date: Tue, 5 May 2026 11:30:49 +0200 Subject: [PATCH 3/3] Make Field StrayFieldKernel and make test faster using _cpp --- src/bindings/wrap_strayfield.cpp | 2 +- src/physics/strayfield.cpp | 4 ---- src/physics/strayfield.hpp | 5 +++-- src/physics/strayfieldbrute.cu | 6 +----- src/physics/strayfieldbrute.hpp | 2 +- src/physics/strayfieldfft.cu | 6 +----- src/physics/strayfieldfft.hpp | 2 +- test/test_demag.py | 22 +++++----------------- 8 files changed, 13 insertions(+), 36 deletions(-) diff --git a/src/bindings/wrap_strayfield.cpp b/src/bindings/wrap_strayfield.cpp index 3619468d..70ead9cb 100644 --- a/src/bindings/wrap_strayfield.cpp +++ b/src/bindings/wrap_strayfield.cpp @@ -25,5 +25,5 @@ void wrap_strayfield(py::module& m) { .def_property("order", &StrayField::order, &StrayField::setOrder) .def_property("epsilon", &StrayField::eps, &StrayField::setEps) .def_property("switching_radius", &StrayField::switchingradius, &StrayField::setSwitchingradius) - .def_property_readonly("kernel", [](const StrayField& sf) {return fieldToArray(sf.kernel());}); + .def_property_readonly("kernel", [](const StrayField& sf) {return fieldToArray(sf.kernel().field());}); } diff --git a/src/physics/strayfield.cpp b/src/physics/strayfield.cpp index 8e844c6b..5e01005c 100644 --- a/src/physics/strayfield.cpp +++ b/src/physics/strayfield.cpp @@ -135,8 +135,4 @@ bool StrayField::assuredZero() const { throw std::invalid_argument("Cannot calculate strayfield since magnet is neither " "a Ferromagnet, a (non-collinear) " "Antiferromagnet/Ferrimagnet, nor an Altermagnet."); -} - -Field StrayField::kernel() const { - return executor_->kernel(); } \ No newline at end of file diff --git a/src/physics/strayfield.hpp b/src/physics/strayfield.hpp index eab92a5d..8ad89316 100644 --- a/src/physics/strayfield.hpp +++ b/src/physics/strayfield.hpp @@ -4,6 +4,7 @@ #include #include "fieldquantity.hpp" +#include "strayfieldkernel.hpp" class Parameter; class Magnet; @@ -66,7 +67,7 @@ class StrayFieldExecutor { virtual double switchingradius() const = 0; /** Return the strayfieldkernel as a field */ - virtual Field kernel() const = 0; + virtual const StrayFieldKernel& kernel() const = 0; protected: /** Source of the stray field*/ @@ -156,7 +157,7 @@ class StrayField : public FieldQuantity { bool assuredZero() const; /** Return the strayfieldkernel as a field */ - Field kernel() const; + const StrayFieldKernel& kernel() const { return executor_->kernel();} private: std::shared_ptr system_; diff --git a/src/physics/strayfieldbrute.cu b/src/physics/strayfieldbrute.cu index ddd1db3e..e45eb5fa 100644 --- a/src/physics/strayfieldbrute.cu +++ b/src/physics/strayfieldbrute.cu @@ -73,8 +73,4 @@ Field StrayFieldBruteExecutor::exec() const { cudaLaunch(ncells, k_demagfield, h.cu(), hostmag.cu(), kernel_.field().cu(), msat.cu()); } return h; -} - -Field StrayFieldBruteExecutor::kernel() const { - return kernel_.field(); -} +} \ No newline at end of file diff --git a/src/physics/strayfieldbrute.hpp b/src/physics/strayfieldbrute.hpp index 087bb469..e7e2368d 100644 --- a/src/physics/strayfieldbrute.hpp +++ b/src/physics/strayfieldbrute.hpp @@ -41,7 +41,7 @@ class StrayFieldBruteExecutor : public StrayFieldExecutor { /** Return the switching radius. */ double switchingradius() const { return kernel_.switchingradius();} - Field kernel() const; + const StrayFieldKernel& kernel() const { return kernel_;}; private: StrayFieldKernel kernel_; diff --git a/src/physics/strayfieldfft.cu b/src/physics/strayfieldfft.cu index cfff5cdd..4fc4bc0c 100644 --- a/src/physics/strayfieldfft.cu +++ b/src/physics/strayfieldfft.cu @@ -238,8 +238,4 @@ Field StrayFieldFFTExecutor::exec() const { Field h(system_, 3); cudaLaunch(h.grid().ncells(), k_unpad, h.cu(), mpad->cu()); return h; -} - -Field StrayFieldFFTExecutor::kernel() const { - return kernel_.field(); -} +} \ No newline at end of file diff --git a/src/physics/strayfieldfft.hpp b/src/physics/strayfieldfft.hpp index 3d02fa39..a426be23 100644 --- a/src/physics/strayfieldfft.hpp +++ b/src/physics/strayfieldfft.hpp @@ -45,7 +45,7 @@ class StrayFieldFFTExecutor : public StrayFieldExecutor { /** Return the switching radius. */ double switchingradius() const { return kernel_.switchingradius();} - Field kernel() const; + const StrayFieldKernel& kernel() const { return kernel_;}; private: StrayFieldKernel kernel_; diff --git a/test/test_demag.py b/test/test_demag.py index 1cb7d851..3d8f1048 100644 --- a/test/test_demag.py +++ b/test/test_demag.py @@ -1,5 +1,5 @@ import numpy as np -from mumaxplus import Ferromagnet, Grid, World +from mumaxplus import Ferromagnet, Grid, World, _cpp from mumaxplus.util import MU0 from pathlib import Path @@ -11,10 +11,7 @@ def relative_error(result, wanted): return np.abs((wanted - result) / result) def demag_field_py(magnet): - magnet.demag_field.order = order - magnet.demag_field.epsilon = eps - magnet.demag_field.switch_radius = R - kernel = magnet.demag_field.kernel + kernel = _cpp._demag_kernel(magnet._impl, order, eps, R) mag = magnet.msat.average() * magnet.magnetization.get() # add padding to the magnetization so that the size of magnetization # matches the size of the kernel @@ -52,18 +49,12 @@ def setup_class(self): world = World((1e-9, 1e-9, 1e-9)) magnet = Ferromagnet(world, Grid((nx, ny, nz))) - magnet.demag_field.order = order - magnet.demag_field.epsilon = eps - magnet.demag_field.switch_radius = R - self.kernel = magnet.demag_field.kernel + self.kernel = _cpp._demag_kernel(magnet._impl, order, eps, R) # in the aspect tests, the cellsizes are different world = World((1e-9, 1.27e-9, 1.13e-9)) magnet = Ferromagnet(world, Grid((nx_aspect, ny_aspect, nz_aspect))) - magnet.demag_field.order = order - magnet.demag_field.epsilon = eps - magnet.demag_field.switch_radius = R - self.kernel_aspect = magnet.demag_field.kernel + self.kernel_aspect = _cpp._demag_kernel(magnet._impl, order, eps, R) # open all files script_dir = Path(__file__).resolve().parent @@ -90,10 +81,7 @@ def test_Nxx_radius(self): nx, ny, nz = 126, 64, 8 world = World((1e-9, 1e-9, 1e-9)) magnet = Ferromagnet(world, Grid((nx, ny, nz))) - magnet.demag_field.order = 11 - magnet.demag_field.epsilon = 5e-10 - magnet.demag_field.switch_radius = 5e-9 - mumaxplus_result = magnet.demag_field.kernel[0,nz:,ny:,nx:] + mumaxplus_result = _cpp._demag_kernel(magnet._impl, 11, 5e-10, 5e-9)[0,nz:,ny:, nx:] # Nxx # avoid fake errors when both values are super small mask = ~((np.abs(self.exact_Nxx) < 5e-15) & (np.abs(mumaxplus_result) < 5e-15))