cfsem 2.3.1__cp313-cp313-win_amd64.whl
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.
- cfsem/__init__.py +577 -0
- cfsem/_cfsem.cp313-win_amd64.pyd +0 -0
- cfsem/bindings.py +587 -0
- cfsem/py.typed +0 -0
- cfsem/types.py +7 -0
- cfsem-2.3.1.dist-info/METADATA +103 -0
- cfsem-2.3.1.dist-info/RECORD +9 -0
- cfsem-2.3.1.dist-info/WHEEL +4 -0
- cfsem-2.3.1.dist-info/licenses/LICENSE +21 -0
cfsem/__init__.py
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"""Quasi-steady electromagnetics calcs"""
|
|
2
|
+
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
from interpn import MulticubicRectilinear
|
|
7
|
+
|
|
8
|
+
from cfsem.types import Array3xN
|
|
9
|
+
|
|
10
|
+
from cfsem.bindings import (
|
|
11
|
+
flux_circular_filament,
|
|
12
|
+
flux_density_linear_filament,
|
|
13
|
+
flux_density_biot_savart,
|
|
14
|
+
flux_density_circular_filament,
|
|
15
|
+
gs_operator_order2,
|
|
16
|
+
gs_operator_order4,
|
|
17
|
+
inductance_piecewise_linear_filaments,
|
|
18
|
+
filament_helix_path,
|
|
19
|
+
rotate_filaments_about_path,
|
|
20
|
+
vector_potential_linear_filament,
|
|
21
|
+
vector_potential_circular_filament,
|
|
22
|
+
flux_density_circular_filament_cartesian,
|
|
23
|
+
mutual_inductance_circular_to_linear,
|
|
24
|
+
flux_density_dipole,
|
|
25
|
+
body_force_density_circular_filament_cartesian,
|
|
26
|
+
body_force_density_linear_filament,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from ._cfsem import ellipe, ellipk
|
|
30
|
+
|
|
31
|
+
MU_0 = 4.0 * np.pi * 1e-7 * (1.0 + 5.5e-10)
|
|
32
|
+
"""
|
|
33
|
+
Vacuum permeability with slight correction from latest measurements per NIST CODATA 2019 (SP-961),
|
|
34
|
+
https://www.physics.nist.gov/cuu/pdf/wall_2018.pdf .
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"flux_circular_filament",
|
|
39
|
+
"flux_density_biot_savart",
|
|
40
|
+
"flux_density_linear_filament",
|
|
41
|
+
"flux_density_circular_filament",
|
|
42
|
+
"gs_operator_order2",
|
|
43
|
+
"gs_operator_order4",
|
|
44
|
+
"filament_helix_path",
|
|
45
|
+
"inductance_piecewise_linear_filaments",
|
|
46
|
+
"self_inductance_piecewise_linear_filaments",
|
|
47
|
+
"mutual_inductance_piecewise_linear_filaments",
|
|
48
|
+
"flux_density_ideal_solenoid",
|
|
49
|
+
"self_inductance_lyle6",
|
|
50
|
+
"mutual_inductance_of_circular_filaments",
|
|
51
|
+
"mutual_inductance_of_cylindrical_coils",
|
|
52
|
+
"filament_coil",
|
|
53
|
+
"self_inductance_circular_ring_wien",
|
|
54
|
+
"self_inductance_annular_ring",
|
|
55
|
+
"self_inductance_distributed_axisymmetric_conductor",
|
|
56
|
+
"ellipe",
|
|
57
|
+
"ellipk",
|
|
58
|
+
"rotate_filaments_about_path",
|
|
59
|
+
"vector_potential_linear_filament",
|
|
60
|
+
"vector_potential_circular_filament",
|
|
61
|
+
"flux_density_circular_filament_cartesian",
|
|
62
|
+
"mutual_inductance_circular_to_linear",
|
|
63
|
+
"flux_density_dipole",
|
|
64
|
+
"body_force_density_circular_filament_cartesian",
|
|
65
|
+
"body_force_density_linear_filament",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def self_inductance_piecewise_linear_filaments(xyzp: Array3xN) -> float:
|
|
70
|
+
"""
|
|
71
|
+
Estimate the self-inductance of one piecewise-linear current filament.
|
|
72
|
+
|
|
73
|
+
Uses Neumann's Formula for the mutual inductance of arbitrary loops
|
|
74
|
+
for non-self-pairings, zeroes-out the contributions from self-pairings
|
|
75
|
+
to resolve the thin-filament self-inductance singularity, and replaces the
|
|
76
|
+
segment self-inductance term with an analytic value from [3].
|
|
77
|
+
|
|
78
|
+
Assumes:
|
|
79
|
+
|
|
80
|
+
* Thin, well-behaved filaments
|
|
81
|
+
* Uniform current distribution within segments
|
|
82
|
+
* Low frequency operation; no skin effect
|
|
83
|
+
(which would reduce the segment self-field term)
|
|
84
|
+
* Vacuum permeability everywhere
|
|
85
|
+
* Each filament has a constant current in all segments
|
|
86
|
+
(otherwise we need an inductance matrix)
|
|
87
|
+
|
|
88
|
+
References:
|
|
89
|
+
[1] “Inductance,” Wikipedia. Dec. 12, 2022. Accessed: Jan. 23, 2023. [Online].
|
|
90
|
+
Available: <https://en.wikipedia.org/w/index.php?title=Inductance>
|
|
91
|
+
|
|
92
|
+
[2] F. E. Neumann, “Allgemeine Gesetze der inducirten elektrischen Ströme,”
|
|
93
|
+
Jan. 1846, doi: [10.1002/andp.18461430103](https://doi.org/10.1002/andp.18461430103)
|
|
94
|
+
|
|
95
|
+
[3] R. Dengler, “Self inductance of a wire loop as a curve integral,”
|
|
96
|
+
AEM, vol. 5, no. 1, p. 1, Jan. 2016, doi: [10.7716/aem.v5i1.331](https://doi.org/10.7716/aem.v5i1.331)
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
xyzp: [m] 3xN point series describing the filament
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
[H] Scalar self-inductance
|
|
103
|
+
"""
|
|
104
|
+
# Indexing numpy arrays here produces some `Any`-type hints and strips the element type
|
|
105
|
+
# erroneously in the pyright output as of pyright 1.1.393.
|
|
106
|
+
x, y, z = xyzp # type: ignore
|
|
107
|
+
xyzfil = (x[:-1], y[:-1], z[:-1]) # type: ignore
|
|
108
|
+
dlxyzfil = (x[1:] - x[:-1], y[1:] - y[:-1], z[1:] - z[:-1]) # type: ignore
|
|
109
|
+
|
|
110
|
+
self_inductance = inductance_piecewise_linear_filaments(
|
|
111
|
+
xyzfil, # type: ignore
|
|
112
|
+
dlxyzfil, # type: ignore
|
|
113
|
+
xyzfil, # type: ignore
|
|
114
|
+
dlxyzfil, # type: ignore
|
|
115
|
+
True,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return self_inductance # [H]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def mutual_inductance_piecewise_linear_filaments(
|
|
122
|
+
xyz0: Array3xN,
|
|
123
|
+
xyz1: Array3xN,
|
|
124
|
+
) -> float:
|
|
125
|
+
"""
|
|
126
|
+
Estimate the mutual inductance between two piecewise-linear current filaments.
|
|
127
|
+
|
|
128
|
+
Uses Neumann's Formula for the mutual inductance of arbitrary loops, which is
|
|
129
|
+
originally from [2] and can be found in a more friendly format on wikipedia.
|
|
130
|
+
|
|
131
|
+
Assumes:
|
|
132
|
+
|
|
133
|
+
* Thin, well-behaved filaments
|
|
134
|
+
* Vacuum permeability everywhere
|
|
135
|
+
* Each filament has a constant current in all segments
|
|
136
|
+
(otherwise we need an inductance matrix)
|
|
137
|
+
* All segments between the two filaments are distinct; no identical pairs
|
|
138
|
+
|
|
139
|
+
References:
|
|
140
|
+
[1] “Inductance,” Wikipedia. Dec. 12, 2022. Accessed: Jan. 23, 2023. [Online].
|
|
141
|
+
Available: <https://en.wikipedia.org/w/index.php?title=Inductance>
|
|
142
|
+
|
|
143
|
+
[2] F. E. Neumann, “Allgemeine Gesetze der inducirten elektrischen Ströme,”
|
|
144
|
+
Jan. 1846, doi: [10.1002/andp.18461430103](https://doi.org/10.1002/andp.18461430103)
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
xyz0: [m] 3xN point series describing the first filament
|
|
148
|
+
xyz1: [m] 3xM point series describing the second filament
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
[H] Scalar mutual inductance between the two filaments
|
|
152
|
+
"""
|
|
153
|
+
# Indexing numpy arrays here produces some `Any`-type hints and strips the element type
|
|
154
|
+
# erroneously in the pyright output as of pyright 1.1.393.
|
|
155
|
+
x0, y0, z0 = xyz0 # type: ignore
|
|
156
|
+
xyzfil0 = (x0[:-1], y0[:-1], z0[:-1]) # type: ignore
|
|
157
|
+
dlxyzfil0 = (x0[1:] - x0[:-1], y0[1:] - y0[:-1], z0[1:] - z0[:-1]) # type: ignore
|
|
158
|
+
|
|
159
|
+
x1, y1, z1 = xyz1 # type: ignore
|
|
160
|
+
xyzfil1 = (x1[:-1], y1[:-1], z1[:-1]) # type: ignore
|
|
161
|
+
dlxyzfil1 = (x1[1:] - x1[:-1], y1[1:] - y1[:-1], z1[1:] - z1[:-1]) # type: ignore
|
|
162
|
+
|
|
163
|
+
inductance = inductance_piecewise_linear_filaments(
|
|
164
|
+
xyzfil0, # type: ignore
|
|
165
|
+
dlxyzfil0, # type: ignore
|
|
166
|
+
xyzfil1, # type: ignore
|
|
167
|
+
dlxyzfil1, # type: ignore
|
|
168
|
+
False,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return inductance # [H]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def flux_density_ideal_solenoid(
|
|
175
|
+
current: float, num_turns: float, length: float
|
|
176
|
+
) -> float:
|
|
177
|
+
"""
|
|
178
|
+
Axial B-field on centerline of an ideal (infinitely long) solenoid.
|
|
179
|
+
|
|
180
|
+
This calc converges reasonably well for coil L/D > 20.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
current: [A] solenoid current
|
|
184
|
+
num_turns: [#] number of conductor turns
|
|
185
|
+
length: [m] length of winding pack
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
[T] B-field on axis (in the direction aligned with the axis)
|
|
189
|
+
"""
|
|
190
|
+
b_on_axis = MU_0 * num_turns * current / length
|
|
191
|
+
return b_on_axis # [T]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def self_inductance_lyle6(r: float, dr: float, dz: float, n: float) -> float:
|
|
195
|
+
"""
|
|
196
|
+
Self-inductance of a cylindrically-symmetric coil of rectangular
|
|
197
|
+
cross-section, estimated to 6th order.
|
|
198
|
+
|
|
199
|
+
This estimate is viable up to an L/D of about 1.5, above which it
|
|
200
|
+
rapidly accumulates error and eventually produces negative values.
|
|
201
|
+
|
|
202
|
+
References:
|
|
203
|
+
[1] T. R. Lyle,
|
|
204
|
+
“IX. On the self-inductance of circular coils of rectangular section,”
|
|
205
|
+
Philosophical Transactions of the Royal Society of London.
|
|
206
|
+
Series A, Containing Papers of a Mathematical or Physical Character,
|
|
207
|
+
vol. 213, no. 497-508, pp. 421-435, Jan. 1914, doi: [10.1098/rsta.1914.0009](https://doi.org/10.1098/rsta.1914.0009)
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
r: [m] radius, coil center
|
|
211
|
+
dr: [m] radial width of coil
|
|
212
|
+
dz: [m] cylindrical height of coil
|
|
213
|
+
n: number of turns
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
[H] self-inductance
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
assert dr < 2.0 * r, "Axisymmetric coil edges can't extend to negative R"
|
|
220
|
+
assert dz / r <= 3.5, "Coil geometry outside domain of validity of Lyle's formula"
|
|
221
|
+
|
|
222
|
+
# Guarantee 64-bit floats needed for 6th-order shape term
|
|
223
|
+
a = np.float64(r)
|
|
224
|
+
b = np.float64(dz)
|
|
225
|
+
c = np.float64(dr)
|
|
226
|
+
|
|
227
|
+
# Build up reusable terms for calculation of shape parameter
|
|
228
|
+
d = np.sqrt(b**2 + c**2) # [m] diagonal length
|
|
229
|
+
u = ((b / c) ** 2) * 2 * np.log(d / b)
|
|
230
|
+
v = ((c / b) ** 2) * 2 * np.log(d / c)
|
|
231
|
+
w = (b / c) * np.arctan(c / b)
|
|
232
|
+
ww = (c / b) * np.arctan(b / c)
|
|
233
|
+
|
|
234
|
+
bd2 = (b / d) ** 2
|
|
235
|
+
cd2 = (c / d) ** 2
|
|
236
|
+
da2 = (d / a) ** 2
|
|
237
|
+
ml = np.log(8 * a / d)
|
|
238
|
+
|
|
239
|
+
f = (
|
|
240
|
+
ml
|
|
241
|
+
+ (1 + u + v - 8 * (w + ww)) / 12.0 # 0th order in d/a
|
|
242
|
+
+ (
|
|
243
|
+
da2
|
|
244
|
+
* (
|
|
245
|
+
cd2 * (221 + 60 * ml - 6 * v)
|
|
246
|
+
+ 3 * bd2 * (69 + 60 * ml + 10 * u - 64 * w)
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
/ 5760.0 # 2nd order
|
|
250
|
+
+ (
|
|
251
|
+
da2**2
|
|
252
|
+
* (
|
|
253
|
+
2 * cd2**2 * (5721 + 3080 * ml - 345 * v)
|
|
254
|
+
+ 5 * bd2 * cd2 * (407 + 5880 * ml + 6720 * u - 14336 * w)
|
|
255
|
+
- 10 * bd2**2 * (3659 + 2520 * ml + 805 * u - 6144 * w)
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
/ 2.58048e7 # 4th order
|
|
259
|
+
+ (
|
|
260
|
+
da2**3
|
|
261
|
+
* (
|
|
262
|
+
3 * cd2**3 * (4308631 + 86520 * ml - 10052 * v)
|
|
263
|
+
- 14 * bd2**2 * cd2 * (617423 + 289800 * ml + 579600 * u - 1474560 * w)
|
|
264
|
+
+ 21 * bd2**3 * (308779 + 63000 * ml + 43596 * u - 409600 * w)
|
|
265
|
+
+ 42 * bd2 * cd2**2 * (-8329 + 46200 * ml + 134400 * u - 172032 * w)
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
/ 1.73408256e10 # 6th order
|
|
269
|
+
) # [nondim] shape parameter
|
|
270
|
+
|
|
271
|
+
self_inductance = MU_0 * (n**2) * a * f
|
|
272
|
+
|
|
273
|
+
assert self_inductance >= 0.0, "Coil geometry outside domain of validity of Lyle's formula"
|
|
274
|
+
|
|
275
|
+
return self_inductance # [H]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def mutual_inductance_of_circular_filaments(
|
|
279
|
+
rzn1: NDArray, rzn2: NDArray, par: bool = True
|
|
280
|
+
) -> float:
|
|
281
|
+
"""
|
|
282
|
+
Analytic mutual inductance between a pair of ideal cylindrically-symmetric coaxial filaments.
|
|
283
|
+
|
|
284
|
+
This is equivalent to taking the flux produced by each circular filament from
|
|
285
|
+
either collection of filaments to the other. Mutual inductance is reflexive,
|
|
286
|
+
so the order of the inputs is not important.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
rzn1 (array): 3x1 array (r [m], z [m], n []) coordinates and number of turns
|
|
290
|
+
rzn2 (array): 3x1 array (r [m], z [m], n []) coordinates and number of turns
|
|
291
|
+
par: Whether to use CPU parallelism
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
float: [H] mutual inductance
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
m = mutual_inductance_of_cylindrical_coils(
|
|
298
|
+
rzn1.reshape((3, 1)), rzn2.reshape((3, 1)), par
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return m # [H]
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def mutual_inductance_of_cylindrical_coils(
|
|
305
|
+
f1: NDArray, f2: NDArray, par: bool = True
|
|
306
|
+
) -> float:
|
|
307
|
+
"""
|
|
308
|
+
Analytical mutual inductance between two coaxial collections of ideal filaments.
|
|
309
|
+
|
|
310
|
+
Each collection typically represents a discretized "real" cylindrically-symmetric
|
|
311
|
+
coil of rectangular cross-section, but could have any cross-section as long as it
|
|
312
|
+
maintains cylindrical symmetry.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
f1: 3 x N array of filament definitions like (r [m], z [m], n [])
|
|
316
|
+
f2: 3 x N array of filament definitions like (r [m], z [m], n [])
|
|
317
|
+
par: Whether to use CPU parallelism
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
[H] mutual inductance of the two discretized coils
|
|
321
|
+
"""
|
|
322
|
+
r1, z1, n1 = f1
|
|
323
|
+
r2, z2, n2 = f2
|
|
324
|
+
|
|
325
|
+
# Using n2 as the current per filament is equivalent to examining a 1A reference current,
|
|
326
|
+
# which gives us the flux per amp (inductance)
|
|
327
|
+
m = np.sum(
|
|
328
|
+
n1 * flux_circular_filament(n2, r2, z2, r1, z1, par)
|
|
329
|
+
) # [H] total mutual inductance
|
|
330
|
+
|
|
331
|
+
return m # [H]
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def filament_coil(
|
|
335
|
+
r: float, z: float, w: float, h: float, nt: float, nr: int, nz: int
|
|
336
|
+
) -> NDArray:
|
|
337
|
+
"""
|
|
338
|
+
Create an array of filaments from coil cross-section, evenly spaced
|
|
339
|
+
_inside_ the winding pack. No filaments are coincident with the coil surface.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
r: [m] radius, coil center
|
|
343
|
+
z: [m] axial position, coil center
|
|
344
|
+
w: [m] width of coil pack
|
|
345
|
+
h: [m] height of coil pack
|
|
346
|
+
nt: turns
|
|
347
|
+
nr: radial discretizations
|
|
348
|
+
nz: axial discretizations
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
(nr*nz) x 3, (r,z,n) of each filament
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
# Build a 2D mesh of points evenly spaced in the interior of the bounding rectangle
|
|
355
|
+
rs = np.linspace(r - w * (nr - 1) / nr / 2, r + w * (nr - 1) / nr / 2, nr) # [m]
|
|
356
|
+
zs = np.linspace(z - h * (nz - 1) / nz / 2, z + h * (nz - 1) / nz / 2, nz) # [m]
|
|
357
|
+
rmesh, zmesh = np.meshgrid(rs, zs, indexing="ij") # [m]
|
|
358
|
+
|
|
359
|
+
# Number of turns attributed to each point is not necessarily an integer
|
|
360
|
+
n = np.full_like(rmesh.flatten(), float(nt) / (nr * nz))
|
|
361
|
+
|
|
362
|
+
# Pack filament locations and number of turns into an array together
|
|
363
|
+
filaments = np.dstack([rmesh.flatten(), zmesh.flatten(), n]).reshape(nr * nz, 3)
|
|
364
|
+
|
|
365
|
+
return filaments # [m], [m], [dimensionless]
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def self_inductance_circular_ring_wien(
|
|
369
|
+
major_radius: NDArray, minor_radius: NDArray
|
|
370
|
+
) -> NDArray:
|
|
371
|
+
"""
|
|
372
|
+
Wien's formula for the self-inductance of a circular ring
|
|
373
|
+
with thin circular cross section.
|
|
374
|
+
|
|
375
|
+
Uses equation 7 from reference [1].
|
|
376
|
+
|
|
377
|
+
References:
|
|
378
|
+
[1] E Rosa and L Cohen, "On the Self-Inductance of Circles,"
|
|
379
|
+
Bulletin of the Bureau of Standards, 1908.
|
|
380
|
+
[Online]. Available:
|
|
381
|
+
<https://nvlpubs.nist.gov/nistpubs/bulletin/04/nbsbulletinv4n1p149_A2b.pdf>
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
major_radius: [m] Major radius of the ring.
|
|
385
|
+
minor_radius: [m] Radius of the ring's cross section.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
[H] self-inductance
|
|
389
|
+
"""
|
|
390
|
+
ar = major_radius / minor_radius # [], a / rho, dimensionless
|
|
391
|
+
ra2 = (minor_radius / major_radius) ** 2 # [], (rho / a)^2, dimensionless
|
|
392
|
+
# Equation 7 in Rosa & Cohen lacks the factor of 1e-7
|
|
393
|
+
# because it is not in SI base units.
|
|
394
|
+
self_inductance = (
|
|
395
|
+
MU_0 * major_radius * ((1 + 0.125 * ra2) * np.log(8 * ar) - 0.0083 * ra2 - 1.75)
|
|
396
|
+
) # [H]
|
|
397
|
+
return self_inductance # [H]
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def self_inductance_distributed_axisymmetric_conductor(
|
|
401
|
+
current: float,
|
|
402
|
+
grid: Tuple[NDArray, NDArray],
|
|
403
|
+
mesh: Tuple[NDArray, NDArray],
|
|
404
|
+
b_part: Tuple[NDArray, NDArray],
|
|
405
|
+
psi_part: NDArray,
|
|
406
|
+
mask: NDArray,
|
|
407
|
+
edge_path: Tuple[NDArray, NDArray],
|
|
408
|
+
) -> Tuple[float, float, float]:
|
|
409
|
+
"""
|
|
410
|
+
Calculation of a distributed conductor's self-inductance from two components:
|
|
411
|
+
|
|
412
|
+
* External inductance: the portion related to the poloidal flux exactly at the conductor edge
|
|
413
|
+
* Internal inductance: the portion related to the poloidal magnetic field inside the conductor,
|
|
414
|
+
where the filamentized method used for coils does not apply due to the parallel arrangement
|
|
415
|
+
and variable current density.
|
|
416
|
+
|
|
417
|
+
Note: the B-field and flux inputs are _not_ the total from all sources - they are only the
|
|
418
|
+
contribution from the distributed conductor under examination.
|
|
419
|
+
|
|
420
|
+
This calculation was developed for use with tokamak plasmas, but applies similarly to
|
|
421
|
+
other kinds of distributed axisymmetric conductor.
|
|
422
|
+
|
|
423
|
+
Assumptions:
|
|
424
|
+
|
|
425
|
+
* Cylindrically-symmetric, distributed, single-winding, contiguous conductor
|
|
426
|
+
* No high-magnetic-permeability materials in the vicinity
|
|
427
|
+
* Isopotential on the edge contour
|
|
428
|
+
* This is slightly less restrictive than isopotential on the section,
|
|
429
|
+
but notably does _not_ allow the calc to be used with, for example,
|
|
430
|
+
large, shell conductors where different regions are meaningfully
|
|
431
|
+
independent of each other.
|
|
432
|
+
* Conductor interior does not touch the edge of the computational domain
|
|
433
|
+
* At least one grid cell of padding is needed to support finite differences
|
|
434
|
+
|
|
435
|
+
References:
|
|
436
|
+
[1] S. Ejima, R. W. Callis, J. L. Luxon, R. D. Stambaugh, T. S. Taylor, and J. C. Wesley,
|
|
437
|
+
“Volt-second analysis and consumption in Doublet III plasmas,”
|
|
438
|
+
Nucl. Fusion, vol. 22, no. 10, pp. 1313-1319, Oct. 1982,
|
|
439
|
+
doi: [10.1088/0029-5515/22/10/006](https://doi.org/10.1088/0029-5515/22/10/006)
|
|
440
|
+
|
|
441
|
+
[2] J. A. Romero and J.-E. Contributors, “Plasma internal inductance dynamics in a tokamak,”
|
|
442
|
+
arXiv.org. Accessed: Dec. 21, 2023. [Online]. Available: https://arxiv.org/abs/1009.1984v1
|
|
443
|
+
doi: [10.1088/0029-5515/50/11/115002](https://doi.org/10.1088/0029-5515/50/11/115002)
|
|
444
|
+
|
|
445
|
+
[3] J. T. Wai and E. Kolemen, “GSPD: An algorithm for time-dependent tokamak equilibria design.”
|
|
446
|
+
arXiv, Jun. 22, 2023. Accessed: Sep. 15, 2023. [Online]. Available: https://arxiv.org/abs/2306.13163
|
|
447
|
+
doi: [10.48550/arXiv.2306.13163](https://doi.org/10.48550/arXiv.2306.13163)
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
current: [A] total toroidal current in this conductor
|
|
451
|
+
grid: [m] (1 X nr) grids of (R coords, Z coords)
|
|
452
|
+
mesh: [m] (nr X nz) meshgrids of (R coords, Z coords)
|
|
453
|
+
b_part: [T] (nr X nz) Flux density (R-component, Z-component) due to this conductor
|
|
454
|
+
psi_part: [V-s] or [T-m^2] (nr X nz) this conductor's poloidal flux field
|
|
455
|
+
mask: (nr X nz) positive mask of the conductor's interior region
|
|
456
|
+
edge_path: [m] (2 x N) closed (r, z) path along conductor edge
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
(Lt, Li, Le) Total, internal, and external self-inductance components
|
|
460
|
+
"""
|
|
461
|
+
# Unpack
|
|
462
|
+
rgrid, zgrid = grid # [m]
|
|
463
|
+
rmesh, zmesh = mesh # [m]
|
|
464
|
+
br, bz = b_part # [T]
|
|
465
|
+
|
|
466
|
+
# Set up
|
|
467
|
+
nr = rgrid.size
|
|
468
|
+
nz = zgrid.size
|
|
469
|
+
psi_interpolator = MulticubicRectilinear.new([rgrid, zgrid], psi_part.flatten())
|
|
470
|
+
|
|
471
|
+
# Same-length diffs assuming last grid cell is the same size
|
|
472
|
+
# as the previous one. In general the conductor can't touch the
|
|
473
|
+
# edge of the computational domain without breaking the solver,
|
|
474
|
+
# so it's ok to allow some potential error at the last index.
|
|
475
|
+
drmesh = np.zeros_like(rmesh) # [m]
|
|
476
|
+
drmesh[: nr - 1, :] = np.diff(rmesh, axis=0)
|
|
477
|
+
drmesh[-1, :] = drmesh[-2, :]
|
|
478
|
+
|
|
479
|
+
dzmesh = np.zeros_like(zmesh) # [m]
|
|
480
|
+
dzmesh[:, : nz - 1] = np.diff(zmesh, axis=1)
|
|
481
|
+
dzmesh[:, -1] = dzmesh[:, -2]
|
|
482
|
+
|
|
483
|
+
# Toroidal volume of each cell in the mesh.
|
|
484
|
+
#
|
|
485
|
+
# Ideally we'd use a two-sided difference for
|
|
486
|
+
# calculating dr and dz in order to properly handle cell volume
|
|
487
|
+
# for nonuniform grids, but (1) that's a lot of extra array handling
|
|
488
|
+
# for minimal real benefit and (2) the grid is almost always uniform
|
|
489
|
+
volmesh = 2.0 * np.pi * rmesh * drmesh * dzmesh # [m^3]
|
|
490
|
+
|
|
491
|
+
# Stored poloidal magnetic energy inside the conductor volume.
|
|
492
|
+
#
|
|
493
|
+
# Because E ~ B^2 and B = sqrt(Br^2 + Bz^2 + Btor^2) -> B^2 = Br^2 + Bz^2 + Btor^2,
|
|
494
|
+
# the components of magnetic energy (and induction) due to a current on different axes
|
|
495
|
+
# are separable, and we can ignore the toroidal field entirely when treating the
|
|
496
|
+
# poloidal inductance.
|
|
497
|
+
#
|
|
498
|
+
# That doesn't mean there isn't store energy related to the conductor's toroidal field,
|
|
499
|
+
# only that it can be separated from the poloidal inductance.
|
|
500
|
+
wmag_pol = (1.0 / (2.0 * MU_0)) * np.sum((br**2 + bz**2) * volmesh * mask) # [J]
|
|
501
|
+
|
|
502
|
+
# Internal inductance.
|
|
503
|
+
#
|
|
504
|
+
# Because E = (1/2) * L * I^2, we can superpose multiple inductance terms
|
|
505
|
+
# for a given current-carrying element, and say E = (1/2) * (Li + Le) * I^2 .
|
|
506
|
+
#
|
|
507
|
+
# Now that we have the internal stored magnetic energy, we can calculate
|
|
508
|
+
# the part of the self-inductance related to that energy directly.
|
|
509
|
+
internal_inductance = 2.0 * wmag_pol / current**2 # [H]
|
|
510
|
+
|
|
511
|
+
# External inductance.
|
|
512
|
+
#
|
|
513
|
+
# This is a hack based on Poynting's Theorem relating surface conditions to
|
|
514
|
+
# magnetic energy.
|
|
515
|
+
#
|
|
516
|
+
# Do weighted average of flux along the edge contour
|
|
517
|
+
# to adjust for the length of individual segments
|
|
518
|
+
edge_dr = np.diff(edge_path[0]) # [m]
|
|
519
|
+
edge_dz = np.diff(edge_path[1]) # [m]
|
|
520
|
+
edge_dl = (edge_dr**2 + edge_dz**2) ** 0.5 # [m] length of each segment
|
|
521
|
+
edge_length = np.sum(edge_dl) # [m] total length of conductor limit contour
|
|
522
|
+
edge_psi = psi_interpolator.eval([x[:-1] for x in edge_path]) # [V-s]
|
|
523
|
+
edge_psi_mean = np.sum(edge_psi * edge_dl) / edge_length # [V-s]
|
|
524
|
+
# Take the inductance
|
|
525
|
+
external_inductance = edge_psi_mean / current # [H]
|
|
526
|
+
|
|
527
|
+
# Total inductance.
|
|
528
|
+
total_inductance = internal_inductance + external_inductance # [H]
|
|
529
|
+
|
|
530
|
+
return (
|
|
531
|
+
float(total_inductance),
|
|
532
|
+
float(internal_inductance),
|
|
533
|
+
float(external_inductance),
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def self_inductance_annular_ring(r: float, a: float, b: float) -> float:
|
|
538
|
+
"""
|
|
539
|
+
Low-frequency self-inductance of a thick-walled tube bent in a circle.
|
|
540
|
+
|
|
541
|
+
Uses Wien's method, per the 1912 NIST handbook [1], Eqn. 64 on pg. 112,
|
|
542
|
+
with a correction to a misprint in term 5 and a unit conversion factor
|
|
543
|
+
in the final expression.
|
|
544
|
+
|
|
545
|
+
This is an approximation that drops terms of order higher than `(a/r)^2`
|
|
546
|
+
and `(b/r)^2`.
|
|
547
|
+
|
|
548
|
+
References:
|
|
549
|
+
[1] E. B. Rosa and F. W. Grover, “Formulas and tables for the calculation of mutual and self-inductance (Revised),”
|
|
550
|
+
BULL. NATL. BUR. STAND., vol. 8, no. 1, p. 1, Jan. 1912,
|
|
551
|
+
doi: [10.6028/bulletin.185](https://doi.org/10.6028/bulletin.185)
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
r: [m] major radius of loop
|
|
555
|
+
a: [m] inner minor radius (tube inside radius)
|
|
556
|
+
b: [m] outer minor radius (tube outside radius)
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
[H] self-inductance [H]
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
if not ((r > 0.0) and (a > 0.0) and (b > 0.0)):
|
|
563
|
+
raise ValueError("All radii must be positive and nonzero.")
|
|
564
|
+
if not (a < b):
|
|
565
|
+
raise ValueError("Inner minor radius must be less than outer minor radius.")
|
|
566
|
+
if not (b < r):
|
|
567
|
+
raise ValueError("Outer minor radius must be less than major radius.")
|
|
568
|
+
|
|
569
|
+
term1 = (1.0 + (a**2 + b**2) / (8.0 * r**2)) * np.log(8.0 * r / b)
|
|
570
|
+
term2 = -1.75 + (2.0 * b**2 + a**2) / (32.0 * r**2)
|
|
571
|
+
term3 = -0.5 * a**2 / (b**2 - a**2)
|
|
572
|
+
term4 = (a**4 / ((b**2 - a**2) ** 2)) * (1.0 + a**2 / (8.0 * r**2)) * np.log(b / a)
|
|
573
|
+
term5 = -(a**4 + a**2 * b**2 + b**4) / (48.0 * r**2 * (b**2 - a**2))
|
|
574
|
+
|
|
575
|
+
self_inductance = MU_0 * r * (term1 + term2 + term3 + term4 + term5) # [H]
|
|
576
|
+
|
|
577
|
+
return self_inductance # [H]
|
|
Binary file
|
cfsem/bindings.py
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Symmetric bindings for backend calcs.
|
|
3
|
+
|
|
4
|
+
This fulfills the function of typing stubs, while also guaranteeing arrays are
|
|
5
|
+
passed as contiguous and reallocating into contiguous inputs if necessary.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from cfsem.types import Array3xN
|
|
9
|
+
|
|
10
|
+
from numpy import ascontiguousarray, float64, zeros_like
|
|
11
|
+
from numpy.typing import NDArray
|
|
12
|
+
|
|
13
|
+
from ._cfsem import flux_circular_filament as em_flux_circular_filament
|
|
14
|
+
from ._cfsem import flux_density_linear_filament as em_flux_density_linear_filament
|
|
15
|
+
from ._cfsem import flux_density_circular_filament as em_flux_density_circular_filament
|
|
16
|
+
from ._cfsem import (
|
|
17
|
+
flux_density_circular_filament_cartesian as em_flux_density_circular_filament_cartesian,
|
|
18
|
+
)
|
|
19
|
+
from ._cfsem import (
|
|
20
|
+
mutual_inductance_circular_to_linear as em_mutual_inductance_circular_to_linear,
|
|
21
|
+
)
|
|
22
|
+
from ._cfsem import (
|
|
23
|
+
body_force_density_circular_filament_cartesian as em_body_force_density_circular_filament_cartesian,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from ._cfsem import gs_operator_order2 as em_gs_operator_order2
|
|
27
|
+
from ._cfsem import gs_operator_order4 as em_gs_operator_order4
|
|
28
|
+
from ._cfsem import (
|
|
29
|
+
inductance_piecewise_linear_filaments as em_inductance_piecewise_linear_filaments,
|
|
30
|
+
)
|
|
31
|
+
from ._cfsem import filament_helix_path as em_filament_helix_path
|
|
32
|
+
from ._cfsem import rotate_filaments_about_path as em_rotate_filaments_about_path
|
|
33
|
+
from ._cfsem import (
|
|
34
|
+
vector_potential_circular_filament as em_vector_potential_circular_filament,
|
|
35
|
+
)
|
|
36
|
+
from ._cfsem import (
|
|
37
|
+
vector_potential_linear_filament as em_vector_potential_linear_filament,
|
|
38
|
+
)
|
|
39
|
+
from ._cfsem import (
|
|
40
|
+
body_force_density_linear_filament as em_body_force_density_linear_filament,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
from ._cfsem import flux_density_dipole as em_flux_density_dipole
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def flux_circular_filament(
|
|
47
|
+
ifil: NDArray[float64],
|
|
48
|
+
rfil: NDArray[float64],
|
|
49
|
+
zfil: NDArray[float64],
|
|
50
|
+
rprime: NDArray[float64],
|
|
51
|
+
zprime: NDArray[float64],
|
|
52
|
+
par: bool = True,
|
|
53
|
+
) -> NDArray[float64]:
|
|
54
|
+
"""
|
|
55
|
+
Flux contributions from some circular filaments to some observation points,
|
|
56
|
+
which happens to be the Green's function for the Grad-Shafranov solve.
|
|
57
|
+
|
|
58
|
+
This represents the integral of $\\vec{B} \\cdot \\hat{n} \\, dA$ from the z-axis to each
|
|
59
|
+
(`rprime`, `zprime`) observation location with $\\hat{n}$ oriented parallel to the z-axis.
|
|
60
|
+
|
|
61
|
+
A convenient interpretation of the flux is as the mutual inductance
|
|
62
|
+
between a circular filament at (`rfil`, `zfil`) and a second circular
|
|
63
|
+
filament at (`rprime`, `zprime`); this can be used to get the mutual inductance
|
|
64
|
+
between two filamentized coils as the sum of flux contributions between each coil's filaments.
|
|
65
|
+
Because mutual inductance is reflexive, the order of the coils can be reversed and
|
|
66
|
+
the same result is obtained.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
ifil: [A] filament current
|
|
70
|
+
rfil: [m] filament R-coord
|
|
71
|
+
zfil: [m] filament Z-coord
|
|
72
|
+
rprime: [m] Observation point R-coord
|
|
73
|
+
zprime: [m] Observation point Z-coord
|
|
74
|
+
par: Whether to use CPU parallelism
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
[Wb] or [T-m^2] or [V-s] psi, poloidal flux at each observation point
|
|
78
|
+
"""
|
|
79
|
+
ifil, rfil, zfil = _3tup_contig((ifil, rfil, zfil))
|
|
80
|
+
rprime, zprime = _2tup_contig((rprime, zprime))
|
|
81
|
+
psi = em_flux_circular_filament(ifil, rfil, zfil, rprime, zprime, par)
|
|
82
|
+
return psi # [Wb] or [T-m^2] or [V-s]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def vector_potential_circular_filament(
|
|
86
|
+
ifil: NDArray[float64],
|
|
87
|
+
rfil: NDArray[float64],
|
|
88
|
+
zfil: NDArray[float64],
|
|
89
|
+
rprime: NDArray[float64],
|
|
90
|
+
zprime: NDArray[float64],
|
|
91
|
+
par: bool = True,
|
|
92
|
+
) -> NDArray[float64]:
|
|
93
|
+
"""
|
|
94
|
+
Vector potential contributions from some circular filaments to some observation points.
|
|
95
|
+
Off-axis A_phi component for a circular current filament in vacuum.
|
|
96
|
+
|
|
97
|
+
The vector potential of a loop has zero r- and z- components due to symmetry,
|
|
98
|
+
and does not vary in the phi-direction.
|
|
99
|
+
|
|
100
|
+
Note that to recover the B-field as the curl of A, the curl operator for cylindrical
|
|
101
|
+
coordinates must be used with the output of this function incorporated into a full
|
|
102
|
+
3D A-field like [A_r, A_phi, A_z].
|
|
103
|
+
|
|
104
|
+
References:
|
|
105
|
+
[1] J. C. Simpson, J. E. Lane, C. D. Immer, R. C. Youngquist, and T. Steinrock,
|
|
106
|
+
“Simple Analytic Expressions for the Magnetic Field of a Circular Current Loop,”
|
|
107
|
+
Jan. 01, 2001. Accessed: Sep. 06, 2022. [Online]. Available: <https://ntrs.nasa.gov/citations/20010038494>
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
ifil: [A] filament current
|
|
111
|
+
rfil: [m] filament R-coord
|
|
112
|
+
zfil: [m] filament Z-coord
|
|
113
|
+
rprime: [m] Observation point R-coord
|
|
114
|
+
zprime: [m] Observation point Z-coord
|
|
115
|
+
par: Whether to use CPU parallelism
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
[Wb/m] or [V-s/m] a_phi, vector potential in the toroidal direction
|
|
119
|
+
"""
|
|
120
|
+
ifil, rfil, zfil = _3tup_contig((ifil, rfil, zfil))
|
|
121
|
+
rprime, zprime = _2tup_contig((rprime, zprime))
|
|
122
|
+
a_phi = em_vector_potential_circular_filament(ifil, rfil, zfil, rprime, zprime, par)
|
|
123
|
+
return a_phi # [Wb/m] or [V-s/m]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def flux_density_circular_filament(
|
|
127
|
+
ifil: NDArray[float64],
|
|
128
|
+
rfil: NDArray[float64],
|
|
129
|
+
zfil: NDArray[float64],
|
|
130
|
+
rprime: NDArray[float64],
|
|
131
|
+
zprime: NDArray[float64],
|
|
132
|
+
par: bool = True,
|
|
133
|
+
) -> tuple[NDArray[float64], NDArray[float64]]:
|
|
134
|
+
"""
|
|
135
|
+
Off-axis Br,Bz components for a circular current filament in vacuum.
|
|
136
|
+
|
|
137
|
+
Near-exact formula (except numerically-evaluated elliptic integrals)
|
|
138
|
+
See eqns. 12, 13 pg. 34 in [1], eqn 9.8.7 in [2], and all of [3].
|
|
139
|
+
|
|
140
|
+
Note the formula for Br as given by [1] is incorrect and does not satisfy the
|
|
141
|
+
constraints of the calculation without correcting by a factor of ($z / r$).
|
|
142
|
+
|
|
143
|
+
References:
|
|
144
|
+
[1] D. B. Montgomery and J. Terrell,
|
|
145
|
+
“Some Useful Information For The Design Of Aircore Solenoids,
|
|
146
|
+
Part I. Relationships Between Magnetic Field, Power, Ampere-Turns
|
|
147
|
+
And Current Density. Part II. Homogeneous Magnetic Fields,”
|
|
148
|
+
Massachusetts Inst. Of Tech. Francis Bitter National Magnet Lab, Cambridge, MA,
|
|
149
|
+
Nov. 1961. Accessed: May 18, 2021. [Online].
|
|
150
|
+
Available: <https://apps.dtic.mil/sti/citations/tr/AD0269073>
|
|
151
|
+
|
|
152
|
+
[2] 8.02 Course Notes. Available:
|
|
153
|
+
<https://web.mit.edu/8.02t/www/802TEAL3D/visualizations/coursenotes/modules/guide09.pdf>
|
|
154
|
+
|
|
155
|
+
[3] Eric Dennyson, "Magnet Formulas". Available:
|
|
156
|
+
<https://tiggerntatie.github.io/emagnet-py/offaxis/off_axis_loop.html>
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
ifil: [A] filament current
|
|
160
|
+
rfil: [m] filament R-coord
|
|
161
|
+
zfil: [m] filament Z-coord
|
|
162
|
+
rprime: [m] Observation point R-coord
|
|
163
|
+
zprime: [m] Observation point Z-coord
|
|
164
|
+
par: Whether to use CPU parallelism
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
[T] (Br, Bz) flux density components
|
|
168
|
+
"""
|
|
169
|
+
ifil, rfil, zfil = _3tup_contig((ifil, rfil, zfil))
|
|
170
|
+
rprime, zprime = _2tup_contig((rprime, zprime))
|
|
171
|
+
br, bz = em_flux_density_circular_filament(ifil, rfil, zfil, rprime, zprime, par)
|
|
172
|
+
return br, bz # [T]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def flux_density_linear_filament(
|
|
176
|
+
xyzp: Array3xN,
|
|
177
|
+
xyzfil: Array3xN,
|
|
178
|
+
dlxyzfil: Array3xN,
|
|
179
|
+
ifil: NDArray[float64],
|
|
180
|
+
par: bool = True,
|
|
181
|
+
) -> Array3xN:
|
|
182
|
+
"""
|
|
183
|
+
Biot-Savart law calculation for B-field contributions from many filament segments
|
|
184
|
+
to many observation points.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
xyzp: [m] x,y,z coords of observation points
|
|
188
|
+
xyzfil: [m] x,y,z coords of current filament origins (start of segment)
|
|
189
|
+
dlxyzfil: [m] x,y,z length delta of current filaments
|
|
190
|
+
ifil: [A] current in each filament segment
|
|
191
|
+
par: Whether to use CPU parallelism
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
[T] (Bx, By, Bz) magnetic flux density at observation points
|
|
195
|
+
"""
|
|
196
|
+
xyzp = _3tup_contig(xyzp)
|
|
197
|
+
xyzfil = _3tup_contig(xyzfil)
|
|
198
|
+
dlxyzfil = _3tup_contig(dlxyzfil)
|
|
199
|
+
ifil = ascontiguousarray(ifil).flatten()
|
|
200
|
+
return em_flux_density_linear_filament(xyzp, xyzfil, dlxyzfil, ifil, par)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
flux_density_biot_savart = flux_density_linear_filament # For backwards-compatibility
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def vector_potential_linear_filament(
|
|
207
|
+
xyzp: Array3xN,
|
|
208
|
+
xyzfil: Array3xN,
|
|
209
|
+
dlxyzfil: Array3xN,
|
|
210
|
+
ifil: NDArray[float64],
|
|
211
|
+
par: bool = True,
|
|
212
|
+
) -> Array3xN:
|
|
213
|
+
"""
|
|
214
|
+
Vector potential calculation for A-field contribution from many current filament
|
|
215
|
+
segments to many observation points.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
xyzp: [m] x,y,z coords of observation points
|
|
219
|
+
xyzfil: [m] x,y,z coords of current filament origins (start of segment)
|
|
220
|
+
dlxyzfil: [m] x,y,z length delta of current filaments
|
|
221
|
+
ifil: [A] current in each filament segment
|
|
222
|
+
par: Whether to use CPU parallelism
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
[Wb/m] or [V-s/m] (Ax, Ay, Az) magnetic vector potential at observation points
|
|
226
|
+
"""
|
|
227
|
+
xyzp = _3tup_contig(xyzp)
|
|
228
|
+
xyzfil = _3tup_contig(xyzfil)
|
|
229
|
+
dlxyzfil = _3tup_contig(dlxyzfil)
|
|
230
|
+
ifil = ascontiguousarray(ifil).flatten()
|
|
231
|
+
return em_vector_potential_linear_filament(xyzp, xyzfil, dlxyzfil, ifil, par)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def inductance_piecewise_linear_filaments(
|
|
235
|
+
xyzfil0: Array3xN,
|
|
236
|
+
dlxyzfil0: Array3xN,
|
|
237
|
+
xyzfil1: Array3xN,
|
|
238
|
+
dlxyzfil1: Array3xN,
|
|
239
|
+
self_inductance: bool = False,
|
|
240
|
+
) -> float:
|
|
241
|
+
"""
|
|
242
|
+
Estimate the mutual inductance between two piecewise-linear current filaments,
|
|
243
|
+
or estimate self-inductance by passing the same filaments twice and setting
|
|
244
|
+
`self_inductance = True`.
|
|
245
|
+
|
|
246
|
+
It may be easier to use wrappers of this function that are specialized for self- and mutual-inductance
|
|
247
|
+
calculations:
|
|
248
|
+
[`self_inductance_piecewise_linear_filaments`][cfsem.self_inductance_piecewise_linear_filaments]
|
|
249
|
+
and [`mutual_inductance_piecewise_linear_filaments`][cfsem.mutual_inductance_piecewise_linear_filaments].
|
|
250
|
+
|
|
251
|
+
Uses Neumann's Formula for the mutual inductance of arbitrary loops, which is
|
|
252
|
+
originally from [2] and can be found in a more friendly format on wikipedia.
|
|
253
|
+
|
|
254
|
+
When self_inductance flag is set, zeroes-out the contributions from self-pairings
|
|
255
|
+
to resolve the thin-filament self-inductance singularity and replaces the
|
|
256
|
+
segment self-inductance term with an analytic value from [3].
|
|
257
|
+
|
|
258
|
+
Assumes:
|
|
259
|
+
|
|
260
|
+
* Thin, well-behaved filaments
|
|
261
|
+
* Uniform current distribution within segments
|
|
262
|
+
* Low frequency operation; no skin effect
|
|
263
|
+
(which would reduce the segment self-field term)
|
|
264
|
+
* Vacuum permeability everywhere
|
|
265
|
+
* Each filament has a constant current in all segments
|
|
266
|
+
(otherwise we need an inductance matrix)
|
|
267
|
+
|
|
268
|
+
References:
|
|
269
|
+
[1] “Inductance,” Wikipedia. Dec. 12, 2022. Accessed: Jan. 23, 2023. [Online].
|
|
270
|
+
Available: <https://en.wikipedia.org/w/index.php?title=Inductance>
|
|
271
|
+
|
|
272
|
+
[2] F. E. Neumann, “Allgemeine Gesetze der inducirten elektrischen Ströme,”
|
|
273
|
+
Jan. 1846, doi: [10.1002/andp.18461430103](https://doi.org/10.1002/andp.18461430103)
|
|
274
|
+
|
|
275
|
+
[3] R. Dengler, “Self inductance of a wire loop as a curve integral,”
|
|
276
|
+
AEM, vol. 5, no. 1, p. 1, Jan. 2016, doi: [10.7716/aem.v5i1.331](https://doi.org/10.7716/aem.v5i1.331)
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
xyzfil0: [m] Nx3 point series describing the filament origins
|
|
280
|
+
dlxyzfil0: [m] Nx3 length vector of each filament
|
|
281
|
+
xyzfil1: [m] Nx3 point series describing the filament origins
|
|
282
|
+
dlxyzfil1: [m] Nx3 length vector of each filament
|
|
283
|
+
self_inductance: Whether this is being used as a self-inductance calc
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
[H] Scalar inductance
|
|
287
|
+
"""
|
|
288
|
+
xyzfil0 = _3tup_contig(xyzfil0)
|
|
289
|
+
dlxyzfil0 = _3tup_contig(dlxyzfil0)
|
|
290
|
+
xyzfil1 = _3tup_contig(xyzfil1)
|
|
291
|
+
dlxyzfil1 = _3tup_contig(dlxyzfil1)
|
|
292
|
+
|
|
293
|
+
return em_inductance_piecewise_linear_filaments(
|
|
294
|
+
xyzfil0, dlxyzfil0, xyzfil1, dlxyzfil1, self_inductance
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def gs_operator_order2(rs: NDArray[float64], zs: NDArray[float64]) -> Array3xN:
|
|
299
|
+
"""Build second-order Grad-Shafranov operator in triplet format.
|
|
300
|
+
Assumes regular grid spacing.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
rs: [m] r-coordinates of finite difference grid
|
|
304
|
+
zs: [m] z-coordinates of finite difference grid
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Differential operator as triplet format sparse matrix
|
|
308
|
+
"""
|
|
309
|
+
rs, zs = _2tup_contig((rs, zs))
|
|
310
|
+
return em_gs_operator_order2(rs, zs)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def gs_operator_order4(rs: NDArray[float64], zs: NDArray[float64]) -> Array3xN:
|
|
314
|
+
"""
|
|
315
|
+
Build fourth-order Grad-Shafranov operator in triplet format.
|
|
316
|
+
Assumes regular grid spacing.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
rs: [m] r-coordinates of finite difference grid
|
|
320
|
+
zs: [m] z-coordinates of finite difference grid
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Differential operator as triplet format sparse matrix
|
|
324
|
+
"""
|
|
325
|
+
rs, zs = _2tup_contig((rs, zs))
|
|
326
|
+
return em_gs_operator_order4(rs, zs)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def filament_helix_path(
|
|
330
|
+
path: Array3xN,
|
|
331
|
+
helix_start_offset: tuple[float, float, float],
|
|
332
|
+
twist_pitch: float,
|
|
333
|
+
angle_offset: float,
|
|
334
|
+
) -> NDArray[float64]:
|
|
335
|
+
"""
|
|
336
|
+
Filamentize a helix about an arbitrary piecewise-linear path.
|
|
337
|
+
|
|
338
|
+
Assumes angle between sequential path segments is small and will fail
|
|
339
|
+
if that angle approaches or exceeds 90 degrees.
|
|
340
|
+
|
|
341
|
+
The helix initial position vector, helix_start_offset, must be in a plane normal to
|
|
342
|
+
the first path segment in order to produce good results. If it is not in-plane,
|
|
343
|
+
it will be projected on to that plane and then scaled to the magnitude of its
|
|
344
|
+
original length s.t. the distance from the helix to the path center is preserved
|
|
345
|
+
but its orientation is not.
|
|
346
|
+
|
|
347
|
+
Description of the method:
|
|
348
|
+
|
|
349
|
+
1. Translate [filament segment n-1] to the base of [path segment n]
|
|
350
|
+
and call it [filament segment n]
|
|
351
|
+
2. Take cross product of [path segment n] with [path segment n-1]
|
|
352
|
+
3. Rotate [filament segment n] segment about the axis of that cross product
|
|
353
|
+
to bring it into the plane defined by [path segment n] as a normal vector
|
|
354
|
+
4. Rotate [filament seg. n] about [path seg. n] to continue the helix orbit
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
path: [m] 3xN Centerline points
|
|
358
|
+
helix_start_offset: [m] (3x1) Initial position of helix rel. to centerline path
|
|
359
|
+
twist_pitch: [m] (scalar) Centerline length per helix orbit
|
|
360
|
+
angle_offset: [rad] (scalar) Initial rotation offset about centerline
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
[m] 3xN array of points on the helix that twists around the path
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
# Make sure input is contiguous, reallocating only if necessary
|
|
367
|
+
path = ascontiguousarray(path)
|
|
368
|
+
|
|
369
|
+
# Allocate output
|
|
370
|
+
helix = zeros_like(path) # [m]
|
|
371
|
+
|
|
372
|
+
# Calculate, mutating output
|
|
373
|
+
em_filament_helix_path(
|
|
374
|
+
(*path,),
|
|
375
|
+
helix_start_offset,
|
|
376
|
+
twist_pitch,
|
|
377
|
+
angle_offset,
|
|
378
|
+
(*helix,),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
return helix # [m]
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def rotate_filaments_about_path(
|
|
385
|
+
path: Array3xN, angle_offset: float, fils: Array3xN
|
|
386
|
+
) -> NDArray[float64]:
|
|
387
|
+
"""
|
|
388
|
+
Rotate a path of point about another path.
|
|
389
|
+
|
|
390
|
+
Intended for rotating a helix generated by [`filament_helix_path`][cfsem.filament_helix_path]
|
|
391
|
+
about the centerline that was used to generate it.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
path: [m] x,y,z Centerline points
|
|
395
|
+
angle_offset: [rad] (scalar) Initial rotation offset about centerline
|
|
396
|
+
fils: [m] x,y,z Filaments to rotate around centerline
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
[m] 3xN array of points on the helix that twists around the path
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
# Make sure input is contiguous, reallocating only if necessary
|
|
403
|
+
path = ascontiguousarray(path)
|
|
404
|
+
|
|
405
|
+
new_fils = ascontiguousarray(fils).copy()
|
|
406
|
+
|
|
407
|
+
em_rotate_filaments_about_path(
|
|
408
|
+
(*path,),
|
|
409
|
+
angle_offset,
|
|
410
|
+
(*new_fils,),
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
return new_fils # [m]
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def flux_density_circular_filament_cartesian(
|
|
417
|
+
ifil: NDArray[float64],
|
|
418
|
+
rfil: NDArray[float64],
|
|
419
|
+
zfil: NDArray[float64],
|
|
420
|
+
xyzp: Array3xN,
|
|
421
|
+
par: bool = True,
|
|
422
|
+
) -> NDArray[float64]:
|
|
423
|
+
"""
|
|
424
|
+
Flux density of a circular filament in cartesian form
|
|
425
|
+
at a set of locations given in cartesian coordinates.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
ifil: [A] filament current
|
|
429
|
+
rfil: [m] filament R-coord
|
|
430
|
+
zfil: [m] filament Z-coord
|
|
431
|
+
xyzp: [m] x,y,z coords of observation points
|
|
432
|
+
par: Whether to use CPU parallelism
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
[T] flux density
|
|
436
|
+
"""
|
|
437
|
+
ifil, rfil, zfil = _3tup_contig((ifil, rfil, zfil))
|
|
438
|
+
xyzp = _3tup_contig(xyzp)
|
|
439
|
+
bx, by, bz = em_flux_density_circular_filament_cartesian(
|
|
440
|
+
ifil, rfil, zfil, xyzp, par
|
|
441
|
+
) # [T]
|
|
442
|
+
|
|
443
|
+
return bx, by, bz # type: ignore
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def mutual_inductance_circular_to_linear(
|
|
447
|
+
rfil: NDArray[float64],
|
|
448
|
+
zfil: NDArray[float64],
|
|
449
|
+
nfil: NDArray[float64],
|
|
450
|
+
xyzfil: Array3xN,
|
|
451
|
+
dlxyzfil: Array3xN,
|
|
452
|
+
par: bool = True,
|
|
453
|
+
) -> NDArray[float64]:
|
|
454
|
+
"""
|
|
455
|
+
Mutual inductance between a collection of circular filaments and a piecewise-linear filament.
|
|
456
|
+
This method is much faster (~100x typically) than discretizing the circular loop
|
|
457
|
+
into linear segments and using Neumann's formula.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
rfil: [m] filament R-coord
|
|
461
|
+
zfil: [m] filament Z-coord
|
|
462
|
+
nfil: [dimensionless] filament number of turns
|
|
463
|
+
xyzfil: [m] x,y,z coords of current filament origins (start of segment)
|
|
464
|
+
dlxyzfil: [m] x,y,z length delta of current filaments
|
|
465
|
+
par: Whether to use CPU parallelism
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
[H] mutual inductance
|
|
469
|
+
"""
|
|
470
|
+
rfil, zfil, nfil = _3tup_contig((rfil, zfil, nfil))
|
|
471
|
+
xyzfil = _3tup_contig(xyzfil)
|
|
472
|
+
dlxyzfil = _3tup_contig(dlxyzfil)
|
|
473
|
+
m = em_mutual_inductance_circular_to_linear(rfil, zfil, nfil, xyzfil, dlxyzfil, par)
|
|
474
|
+
|
|
475
|
+
return m # [H]
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def flux_density_dipole(
|
|
479
|
+
loc: Array3xN,
|
|
480
|
+
moment: Array3xN,
|
|
481
|
+
xyzp: Array3xN,
|
|
482
|
+
par: bool = True,
|
|
483
|
+
) -> NDArray[float64]:
|
|
484
|
+
"""
|
|
485
|
+
Magnetic flux density of a dipole in cartesian coordiantes.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
loc: [m] x,y,z coordinates of dipole
|
|
489
|
+
moment: [A-m^2] dipole magnetic moment vector
|
|
490
|
+
xyzp: [m] x,y,z coords of observation points
|
|
491
|
+
par: Whether to use CPU parallelism
|
|
492
|
+
|
|
493
|
+
Returns:
|
|
494
|
+
[T] flux density
|
|
495
|
+
"""
|
|
496
|
+
loc = _3tup_contig(loc)
|
|
497
|
+
moment = _3tup_contig(moment)
|
|
498
|
+
xyzp = _3tup_contig(xyzp)
|
|
499
|
+
bx, by, bz = em_flux_density_dipole(loc, moment, xyzp, par) # [T]
|
|
500
|
+
|
|
501
|
+
return bx, by, bz # type: ignore
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def body_force_density_circular_filament_cartesian(
|
|
505
|
+
ifil: NDArray[float64],
|
|
506
|
+
rfil: NDArray[float64],
|
|
507
|
+
zfil: NDArray[float64],
|
|
508
|
+
obs: Array3xN,
|
|
509
|
+
j: Array3xN,
|
|
510
|
+
par: bool = True,
|
|
511
|
+
) -> NDArray[float64]:
|
|
512
|
+
"""
|
|
513
|
+
JxB (Lorentz) body force density (per volume) in cartesian form due to a circular current
|
|
514
|
+
filament segment at an observation point in cartesian form with some current density (per area).
|
|
515
|
+
|
|
516
|
+
Args:
|
|
517
|
+
ifil: [A] filament current
|
|
518
|
+
rfil: [m] filament R-coord
|
|
519
|
+
zfil: [m] filament Z-coord
|
|
520
|
+
obs: [m] x,y,z coords of observation locations
|
|
521
|
+
j: [A/m^2] current density vector at observation locations
|
|
522
|
+
par: Whether to use CPU parallelism
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
[N/m^3] body force density
|
|
526
|
+
"""
|
|
527
|
+
ifil, rfil, zfil = _3tup_contig((ifil, rfil, zfil))
|
|
528
|
+
obs = _3tup_contig(obs)
|
|
529
|
+
j = _3tup_contig(j)
|
|
530
|
+
jxbx, jxby, jxbz = em_body_force_density_circular_filament_cartesian(
|
|
531
|
+
ifil, rfil, zfil, obs, j, par
|
|
532
|
+
) # [N/m^3]
|
|
533
|
+
|
|
534
|
+
return jxbx, jxby, jxbz # type: ignore
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def body_force_density_linear_filament(
|
|
538
|
+
xyzfil: Array3xN,
|
|
539
|
+
dlxyzfil: Array3xN,
|
|
540
|
+
ifil: NDArray[float64],
|
|
541
|
+
obs: Array3xN,
|
|
542
|
+
j: Array3xN,
|
|
543
|
+
par: bool = True,
|
|
544
|
+
) -> NDArray[float64]:
|
|
545
|
+
"""
|
|
546
|
+
JxB (Lorentz) body force density (per volume) due to a linear current
|
|
547
|
+
filament segment at an observation point with some current density (per area).
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
xyzfil: [m] x,y,z coords of current filament origins (start of segment)
|
|
551
|
+
dlxyzfil: [m] x,y,z length delta of current filaments
|
|
552
|
+
ifil: [A] filament current
|
|
553
|
+
obs: [m] x,y,z coords of observation locations
|
|
554
|
+
j: [A/m^2] current density vector at observation locations
|
|
555
|
+
par: Whether to use CPU parallelism
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
[N/m^3] body force density
|
|
559
|
+
"""
|
|
560
|
+
xyzfil = _3tup_contig(xyzfil)
|
|
561
|
+
dlxyzfil = _3tup_contig(dlxyzfil)
|
|
562
|
+
ifil = ascontiguousarray(ifil).flatten()
|
|
563
|
+
obs = _3tup_contig(obs)
|
|
564
|
+
j = _3tup_contig(j)
|
|
565
|
+
jxbx, jxby, jxbz = em_body_force_density_linear_filament(
|
|
566
|
+
xyzfil, dlxyzfil, ifil, obs, j, par
|
|
567
|
+
) # [N/m^3]
|
|
568
|
+
|
|
569
|
+
return jxbx, jxby, jxbz # type: ignore
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _3tup_contig(
|
|
573
|
+
t: Array3xN,
|
|
574
|
+
) -> tuple[NDArray[float64], NDArray[float64], NDArray[float64]]:
|
|
575
|
+
"""Make contiguous references or copies to arrays in a 3-tuple. Only copies data if it is not already contiguous."""
|
|
576
|
+
return (
|
|
577
|
+
ascontiguousarray(t[0]).flatten(),
|
|
578
|
+
ascontiguousarray(t[1]).flatten(),
|
|
579
|
+
ascontiguousarray(t[2]).flatten(),
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def _2tup_contig(
|
|
584
|
+
t: tuple[NDArray[float64], NDArray[float64]],
|
|
585
|
+
) -> tuple[NDArray[float64], NDArray[float64]]:
|
|
586
|
+
"""Make contiguous references or copies to arrays in a 2-tuple. Only copies data if it is not already contiguous."""
|
|
587
|
+
return (ascontiguousarray(t[0]).flatten(), ascontiguousarray(t[1]).flatten())
|
cfsem/py.typed
ADDED
|
File without changes
|
cfsem/types.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cfsem
|
|
3
|
+
Version: 2.3.1
|
|
4
|
+
Classifier: Programming Language :: Rust
|
|
5
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
7
|
+
Requires-Dist: numpy>=2
|
|
8
|
+
Requires-Dist: interpn>=0.2.5
|
|
9
|
+
Requires-Dist: pytest>=7.1.3 ; extra == 'dev'
|
|
10
|
+
Requires-Dist: coverage>=6.5.0 ; extra == 'dev'
|
|
11
|
+
Requires-Dist: ruff>=0.6.2 ; extra == 'dev'
|
|
12
|
+
Requires-Dist: pyright==1.1.393 ; extra == 'dev'
|
|
13
|
+
Requires-Dist: scipy>=1.8 ; extra == 'dev'
|
|
14
|
+
Requires-Dist: mkdocs>=1.6.0 ; extra == 'dev'
|
|
15
|
+
Requires-Dist: mkdocs-material>=9.5.25 ; extra == 'dev'
|
|
16
|
+
Requires-Dist: mkdocstrings[python]>=0.25.1 ; extra == 'dev'
|
|
17
|
+
Requires-Dist: matplotlib>=3.9 ; extra == 'dev'
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Summary: Quasi-steady electromagnetics including filamentized approximations, Biot-Savart, and Grad-Shafranov.
|
|
21
|
+
Author-email: Commonwealth Fusion Systems <jlogan@cfs.energy>
|
|
22
|
+
License: MIT
|
|
23
|
+
Requires-Python: >=3.9, <3.14
|
|
24
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
25
|
+
|
|
26
|
+
# cfsem
|
|
27
|
+
|
|
28
|
+
[Docs - Rust](https://docs.rs/cfsem) | [Docs - Python](https://cfsem-py.readthedocs.io/)
|
|
29
|
+
|
|
30
|
+
Quasi-steady electromagnetics including filamentized approximations, Biot-Savart, and Grad-Shafranov.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Requirements
|
|
35
|
+
|
|
36
|
+
* Python 3.9-3.12 and pip
|
|
37
|
+
* Don't worry about this:
|
|
38
|
+
* This info provided for troubleshooting purposes:
|
|
39
|
+
* If on an x86 processor, you will need a CPU that supports SSE through 4.1, AVX, and FMA.
|
|
40
|
+
* This should be true on any modern machine.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install cfsem
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Development
|
|
47
|
+
|
|
48
|
+
Requirements
|
|
49
|
+
|
|
50
|
+
* [Rust](https://www.rust-lang.org/tools/install)
|
|
51
|
+
|
|
52
|
+
To install in the active python environment, do
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install -e .[dev]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To build the Rust bindings only, do
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
maturin develop --release
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
No part of installation requires root. If access issues are encountered, this can likely be resolved by using a virtual environment.
|
|
65
|
+
|
|
66
|
+
Some computationally-expensive calculations are written in Rust. These calculations and their python bindings are installed from pre-built binaries when installing from pypi or compiled during local development installation, with no intervention from the user in either case. Symmetric bindings with docstrings are available in the `bindings.py` module and re-exported at the library level.
|
|
67
|
+
|
|
68
|
+
To build with all of the optimizations available on your local machine, you can do:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
RUSTCFLAGS="-Ctarget-cpu=native" maturin develop --release
|
|
72
|
+
pip install -e .[dev]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Contributing
|
|
76
|
+
|
|
77
|
+
Contributions consistent with the goals and anti-goals of the package are welcome.
|
|
78
|
+
|
|
79
|
+
Please make an issue ticket to discuss changes before investing significant time into a branch.
|
|
80
|
+
|
|
81
|
+
Goals
|
|
82
|
+
|
|
83
|
+
* Library-level functions and formulas
|
|
84
|
+
* Comprehensive documentation including literature references, assumptions, and units-of-measure
|
|
85
|
+
* Quantitative unit-testing of formulas
|
|
86
|
+
* Performance (both speed and memory-efficiency)
|
|
87
|
+
* Guide development of performance-sensitive functions with structured benchmarking
|
|
88
|
+
* Cross-platform compatibility
|
|
89
|
+
* Minimization of long-term maintenance overhead (both for the library, and for users of the library)
|
|
90
|
+
* Semantic versioning
|
|
91
|
+
* Automated linting and formatting tools
|
|
92
|
+
* Centralized CI and toolchain configuration in as few files as possible
|
|
93
|
+
|
|
94
|
+
Anti-Goals
|
|
95
|
+
|
|
96
|
+
* Fanciness that increases environment complexity, obfuscates reasoning, or introduces platform restrictions
|
|
97
|
+
* Brittle CI or toolchain processes that drive increased maintenance overhead
|
|
98
|
+
* Application-level functionality (graphical interfaces, simulation frameworks, etc)
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
Licensed under the MIT license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT) .
|
|
103
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cfsem-2.3.1.dist-info/METADATA,sha256=FURrdp6mYIsLb1Z7obQ5AxFgN41vrBqt98qd5bS2Zxw,3768
|
|
2
|
+
cfsem-2.3.1.dist-info/WHEEL,sha256=6gg8M7kMeWmDzkhJuJzWLhBYdt7OGDF81wlSJjN85_E,96
|
|
3
|
+
cfsem-2.3.1.dist-info/licenses/LICENSE,sha256=Z0UP8fLqfO3bA_i2g9fzRBRjwB7lHUsnAB3jmtIW2PU,1105
|
|
4
|
+
cfsem/bindings.py,sha256=wFrs9Aj4E38Tjht0kRPYjMB_szgX4rho0IesYvDmdQQ,21345
|
|
5
|
+
cfsem/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
cfsem/types.py,sha256=rr8VSLL4VkEZVmV4lSh9e6SO_MLBPTUcKME5nrIiS1Q,201
|
|
7
|
+
cfsem/__init__.py,sha256=yIzSHtKvfSiEkLeFj6fKFTj82WjY9VvWtnCWD-mfkwo,22394
|
|
8
|
+
cfsem/_cfsem.cp313-win_amd64.pyd,sha256=NFTIERDH0yN4s_JaysiCEAcLNXAPlX2Xf_H3yHDGGZk,724480
|
|
9
|
+
cfsem-2.3.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Commonwealth Fusion Systems
|
|
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.
|