SimiLie
Loading...
Searching...
No Matches
nonlinear_magnetostatics.py
1#!/usr/bin/env python3
2# SPDX-FileCopyrightText: 2026 Baptiste Legouix
3# SPDX-License-Identifier: AGPL-3.0-or-later
4
5from __future__ import annotations
6
7from similie_generate_cpp_hamiltonian import (
8 HamiltonianDefinition,
9 SymbolicFunctionDefinition,
10)
11from sympy import Function, simplify, symbols
12
13
14INTERPOLATED_BH_CURVE_HEADER = """\
15template <std::size_t MaxSamples>
16struct InterpolatedNonlinearBHCurve
17{
18 static constexpr std::size_t MAX_SAMPLES = MaxSamples;
19
20 std::size_t m_num_samples = 0;
21 std::array<double, MAX_SAMPLES> m_b {};
22 std::array<double, MAX_SAMPLES> m_h {};
23 std::array<double, MAX_SAMPLES> m_q {};
24 std::array<double, MAX_SAMPLES> m_nu {};
25 std::array<double, MAX_SAMPLES - 1> m_dnu_dq {};
26 std::array<double, MAX_SAMPLES - 1> m_dh_db {};
27
28 template <std::size_t NumSamples>
29 explicit InterpolatedNonlinearBHCurve(
30 std::array<double, NumSamples> const& magnetic_induction_samples,
31 std::array<double, NumSamples> const& magnetic_field_samples,
32 std::size_t num_samples = NumSamples)
33 : m_num_samples(num_samples)
34 {
35 static_assert(NumSamples <= MAX_SAMPLES);
36 if (m_num_samples < 2 || m_num_samples > NumSamples) {
37 throw std::runtime_error("invalid nonlinear B-H curve sample count");
38 }
39
40 for (std::size_t i = 0; i < m_num_samples; ++i) {
41 m_b[i] = magnetic_induction_samples[i];
42 m_h[i] = magnetic_field_samples[i];
43 m_q[i] = m_b[i] * m_b[i];
44 }
45
46 m_nu[0] = m_h[1] / m_b[1];
47 for (std::size_t i = 1; i < m_num_samples; ++i) {
48 m_nu[i] = m_h[i] / m_b[i];
49 }
50 for (std::size_t i = 0; i + 1 < m_num_samples; ++i) {
51 m_dnu_dq[i] = (m_nu[i + 1] - m_nu[i]) / (m_q[i + 1] - m_q[i]);
52 m_dh_db[i] = (m_h[i + 1] - m_h[i]) / (m_b[i + 1] - m_b[i]);
53 }
54 }
55
56 [[nodiscard]] KOKKOS_FUNCTION std::size_t bracket_q(double q_value) const
57 {
58 if (q_value <= m_q[0]) {
59 return 0;
60 }
61 for (std::size_t i = 0; i + 1 < m_num_samples; ++i) {
62 if (q_value <= m_q[i + 1]) {
63 return i;
64 }
65 }
66 return m_num_samples - 2;
67 }
68
69 [[nodiscard]] KOKKOS_FUNCTION std::size_t bracket_h(double h_value) const
70 {
71 if (h_value <= m_h[0]) {
72 return 0;
73 }
74 for (std::size_t i = 0; i + 1 < m_num_samples; ++i) {
75 if (h_value <= m_h[i + 1]) {
76 return i;
77 }
78 }
79 return m_num_samples - 2;
80 }
81
82 [[nodiscard]] KOKKOS_FUNCTION double nu_from_q(double q_value) const
83 {
84 std::size_t const interval = bracket_q(q_value);
85 return m_nu[interval] + m_dnu_dq[interval] * (q_value - m_q[interval]);
86 }
87
88 [[nodiscard]] KOKKOS_FUNCTION double dnu_dq(double q_value) const
89 {
90 return m_dnu_dq[bracket_q(q_value)];
91 }
92
93 [[nodiscard]] KOKKOS_FUNCTION double h_from_b(double b_value) const
94 {
95 if (b_value <= m_b[0]) {
96 return m_h[0];
97 }
98 if (b_value >= m_b[m_num_samples - 1]) {
99 std::size_t const interval = m_num_samples - 2;
100 return m_h[interval] + m_dh_db[interval] * (b_value - m_b[interval]);
101 }
102 std::size_t const interval = bracket_q(b_value * b_value);
103 return m_h[interval] + m_dh_db[interval] * (b_value - m_b[interval]);
104 }
105
106 [[nodiscard]] KOKKOS_FUNCTION double dh_db_from_b(double b_value) const
107 {
108 if (b_value <= m_b[0]) {
109 return m_dh_db[0];
110 }
111 if (b_value >= m_b[m_num_samples - 1]) {
112 return m_dh_db[m_num_samples - 2];
113 }
114 return m_dh_db[bracket_q(b_value * b_value)];
115 }
116
117 [[nodiscard]] KOKKOS_FUNCTION double h_over_b_from_b(double b_value) const
118 {
119 if (b_value == 0.0) {
120 return dh_db_from_b(0.0);
121 }
122 return h_from_b(b_value) / b_value;
123 }
124
125 [[nodiscard]] KOKKOS_FUNCTION double d_h_over_b_db_from_b(double b_value) const
126 {
127 if (b_value == 0.0) {
128 return 0.0;
129 }
130 return (dh_db_from_b(b_value) * b_value - h_from_b(b_value)) / (b_value * b_value);
131 }
132
133 [[nodiscard]] KOKKOS_FUNCTION double d2_h_over_b_db2_from_b(double b_value) const
134 {
135 if (b_value == 0.0) {
136 return 0.0;
137 }
138 double const intercept = h_from_b(b_value) - dh_db_from_b(b_value) * b_value;
139 return 2.0 * intercept / (b_value * b_value * b_value);
140 }
141
142 [[nodiscard]] KOKKOS_FUNCTION double h_over_b_from_q(double q_value) const
143 {
144 return h_over_b_from_b(std::sqrt(q_value));
145 }
146
147 [[nodiscard]] KOKKOS_FUNCTION double d_h_over_b_dq_from_q(double q_value) const
148 {
149 if (q_value == 0.0) {
150 return 0.0;
151 }
152 double const b_value = std::sqrt(q_value);
153 return d_h_over_b_db_from_b(b_value) / (2.0 * b_value);
154 }
155
156 [[nodiscard]] KOKKOS_FUNCTION double d2_h_over_b_dq2_from_q(double q_value) const
157 {
158 if (q_value == 0.0) {
159 return 0.0;
160 }
161 double const b_value = std::sqrt(q_value);
162 return d2_h_over_b_db2_from_b(b_value) / (4.0 * q_value)
163 - d_h_over_b_db_from_b(b_value) / (4.0 * q_value * b_value);
164 }
165
166 [[nodiscard]] KOKKOS_FUNCTION double b_from_h(double h_value) const
167 {
168 if (h_value <= m_h[0]) {
169 return m_b[0];
170 }
171 std::size_t const interval = bracket_h(h_value);
172 return m_b[interval] + (h_value - m_h[interval]) / m_dh_db[interval];
173 }
174
175 [[nodiscard]] KOKKOS_FUNCTION double db_dh(double h_value) const
176 {
177 return 1.0 / m_dh_db[bracket_h(h_value)];
178 }
179
180};
181"""
182
183
184CONSTITUTIVE_LAW_HEADER = """\
185template <class BHCurve>
186class NonlinearMagneticInductionToMagneticField
187{
188 BHCurve m_bh_curve;
189
190 [[nodiscard]] KOKKOS_FUNCTION std::array<double, 9> jacobian_from_magnetic_induction(
191 std::span<double const, 3> hodge_star,
192 std::span<double const, 3> magnetic_induction) const
193 {
194 double const q = magnetic_induction[0] * magnetic_induction[0]
195 + magnetic_induction[1] * magnetic_induction[1]
196 + magnetic_induction[2] * magnetic_induction[2];
197 double const b_norm = std::sqrt(q);
198 if (b_norm == 0.0) {
199 double const dh_db = m_bh_curve.dh_db_from_b(0.0);
200 return {
201 hodge_star[0] * dh_db,
202 0.0,
203 0.0,
204 0.0,
205 hodge_star[1] * dh_db,
206 0.0,
207 0.0,
208 0.0,
209 hodge_star[2] * dh_db,
210 };
211 }
212 double const h_norm = m_bh_curve.h_from_b(b_norm);
213 double const dh_db = m_bh_curve.dh_db_from_b(b_norm);
214 double const scale = h_norm / b_norm;
215 double const radial_scale = (dh_db - scale) / q;
216 std::array<double, 9> jacobian {};
217 for (std::size_t row = 0; row < 3; ++row) {
218 for (std::size_t column = 0; column < 3; ++column) {
219 jacobian[3 * row + column]
220 = hodge_star[row]
221 * ((row == column ? scale : 0.0)
222 + radial_scale * magnetic_induction[row] * magnetic_induction[column]);
223 }
224 }
225 return jacobian;
226 }
227
228 [[nodiscard]] KOKKOS_FUNCTION static std::array<double, 9> inverse_matrix3x3(
229 std::array<double, 9> const& matrix)
230 {
231 double const a00 = matrix[0];
232 double const a01 = matrix[1];
233 double const a02 = matrix[2];
234 double const a10 = matrix[3];
235 double const a11 = matrix[4];
236 double const a12 = matrix[5];
237 double const a20 = matrix[6];
238 double const a21 = matrix[7];
239 double const a22 = matrix[8];
240
241 double const c00 = a11 * a22 - a12 * a21;
242 double const c01 = a02 * a21 - a01 * a22;
243 double const c02 = a01 * a12 - a02 * a11;
244 double const c10 = a12 * a20 - a10 * a22;
245 double const c11 = a00 * a22 - a02 * a20;
246 double const c12 = a02 * a10 - a00 * a12;
247 double const c20 = a10 * a21 - a11 * a20;
248 double const c21 = a01 * a20 - a00 * a21;
249 double const c22 = a00 * a11 - a01 * a10;
250
251 double const determinant = a00 * c00 + a01 * c10 + a02 * c20;
252 return {
253 c00 / determinant,
254 c01 / determinant,
255 c02 / determinant,
256 c10 / determinant,
257 c11 / determinant,
258 c12 / determinant,
259 c20 / determinant,
260 c21 / determinant,
261 c22 / determinant,
262 };
263 }
264
265public:
266 explicit NonlinearMagneticInductionToMagneticField(BHCurve bh_curve) : m_bh_curve(bh_curve) {}
267
268 [[nodiscard]] KOKKOS_FUNCTION std::array<double, 3> operator()(
269 std::span<double const, 3> hodge_star,
270 std::span<double const, 3> magnetic_induction) const
271 {
272 double const q = magnetic_induction[0] * magnetic_induction[0]
273 + magnetic_induction[1] * magnetic_induction[1]
274 + magnetic_induction[2] * magnetic_induction[2];
275 double const b_norm = std::sqrt(q);
276 if (b_norm == 0.0) {
277 return {0.0, 0.0, 0.0};
278 }
279 double const scale = m_bh_curve.h_from_b(b_norm) / b_norm;
280 return {
281 hodge_star[0] * scale * magnetic_induction[0],
282 hodge_star[1] * scale * magnetic_induction[1],
283 hodge_star[2] * scale * magnetic_induction[2],
284 };
285 }
286
287 [[nodiscard]] KOKKOS_FUNCTION double value(
288 std::span<double const, 3> hodge_star,
289 std::span<double const, 3> magnetic_induction,
290 std::size_t row,
291 std::size_t column) const
292 {
293 return jacobian(hodge_star, magnetic_induction)[3 * row + column];
294 }
295
296 [[nodiscard]] KOKKOS_FUNCTION std::array<double, 3> inverse(
297 std::span<double const, 3> hodge_star,
298 std::span<double const, 3> magnetic_field) const
299 {
300 double const unhodge_h0 = magnetic_field[0] / hodge_star[0];
301 double const unhodge_h1 = magnetic_field[1] / hodge_star[1];
302 double const unhodge_h2 = magnetic_field[2] / hodge_star[2];
303 double const h_norm = std::sqrt(
304 unhodge_h0 * unhodge_h0 + unhodge_h1 * unhodge_h1 + unhodge_h2 * unhodge_h2);
305 if (h_norm == 0.0) {
306 return {0.0, 0.0, 0.0};
307 }
308 double const b_norm = m_bh_curve.b_from_h(h_norm);
309 double const scale = b_norm / h_norm;
310 return {scale * unhodge_h0, scale * unhodge_h1, scale * unhodge_h2};
311 }
312
313 [[nodiscard]] KOKKOS_FUNCTION double inverse_value(
314 std::span<double const, 3> hodge_star,
315 std::span<double const, 3> magnetic_field,
316 std::size_t row,
317 std::size_t column) const
318 {
319 std::array<double, 3> const magnetic_induction = inverse(hodge_star, magnetic_field);
320 return inverse_matrix3x3(jacobian(hodge_star, magnetic_induction))[3 * row + column];
321 }
322
323 [[nodiscard]] KOKKOS_FUNCTION std::array<double, 9> jacobian(
324 std::span<double const, 3> hodge_star,
325 std::span<double const, 3> magnetic_induction) const
326 {
327 return jacobian_from_magnetic_induction(hodge_star, magnetic_induction);
328 }
329};
330"""
331
332
334 @staticmethod
335 def __call__() -> HamiltonianDefinition:
336 a = symbols("A0:3")
337 b = symbols("B0:3")
338 j = symbols("j0:3")
339 magnetic_field_over_induction = Function("magnetic_field_over_induction")
340 q = sum(component**2 for component in b)
341 h = [b[i] * magnetic_field_over_induction(q) for i in range(3)]
342 hamiltonian = simplify(sum(b[i] * h[i] / 2 for i in range(3))) - sum(
343 a[i] * j[i] for i in range(3)
344 )
345
346 return HamiltonianDefinition(
347 namespace="similie::physics::magnetostatics",
348 struct_name="NonlinearMagnetostaticsHamiltonian",
349 parameters=["bh_curve"],
350 hamiltonian=hamiltonian,
351 variables=[a, b, j],
352 includes=["<similie/physics/magnetostatics/magnetostatics_quantities.hpp>"],
353 template_parameters=["class BHCurve"],
354 parameter_types={"bh_curve": "BHCurve"},
355 symbolic_functions={
356 "magnetic_field_over_induction": SymbolicFunctionDefinition(
357 value_expression="m_bh_curve.h_over_b_from_q({argument})",
358 derivative_expressions={
359 1: "m_bh_curve.d_h_over_b_dq_from_q({argument})",
360 2: "m_bh_curve.d2_h_over_b_dq2_from_q({argument})",
361 },
362 )
363 },
364 moments_object_component_expression="{moments}.template get<{index}>()",
365 moments_object_norm2_expression="{moments}.norm2()",
366 generate_moments_jacobian=True,
367 namespace_preamble=INTERPOLATED_BH_CURVE_HEADER,
368 namespace_epilogue=CONSTITUTIVE_LAW_HEADER,
369 )