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.
- step2torque-0.1.0/LICENSE +22 -0
- step2torque-0.1.0/PKG-INFO +87 -0
- step2torque-0.1.0/README.md +62 -0
- step2torque-0.1.0/pyproject.toml +39 -0
- step2torque-0.1.0/setup.cfg +4 -0
- step2torque-0.1.0/setup.py +32 -0
- step2torque-0.1.0/src/step2torque/__init__.py +16 -0
- step2torque-0.1.0/src/step2torque/_assembly.cpp +161 -0
- step2torque-0.1.0/src/step2torque/_contact.py +289 -0
- step2torque-0.1.0/src/step2torque/_cuda.py +110 -0
- step2torque-0.1.0/src/step2torque/_currents.py +98 -0
- step2torque-0.1.0/src/step2torque/_field.py +263 -0
- step2torque-0.1.0/src/step2torque/_kernel.py +117 -0
- step2torque-0.1.0/src/step2torque/_mesh.py +435 -0
- step2torque-0.1.0/src/step2torque/_motion.py +76 -0
- step2torque-0.1.0/src/step2torque/_parallel.hpp +59 -0
- step2torque-0.1.0/src/step2torque/_results.py +68 -0
- step2torque-0.1.0/src/step2torque/_source.py +175 -0
- step2torque-0.1.0/src/step2torque/_torque.py +402 -0
- step2torque-0.1.0/src/step2torque/_worker.py +345 -0
- step2torque-0.1.0/src/step2torque/materials/1j22.csv +8 -0
- step2torque-0.1.0/src/step2torque/materials/__init__.py +51 -0
- step2torque-0.1.0/src/step2torque/materials/dt4.csv +14 -0
- step2torque-0.1.0/src/step2torque/materials/kede_1j21.csv +11 -0
- step2torque-0.1.0/src/step2torque/materials/rotor.csv +14 -0
- step2torque-0.1.0/src/step2torque/materials/sources.json +15 -0
- step2torque-0.1.0/src/step2torque/model.py +271 -0
- step2torque-0.1.0/src/step2torque/solution.py +307 -0
- step2torque-0.1.0/src/step2torque/visualization/__init__.py +6 -0
- step2torque-0.1.0/src/step2torque/visualization/__main__.py +64 -0
- step2torque-0.1.0/src/step2torque/visualization/appearance.py +52 -0
- step2torque-0.1.0/src/step2torque/visualization/archive.py +177 -0
- step2torque-0.1.0/src/step2torque/visualization/html.py +43 -0
- step2torque-0.1.0/src/step2torque/visualization/lines.py +140 -0
- step2torque-0.1.0/src/step2torque/visualization/native.py +325 -0
- step2torque-0.1.0/src/step2torque/visualization/packed.py +122 -0
- step2torque-0.1.0/src/step2torque/visualization/plane.py +74 -0
- step2torque-0.1.0/src/step2torque/visualization/setup.py +290 -0
- step2torque-0.1.0/src/step2torque/visualization/viewer.html +42 -0
- step2torque-0.1.0/src/step2torque/visualization/viewer.js +3915 -0
- step2torque-0.1.0/src/step2torque/visualization/viewer.py +355 -0
- step2torque-0.1.0/src/step2torque.egg-info/PKG-INFO +87 -0
- step2torque-0.1.0/src/step2torque.egg-info/SOURCES.txt +47 -0
- step2torque-0.1.0/src/step2torque.egg-info/dependency_links.txt +1 -0
- step2torque-0.1.0/src/step2torque.egg-info/entry_points.txt +2 -0
- step2torque-0.1.0/src/step2torque.egg-info/requires.txt +15 -0
- step2torque-0.1.0/src/step2torque.egg-info/top_level.txt +1 -0
- step2torque-0.1.0/tests/test_contact.py +199 -0
- 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,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
|
+
}
|