step2torque 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. step2torque-0.1.0/LICENSE +22 -0
  2. step2torque-0.1.0/PKG-INFO +87 -0
  3. step2torque-0.1.0/README.md +62 -0
  4. step2torque-0.1.0/pyproject.toml +39 -0
  5. step2torque-0.1.0/setup.cfg +4 -0
  6. step2torque-0.1.0/setup.py +32 -0
  7. step2torque-0.1.0/src/step2torque/__init__.py +16 -0
  8. step2torque-0.1.0/src/step2torque/_assembly.cpp +161 -0
  9. step2torque-0.1.0/src/step2torque/_contact.py +289 -0
  10. step2torque-0.1.0/src/step2torque/_cuda.py +110 -0
  11. step2torque-0.1.0/src/step2torque/_currents.py +98 -0
  12. step2torque-0.1.0/src/step2torque/_field.py +263 -0
  13. step2torque-0.1.0/src/step2torque/_kernel.py +117 -0
  14. step2torque-0.1.0/src/step2torque/_mesh.py +435 -0
  15. step2torque-0.1.0/src/step2torque/_motion.py +76 -0
  16. step2torque-0.1.0/src/step2torque/_parallel.hpp +59 -0
  17. step2torque-0.1.0/src/step2torque/_results.py +68 -0
  18. step2torque-0.1.0/src/step2torque/_source.py +175 -0
  19. step2torque-0.1.0/src/step2torque/_torque.py +402 -0
  20. step2torque-0.1.0/src/step2torque/_worker.py +345 -0
  21. step2torque-0.1.0/src/step2torque/materials/1j22.csv +8 -0
  22. step2torque-0.1.0/src/step2torque/materials/__init__.py +51 -0
  23. step2torque-0.1.0/src/step2torque/materials/dt4.csv +14 -0
  24. step2torque-0.1.0/src/step2torque/materials/kede_1j21.csv +11 -0
  25. step2torque-0.1.0/src/step2torque/materials/rotor.csv +14 -0
  26. step2torque-0.1.0/src/step2torque/materials/sources.json +15 -0
  27. step2torque-0.1.0/src/step2torque/model.py +271 -0
  28. step2torque-0.1.0/src/step2torque/solution.py +307 -0
  29. step2torque-0.1.0/src/step2torque/visualization/__init__.py +6 -0
  30. step2torque-0.1.0/src/step2torque/visualization/__main__.py +64 -0
  31. step2torque-0.1.0/src/step2torque/visualization/appearance.py +52 -0
  32. step2torque-0.1.0/src/step2torque/visualization/archive.py +177 -0
  33. step2torque-0.1.0/src/step2torque/visualization/html.py +43 -0
  34. step2torque-0.1.0/src/step2torque/visualization/lines.py +140 -0
  35. step2torque-0.1.0/src/step2torque/visualization/native.py +325 -0
  36. step2torque-0.1.0/src/step2torque/visualization/packed.py +122 -0
  37. step2torque-0.1.0/src/step2torque/visualization/plane.py +74 -0
  38. step2torque-0.1.0/src/step2torque/visualization/setup.py +290 -0
  39. step2torque-0.1.0/src/step2torque/visualization/viewer.html +42 -0
  40. step2torque-0.1.0/src/step2torque/visualization/viewer.js +3915 -0
  41. step2torque-0.1.0/src/step2torque/visualization/viewer.py +355 -0
  42. step2torque-0.1.0/src/step2torque.egg-info/PKG-INFO +87 -0
  43. step2torque-0.1.0/src/step2torque.egg-info/SOURCES.txt +47 -0
  44. step2torque-0.1.0/src/step2torque.egg-info/dependency_links.txt +1 -0
  45. step2torque-0.1.0/src/step2torque.egg-info/entry_points.txt +2 -0
  46. step2torque-0.1.0/src/step2torque.egg-info/requires.txt +15 -0
  47. step2torque-0.1.0/src/step2torque.egg-info/top_level.txt +1 -0
  48. step2torque-0.1.0/tests/test_contact.py +199 -0
  49. step2torque-0.1.0/tests/test_motor.py +305 -0
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 step2torque contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.4
2
+ Name: step2torque
3
+ Version: 0.1.0
4
+ Summary: STEP assemblies to nonlinear 3D motor torque, from Python.
5
+ License-Expression: MIT
6
+ Project-URL: Repository, https://github.com/qwertpas/step2torque
7
+ Project-URL: Viewer, https://qwertpas.github.io/step2torque/
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: ngsolve>=6.2.2608
12
+ Requires-Dist: numpy>=1.26
13
+ Requires-Dist: scipy>=1.12
14
+ Requires-Dist: psutil>=5.9
15
+ Requires-Dist: matplotlib>=3.8
16
+ Provides-Extra: cuda
17
+ Requires-Dist: nvmath-python[cu12]; extra == "cuda"
18
+ Requires-Dist: nvidia-cudss-cu12; extra == "cuda"
19
+ Requires-Dist: cupy-cuda12x; extra == "cuda"
20
+ Provides-Extra: test
21
+ Requires-Dist: pytest; extra == "test"
22
+ Requires-Dist: build; extra == "test"
23
+ Requires-Dist: ruff; extra == "test"
24
+ Dynamic: license-file
25
+
26
+ # step2torque
27
+
28
+ GPU-accelerated nonlinear 3D FEM solver to get motor torque from a STEP assembly. Simple python interface makes it easy to automate evaluation of your custom motors with arbitrary geometry.
29
+
30
+ [Open the browser viewer](https://qwertpas.github.io/step2torque/)
31
+
32
+ | Model | Flux density |
33
+ |---|---|
34
+ | <img src="examples/simple/model-3d.png" height="300" alt="Motor CAD view"> | <img src="examples/simple/animation.gif" height="300" alt="Motor flux density animation"> |
35
+
36
+ Install:
37
+ ```sh
38
+ python3 -m pip install step2torque
39
+ ```
40
+
41
+ Solve using your own .step file:
42
+ ```python
43
+ from step2torque import Motor
44
+
45
+ motor = Motor("motor.step")
46
+ print(motor.components) # CAD component names
47
+ motor.components["stator"].set_material("1J22")
48
+ motor.components["coil"].make_coil(phase=1, turns=72, direction="+z")
49
+ motor.components["magnet"].make_magnet(direction="radial", br=1.2)
50
+
51
+ sol = motor.solve(power=5.5) # returns immediately; optimizes currents
52
+ print(sol.status, sol.progress)
53
+ sol.wait() # optional blocking wait
54
+ print(sol.torque, sol.currents) # N·m, instantaneous phase A
55
+ print(sol.flux_density([[0, 0, 0]])) # B in tesla, points in CAD mm
56
+ ```
57
+
58
+ Render and share the solved field:
59
+
60
+ ```python
61
+ from step2torque import Plane
62
+ sol.render_image("field.png", plane=Plane.axial(z=0))
63
+ sol.render_html("motor.html")
64
+ sol.export("motor.s2t")
65
+ ```
66
+
67
+ Open the HTML directly, or load the `.s2t` file in the browser viewer.
68
+
69
+ ---
70
+
71
+ ### Specifying parameters
72
+ - Coils are circular or rectangular annuli; direction is their right-hand axis. Specify turns or resistance; the other parameter is calculated using CAD dimensions, 60% copper fill and 20°C.
73
+ - Multiphase currents sum to zero by default; `balanced=False` permits independent phase drives. Coils sharing a phase are in series; resistance is per coil.
74
+ - Magnets accept a global vector, `"radial"`, or `"-radial"`. For a multipole ring, use `make_magnet(direction="radial", poles=20)`; `offset=5` shifts its pole pattern by 5 mechanical degrees.
75
+ - Rotor-named components and magnets can be rotated. `Motor(..., axis="+z", center=(0,0,0))` changes the shaft axis and center in mm.
76
+ - `motor.solve(angle=10, current={1: 2.0})` rotates moving parts 10 mechanical degrees from the STEP and uses fixed current. Use `current={}` to get cogging (torque at zero current).
77
+ - Separate touching iron parts use a 10 µm surface contact. `Motor(..., contact_gap=0.01)` sets this gap in mm; use zero for perfect magnetic contact.
78
+ - Other components default to steel.
79
+ - Before solving, you may use `motor.render_image("setup.png")`, `motor.render_html("setup.html")` or `motor.export("setup.s2t")` to check the geometry, phase labels and winding direction. Preview currents default to 1 A per phase; pass `current={1: 2.0}` and `angle=10` to set their initial values.
80
+
81
+
82
+ GPU acceleration is included. `python3 -m pip install 'step2torque[cuda]'` enables NVIDIA
83
+ cuDSS; `device="auto"` selects an available device. Optional solve arguments:
84
+ `threads=8`, `mesh_size=0.6` (surface mm).
85
+
86
+ [Examples, benchmarks and commercial case studies](examples/README.md)
87
+
@@ -0,0 +1,62 @@
1
+ # step2torque
2
+
3
+ GPU-accelerated nonlinear 3D FEM solver to get motor torque from a STEP assembly. Simple python interface makes it easy to automate evaluation of your custom motors with arbitrary geometry.
4
+
5
+ [Open the browser viewer](https://qwertpas.github.io/step2torque/)
6
+
7
+ | Model | Flux density |
8
+ |---|---|
9
+ | <img src="examples/simple/model-3d.png" height="300" alt="Motor CAD view"> | <img src="examples/simple/animation.gif" height="300" alt="Motor flux density animation"> |
10
+
11
+ Install:
12
+ ```sh
13
+ python3 -m pip install step2torque
14
+ ```
15
+
16
+ Solve using your own .step file:
17
+ ```python
18
+ from step2torque import Motor
19
+
20
+ motor = Motor("motor.step")
21
+ print(motor.components) # CAD component names
22
+ motor.components["stator"].set_material("1J22")
23
+ motor.components["coil"].make_coil(phase=1, turns=72, direction="+z")
24
+ motor.components["magnet"].make_magnet(direction="radial", br=1.2)
25
+
26
+ sol = motor.solve(power=5.5) # returns immediately; optimizes currents
27
+ print(sol.status, sol.progress)
28
+ sol.wait() # optional blocking wait
29
+ print(sol.torque, sol.currents) # N·m, instantaneous phase A
30
+ print(sol.flux_density([[0, 0, 0]])) # B in tesla, points in CAD mm
31
+ ```
32
+
33
+ Render and share the solved field:
34
+
35
+ ```python
36
+ from step2torque import Plane
37
+ sol.render_image("field.png", plane=Plane.axial(z=0))
38
+ sol.render_html("motor.html")
39
+ sol.export("motor.s2t")
40
+ ```
41
+
42
+ Open the HTML directly, or load the `.s2t` file in the browser viewer.
43
+
44
+ ---
45
+
46
+ ### Specifying parameters
47
+ - Coils are circular or rectangular annuli; direction is their right-hand axis. Specify turns or resistance; the other parameter is calculated using CAD dimensions, 60% copper fill and 20°C.
48
+ - Multiphase currents sum to zero by default; `balanced=False` permits independent phase drives. Coils sharing a phase are in series; resistance is per coil.
49
+ - Magnets accept a global vector, `"radial"`, or `"-radial"`. For a multipole ring, use `make_magnet(direction="radial", poles=20)`; `offset=5` shifts its pole pattern by 5 mechanical degrees.
50
+ - Rotor-named components and magnets can be rotated. `Motor(..., axis="+z", center=(0,0,0))` changes the shaft axis and center in mm.
51
+ - `motor.solve(angle=10, current={1: 2.0})` rotates moving parts 10 mechanical degrees from the STEP and uses fixed current. Use `current={}` to get cogging (torque at zero current).
52
+ - Separate touching iron parts use a 10 µm surface contact. `Motor(..., contact_gap=0.01)` sets this gap in mm; use zero for perfect magnetic contact.
53
+ - Other components default to steel.
54
+ - Before solving, you may use `motor.render_image("setup.png")`, `motor.render_html("setup.html")` or `motor.export("setup.s2t")` to check the geometry, phase labels and winding direction. Preview currents default to 1 A per phase; pass `current={1: 2.0}` and `angle=10` to set their initial values.
55
+
56
+
57
+ GPU acceleration is included. `python3 -m pip install 'step2torque[cuda]'` enables NVIDIA
58
+ cuDSS; `device="auto"` selects an available device. Optional solve arguments:
59
+ `threads=8`, `mesh_size=0.6` (surface mm).
60
+
61
+ [Examples, benchmarks and commercial case studies](examples/README.md)
62
+
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools", "pybind11"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "step2torque"
7
+ version = "0.1.0"
8
+ description = "STEP assemblies to nonlinear 3D motor torque, from Python."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ dependencies = ["ngsolve>=6.2.2608", "numpy>=1.26", "scipy>=1.12", "psutil>=5.9", "matplotlib>=3.8"]
13
+
14
+ [project.optional-dependencies]
15
+ cuda = ["nvmath-python[cu12]", "nvidia-cudss-cu12", "cupy-cuda12x"]
16
+ test = ["pytest", "build", "ruff"]
17
+
18
+ [project.urls]
19
+ Repository = "https://github.com/qwertpas/step2torque"
20
+ Viewer = "https://qwertpas.github.io/step2torque/"
21
+
22
+ [project.scripts]
23
+ step2torque-view = "step2torque.visualization.__main__:main"
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
27
+
28
+ [tool.setuptools.package-data]
29
+ step2torque = ["materials/*.csv", "materials/*.json", "*.cpp", "*.hpp"]
30
+ "step2torque.visualization" = ["*.html", "*.js"]
31
+
32
+ [tool.pytest.ini_options]
33
+ testpaths = ["tests"]
34
+ addopts = "-m 'not solver'"
35
+ markers = ["solver: opt-in physical solver checks; run explicitly with -m solver"]
36
+
37
+ [tool.ruff.lint]
38
+ select = ["E4", "E7", "E9", "F"]
39
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ import sys
2
+ import hashlib
3
+ from pathlib import Path
4
+
5
+ from pybind11.setup_helpers import Pybind11Extension, build_ext
6
+ from setuptools import setup
7
+
8
+
9
+ class Build(build_ext):
10
+ def run(self):
11
+ super().run()
12
+ binary = Path(self.get_ext_fullpath("step2torque._assembly"))
13
+ binary.with_name("_assembly_version.py").write_text(
14
+ "SHA256 = " + repr(hashlib.sha256(binary.read_bytes()).hexdigest()) + "\n"
15
+ )
16
+
17
+
18
+ threads = [] if sys.platform == "win32" else ["-pthread"]
19
+ optimize = ["/O2"] if sys.platform == "win32" else ["-O3"]
20
+ setup(
21
+ ext_modules=[
22
+ Pybind11Extension(
23
+ "step2torque._assembly",
24
+ ["src/step2torque/_assembly.cpp"],
25
+ cxx_std=17,
26
+ define_macros=[("MOTOR_THREADS", "1")],
27
+ extra_compile_args=threads + optimize,
28
+ extra_link_args=threads,
29
+ )
30
+ ],
31
+ cmdclass={"build_ext": Build},
32
+ )
@@ -0,0 +1,16 @@
1
+ """Load a STEP, configure its components, and solve without blocking.
2
+
3
+ motor = Motor("motor.step")
4
+ motor.components["coil"].make_coil(phase=1, turns=72, direction="+z")
5
+ motor.components["magnet"].make_magnet(direction="radial")
6
+ solution = motor.solve(current="optimized", power=5.5)
7
+ solution.wait()
8
+ print(solution.torque) # N m
9
+ """
10
+
11
+ from .model import Motor
12
+ from .solution import Solution, solve_position
13
+ from .visualization import Plane, Viewer
14
+
15
+ __all__ = ["Motor", "Solution", "solve_position", "Plane", "Viewer"]
16
+ __version__ = "0.1.0"
@@ -0,0 +1,161 @@
1
+ // Same affine P2, degree-6 iron quadrature and B(H) energy as _field.py.
2
+ #include <pybind11/pybind11.h>
3
+ #include <pybind11/numpy.h>
4
+ #include "_parallel.hpp"
5
+ #include <array>
6
+ #include <cmath>
7
+ #include <cstdint>
8
+ #include <stdexcept>
9
+ namespace py = pybind11;
10
+ using Real = py::array_t<double, py::array::c_style>;
11
+ using Int = py::array_t<int, py::array::c_style | py::array::forcecast>;
12
+ using Index = py::array_t<int64_t, py::array::c_style | py::array::forcecast>;
13
+ struct Curve {
14
+ std::vector<double> h,b,slope,area;
15
+ std::array<double,3> evaluate(double value) const {
16
+ int i=std::upper_bound(h.begin(),h.end(),value)-h.begin()-1;
17
+ i=std::max(0,std::min(i,int(h.size())-2));
18
+ double t=value-h[i];
19
+ return {b[i]+t*slope[i],slope[i],area[i]+t*(b[i]+.5*t*slope[i])};
20
+ }
21
+ };
22
+ class Assembly {
23
+ int n,ne,nq;
24
+ int64_t nnz;
25
+ std::vector<int64_t> ptr,entries,cell_ptr,cell_entries;
26
+ std::vector<int> col,dofs,curve;
27
+ std::vector<double> inverse,volume,reference,weights,points,source;
28
+ std::vector<double> local_matrix,local_load,local_energy;
29
+ std::vector<Curve> curves;
30
+ Parallel pool;
31
+ const double *potential=nullptr;
32
+ double *matrix=nullptr,*residual=nullptr;
33
+ double factor=1,floor=.1;
34
+ public:
35
+ Assembly(Index pointers,Int columns,Int indices,Int labels,Real jac,
36
+ Real volumes,Real shapes,Real quadrature,Real locations,
37
+ py::list laws,int threads)
38
+ : n(pointers.size()-1),ne(labels.size()),nq(quadrature.size()),nnz(columns.size()),
39
+ ptr(pointers.data(),pointers.data()+pointers.size()),
40
+ col(columns.data(),columns.data()+columns.size()),
41
+ dofs(indices.data(),indices.data()+indices.size()),
42
+ curve(labels.data(),labels.data()+labels.size()),
43
+ inverse(jac.data(),jac.data()+jac.size()),
44
+ volume(volumes.data(),volumes.data()+volumes.size()),
45
+ reference(shapes.data(),shapes.data()+shapes.size()),
46
+ weights(quadrature.data(),quadrature.data()+quadrature.size()),
47
+ points(locations.data(),locations.data()+locations.size()),
48
+ source(size_t(ne)*12),local_matrix(size_t(ne)*55),local_load(size_t(ne)*10),
49
+ local_energy(ne),pool(threads) {
50
+ if(indices.size()!=int64_t(ne)*10 || shapes.size()!=nq*30 || jac.size()!=int64_t(ne)*9)
51
+ throw std::runtime_error("Compiled assembly requires the affine P2 tetrahedron map");
52
+ for(auto record:laws) {
53
+ py::tuple pair=py::cast<py::tuple>(record);
54
+ Real h=py::cast<Real>(pair[0]),b=py::cast<Real>(pair[1]);
55
+ Curve c;
56
+ c.h.assign(h.data(),h.data()+h.size());c.b.assign(b.data(),b.data()+b.size());
57
+ c.slope.resize(h.size()-1);c.area.assign(h.size(),0);
58
+ for(int i=0;i<h.size()-1;++i) {
59
+ c.slope[i]=(c.b[i+1]-c.b[i])/(c.h[i+1]-c.h[i]);
60
+ c.area[i+1]=c.area[i]+.5*(c.b[i]+c.b[i+1])*(c.h[i+1]-c.h[i]);
61
+ }
62
+ curves.push_back(std::move(c));
63
+ }
64
+ entries.resize(size_t(ne)*100);cell_ptr.assign(n+1,0);cell_entries.resize(size_t(ne)*10);
65
+ for(int row:dofs) {
66
+ if(row<0 || row>=n)throw std::runtime_error("Invalid P2 degree of freedom");
67
+ ++cell_ptr[row+1];
68
+ }
69
+ for(int i=1;i<=n;++i)cell_ptr[i]+=cell_ptr[i-1];
70
+ auto cursor=cell_ptr;
71
+ for(int64_t i=0;i<int64_t(ne)*10;++i)cell_entries[cursor[dofs[i]]++]=i;
72
+ for(int e=0;e<ne;++e)for(int i=0;i<10;++i)for(int j=0;j<10;++j) {
73
+ int row=dofs[e*10+i],column=dofs[e*10+j];
74
+ auto it=std::lower_bound(col.begin()+ptr[row],col.begin()+ptr[row+1],column);
75
+ if(it==col.begin()+ptr[row+1] || *it!=column)
76
+ throw std::runtime_error("Tangent graph differs from finite element map");
77
+ entries[int64_t(e)*100+i*10+j]=it-col.begin();
78
+ }
79
+ }
80
+ void set_source(Real values) {
81
+ if(values.size()!=int64_t(ne)*12)throw std::runtime_error("Winding source differs from mesh");
82
+ std::copy(values.data(),values.data()+values.size(),source.begin());
83
+ }
84
+ void assemble(Real phi,double scale,double lower,Real values,Real load) {
85
+ potential=phi.data();factor=scale;floor=lower;
86
+ if(phi.size()!=n || values.size()!=nnz || load.size()!=n)
87
+ throw std::runtime_error("Tangent arrays differ from finite element map");
88
+ matrix=values.mutable_data();residual=load.mutable_data();
89
+ py::gil_scoped_release release;
90
+ integrate(true);
91
+ }
92
+ double energy(Real phi,double scale) {
93
+ potential=phi.data();factor=scale;
94
+ py::gil_scoped_release release;
95
+ return integrate(false);
96
+ }
97
+ private:
98
+ double integrate(bool assemble) {
99
+ if(assemble) {std::fill(matrix,matrix+nnz,0);std::fill(residual,residual+n,0);}
100
+ pool.run(ne,[&](int begin,int end,int){for(int e=begin;e<end;++e) {
101
+ const double *jac=inverse.data()+9*e,*hs=source.data()+12*e;
102
+ const int *ids=dofs.data()+10*e;
103
+ const Curve &law=curves[curve[e]];
104
+ double local[55]={},load[10]={},cell_energy=0;
105
+ double offset=law.evaluate(.0001)[2];
106
+ for(int q=0;q<nq;++q) {
107
+ const double *ref=reference.data()+30*q,*point=points.data()+3*q;
108
+ double gradient[10][3],h[3],s[3];
109
+ for(int c=0;c<3;++c) {
110
+ s[c]=factor*(hs[c]+point[0]*(hs[3+c]-hs[c])+point[1]*(hs[6+c]-hs[c])+point[2]*(hs[9+c]-hs[c]));
111
+ h[c]=s[c];
112
+ }
113
+ for(int i=0;i<10;++i) for(int c=0;c<3;++c) {
114
+ gradient[i][c]=ref[3*i]*jac[c]+ref[3*i+1]*jac[3+c]+ref[3*i+2]*jac[6+c];
115
+ h[c]-=potential[ids[i]]*gradient[i][c];
116
+ }
117
+ double norm2=h[0]*h[0]+h[1]*h[1]+h[2]*h[2]+1e-8;
118
+ double hn=std::sqrt(norm2),w=volume[e]*weights[q];
119
+ auto bh=law.evaluate(hn);
120
+ constexpr double mu0=1.2566370614359173e-6;
121
+ cell_energy+=w*(bh[2]-offset-.5*mu0*(s[0]*s[0]+s[1]*s[1]+s[2]*s[2]));
122
+ if(!assemble) continue;
123
+ double secant=bh[0]/hn;
124
+ double anisotropic=(std::max(bh[1],floor*secant)-secant)/norm2;
125
+ double hg[10];
126
+ for(int i=0;i<10;++i) {
127
+ hg[i]=h[0]*gradient[i][0]+h[1]*gradient[i][1]+h[2]*gradient[i][2];
128
+ load[i]+=w*secant*hg[i];
129
+ }
130
+ for(int i=0;i<10;++i) for(int j=0;j<=i;++j) {
131
+ double gram=gradient[i][0]*gradient[j][0]+gradient[i][1]*gradient[j][1]+gradient[i][2]*gradient[j][2];
132
+ local[i*(i+1)/2+j]+=w*(secant*gram+anisotropic*hg[i]*hg[j]);
133
+ }
134
+ }
135
+ local_energy[e]=cell_energy;
136
+ if(assemble) {
137
+ std::copy(local,local+55,local_matrix.begin()+e*55);
138
+ std::copy(load,load+10,local_load.begin()+e*10);
139
+ }
140
+ }});
141
+ if(assemble)pool.run(n,[&](int begin,int end,int){for(int row=begin;row<end;++row) {
142
+ for(int64_t k=cell_ptr[row];k<cell_ptr[row+1];++k) {
143
+ int64_t entry=cell_entries[k];int e=entry/10,i=entry%10;
144
+ residual[row]+=local_load[entry];
145
+ for(int j=0;j<10;++j) {
146
+ int a=std::max(i,j),b=std::min(i,j);
147
+ matrix[entries[int64_t(e)*100+i*10+j]]+=local_matrix[e*55+a*(a+1)/2+b];
148
+ }
149
+ }
150
+ }});
151
+ double energy=0;for(double value:local_energy)energy+=value;
152
+ return energy;
153
+ }
154
+ };
155
+ PYBIND11_MODULE(_assembly,m) {
156
+ py::class_<Assembly>(m,"Assembly")
157
+ .def(py::init<Index,Int,Int,Int,Real,Real,Real,Real,Real,py::list,int>())
158
+ .def("set_source",&Assembly::set_source)
159
+ .def("assemble",&Assembly::assemble)
160
+ .def("energy",&Assembly::energy);
161
+ }