cfsem 2.0.4__tar.gz → 2.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 (43) hide show
  1. {cfsem-2.0.4 → cfsem-2.1.0}/CHANGELOG.md +11 -0
  2. {cfsem-2.0.4 → cfsem-2.1.0}/Cargo.lock +2 -2
  3. {cfsem-2.0.4 → cfsem-2.1.0}/Cargo.toml +1 -1
  4. {cfsem-2.0.4 → cfsem-2.1.0}/PKG-INFO +4 -4
  5. {cfsem-2.0.4 → cfsem-2.1.0}/README.md +1 -1
  6. {cfsem-2.0.4 → cfsem-2.1.0}/cfsem/__init__.py +16 -4
  7. {cfsem-2.0.4 → cfsem-2.1.0}/cfsem/bindings.py +107 -21
  8. cfsem-2.1.0/docs/assets/logo.svg +1 -0
  9. {cfsem-2.0.4 → cfsem-2.1.0}/docs/python/flux_density.md +1 -3
  10. {cfsem-2.0.4 → cfsem-2.1.0}/docs/python/inductance.md +2 -0
  11. cfsem-2.1.0/docs/python/vector_potential.md +5 -0
  12. cfsem-2.1.0/docs/stylesheets/extra.css +4 -0
  13. {cfsem-2.0.4 → cfsem-2.1.0}/mkdocs.yml +9 -1
  14. {cfsem-2.0.4 → cfsem-2.1.0}/pyproject.toml +3 -3
  15. {cfsem-2.0.4 → cfsem-2.1.0}/src/lib.rs +159 -9
  16. {cfsem-2.0.4 → cfsem-2.1.0}/test/test_electromagnetics.py +104 -21
  17. {cfsem-2.0.4 → cfsem-2.1.0}/.cargo/config.toml +0 -0
  18. {cfsem-2.0.4 → cfsem-2.1.0}/.github/workflows/CI.yml +0 -0
  19. {cfsem-2.0.4 → cfsem-2.1.0}/.github/workflows/actions.yml +0 -0
  20. {cfsem-2.0.4 → cfsem-2.1.0}/.gitignore +0 -0
  21. {cfsem-2.0.4 → cfsem-2.1.0}/.readthedocs.yml +0 -0
  22. {cfsem-2.0.4 → cfsem-2.1.0}/CONTRIBUTING.md +0 -0
  23. {cfsem-2.0.4 → cfsem-2.1.0}/LICENSE +0 -0
  24. {cfsem-2.0.4 → cfsem-2.1.0}/build_docs.sh +0 -0
  25. {cfsem-2.0.4 → cfsem-2.1.0}/cfsem/py.typed +0 -0
  26. {cfsem-2.0.4 → cfsem-2.1.0}/cfsem/types.py +0 -0
  27. {cfsem-2.0.4 → cfsem-2.1.0}/docs/index.md +0 -0
  28. {cfsem-2.0.4 → cfsem-2.1.0}/docs/javascripts/mathjax.js +0 -0
  29. {cfsem-2.0.4 → cfsem-2.1.0}/docs/python/example_outputs/.placeholder +0 -0
  30. {cfsem-2.0.4 → cfsem-2.1.0}/docs/python/examples.md +0 -0
  31. {cfsem-2.0.4 → cfsem-2.1.0}/docs/python/filament.md +0 -0
  32. {cfsem-2.0.4 → cfsem-2.1.0}/docs/python/grad_shafranov.md +0 -0
  33. {cfsem-2.0.4 → cfsem-2.1.0}/docs/python/math.md +0 -0
  34. {cfsem-2.0.4 → cfsem-2.1.0}/docs/python/types.md +0 -0
  35. {cfsem-2.0.4 → cfsem-2.1.0}/docs/requirements.txt +0 -0
  36. {cfsem-2.0.4 → cfsem-2.1.0}/examples/helmholtz.py +0 -0
  37. {cfsem-2.0.4 → cfsem-2.1.0}/examples/inductance.py +0 -0
  38. {cfsem-2.0.4 → cfsem-2.1.0}/test/__init__.py +0 -0
  39. {cfsem-2.0.4 → cfsem-2.1.0}/test/test_examples.py +0 -0
  40. {cfsem-2.0.4 → cfsem-2.1.0}/test/test_filaments.py +0 -0
  41. {cfsem-2.0.4 → cfsem-2.1.0}/test/test_funcs.py +0 -0
  42. {cfsem-2.0.4 → cfsem-2.1.0}/test/test_gradshafranov.py +0 -0
  43. {cfsem-2.0.4 → cfsem-2.1.0}/test/test_math.py +0 -0
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0 2024-08-27
4
+
5
+ ### Added
6
+
7
+ * Add vector potential calcs for linear and circular filaments
8
+ * Add parallel options for circular-filament calcs
9
+ * Add parametrization of unit tests over parallel and serial variants
10
+ * Add optional parallel flags to functions that use functions with new parallel options
11
+
12
+ ### Changed
13
+
3
14
  ## 2.0.4 2024-07-10
4
15
 
5
16
  ### Changed
@@ -46,9 +46,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
46
46
 
47
47
  [[package]]
48
48
  name = "cfsem"
49
- version = "1.0.0"
49
+ version = "1.1.0"
50
50
  source = "registry+https://github.com/rust-lang/crates.io-index"
51
- checksum = "e5cbed55153f04ab37ca424a26fcce4bd6fe8f482bca97bd3d5052bfba620466"
51
+ checksum = "2b3e27e61569347c86debb5a8032a62c7acb3b6da51d14f68dd948cf7625dd79"
52
52
  dependencies = [
53
53
  "nalgebra",
54
54
  "rayon",
@@ -11,7 +11,7 @@ crate-type = ["cdylib"]
11
11
  [dependencies]
12
12
  pyo3 = "0.21.2"
13
13
  numpy = "0.21.0" # This must match pyo3 version!
14
- cfsem = "1.0.0"
14
+ cfsem = "1.1.0"
15
15
 
16
16
  [profile.release]
17
17
  opt-level = 3
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cfsem
3
- Version: 2.0.4
3
+ Version: 2.1.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
7
7
  Requires-Dist: numpy >=2
8
- Requires-Dist: interpn >=0.2.2
8
+ Requires-Dist: interpn >=0.2.3
9
9
  Requires-Dist: pytest >=7.1.3 ; extra == 'dev'
10
10
  Requires-Dist: coverage >=6.5.0 ; extra == 'dev'
11
- Requires-Dist: ruff >=0.4.2 ; extra == 'dev'
11
+ Requires-Dist: ruff >=0.6.2 ; extra == 'dev'
12
12
  Requires-Dist: pyright >=1.1.331 ; extra == 'dev'
13
13
  Requires-Dist: scipy >=1.8 ; extra == 'dev'
14
14
  Requires-Dist: mkdocs >=1.6.0 ; extra == 'dev'
@@ -25,7 +25,7 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
25
25
 
26
26
  # cfsem
27
27
 
28
- [Docs - Rust](https://docs.rs/cfsem) | [Docs - Python](https://cfsem.readthedocs.io/)
28
+ [Docs - Rust](https://docs.rs/cfsem) | [Docs - Python](https://cfsem-py.readthedocs.io/)
29
29
 
30
30
  Quasi-steady electromagnetics including filamentized approximations, Biot-Savart, and Grad-Shafranov.
31
31
 
@@ -1,6 +1,6 @@
1
1
  # cfsem
2
2
 
3
- [Docs - Rust](https://docs.rs/cfsem) | [Docs - Python](https://cfsem.readthedocs.io/)
3
+ [Docs - Rust](https://docs.rs/cfsem) | [Docs - Python](https://cfsem-py.readthedocs.io/)
4
4
 
5
5
  Quasi-steady electromagnetics including filamentized approximations, Biot-Savart, and Grad-Shafranov.
6
6
 
@@ -9,6 +9,7 @@ from cfsem.types import Array3xN
9
9
 
10
10
  from cfsem.bindings import (
11
11
  flux_circular_filament,
12
+ flux_density_linear_filament,
12
13
  flux_density_biot_savart,
13
14
  flux_density_circular_filament,
14
15
  gs_operator_order2,
@@ -16,6 +17,8 @@ from cfsem.bindings import (
16
17
  inductance_piecewise_linear_filaments,
17
18
  filament_helix_path,
18
19
  rotate_filaments_about_path,
20
+ vector_potential_linear_filament,
21
+ vector_potential_circular_filament,
19
22
  )
20
23
 
21
24
  from ._cfsem import ellipe, ellipk
@@ -29,6 +32,7 @@ https://www.physics.nist.gov/cuu/pdf/wall_2018.pdf .
29
32
  __all__ = [
30
33
  "flux_circular_filament",
31
34
  "flux_density_biot_savart",
35
+ "flux_density_linear_filament",
32
36
  "flux_density_circular_filament",
33
37
  "gs_operator_order2",
34
38
  "gs_operator_order4",
@@ -47,6 +51,8 @@ __all__ = [
47
51
  "ellipe",
48
52
  "ellipk",
49
53
  "rotate_filaments_about_path",
54
+ "vector_potential_linear_filament",
55
+ "vector_potential_circular_filament",
50
56
  ]
51
57
 
52
58
 
@@ -240,7 +246,9 @@ def self_inductance_lyle6(r: float, dr: float, dz: float, n: float) -> float:
240
246
  return self_inductance # [H]
241
247
 
242
248
 
243
- def mutual_inductance_of_circular_filaments(rzn1: NDArray, rzn2: NDArray) -> float:
249
+ def mutual_inductance_of_circular_filaments(
250
+ rzn1: NDArray, rzn2: NDArray, par: bool = True
251
+ ) -> float:
244
252
  """
245
253
  Analytic mutual inductance between a pair of ideal cylindrically-symmetric coaxial filaments.
246
254
 
@@ -251,19 +259,22 @@ def mutual_inductance_of_circular_filaments(rzn1: NDArray, rzn2: NDArray) -> flo
251
259
  Args:
252
260
  rzn1 (array): 3x1 array (r [m], z [m], n []) coordinates and number of turns
253
261
  rzn2 (array): 3x1 array (r [m], z [m], n []) coordinates and number of turns
262
+ par: Whether to use CPU parallelism
254
263
 
255
264
  Returns:
256
265
  float: [H] mutual inductance
257
266
  """
258
267
 
259
268
  m = mutual_inductance_of_cylindrical_coils(
260
- rzn1.reshape((3, 1)), rzn2.reshape((3, 1))
269
+ rzn1.reshape((3, 1)), rzn2.reshape((3, 1)), par
261
270
  )
262
271
 
263
272
  return m # [H]
264
273
 
265
274
 
266
- def mutual_inductance_of_cylindrical_coils(f1: NDArray, f2: NDArray) -> float:
275
+ def mutual_inductance_of_cylindrical_coils(
276
+ f1: NDArray, f2: NDArray, par: bool = True
277
+ ) -> float:
267
278
  """
268
279
  Analytical mutual inductance between two coaxial collections of ideal filaments.
269
280
 
@@ -274,6 +285,7 @@ def mutual_inductance_of_cylindrical_coils(f1: NDArray, f2: NDArray) -> float:
274
285
  Args:
275
286
  f1: 3 x N array of filament definitions like (r [m], z [m], n [])
276
287
  f2: 3 x N array of filament definitions like (r [m], z [m], n [])
288
+ par: Whether to use CPU parallelism
277
289
 
278
290
  Returns:
279
291
  [H] mutual inductance of the two discretized coils
@@ -284,7 +296,7 @@ def mutual_inductance_of_cylindrical_coils(f1: NDArray, f2: NDArray) -> float:
284
296
  # Using n2 as the current per filament is equivalent to examining a 1A reference current,
285
297
  # which gives us the flux per amp (inductance)
286
298
  m = np.sum(
287
- n1 * flux_circular_filament(n2, r2, z2, r1, z1)
299
+ n1 * flux_circular_filament(n2, r2, z2, r1, z1, par)
288
300
  ) # [H] total mutual inductance
289
301
 
290
302
  return m # [H]
@@ -11,7 +11,7 @@ from numpy import ascontiguousarray, float64, zeros_like
11
11
  from numpy.typing import NDArray
12
12
 
13
13
  from ._cfsem import flux_circular_filament as em_flux_circular_filament
14
- from ._cfsem import flux_density_biot_savart as em_flux_density_biot_savart
14
+ from ._cfsem import flux_density_linear_filament as em_flux_density_linear_filament
15
15
  from ._cfsem import flux_density_circular_filament as em_flux_density_circular_filament
16
16
  from ._cfsem import gs_operator_order2 as em_gs_operator_order2
17
17
  from ._cfsem import gs_operator_order4 as em_gs_operator_order4
@@ -20,6 +20,12 @@ from ._cfsem import (
20
20
  )
21
21
  from ._cfsem import filament_helix_path as em_filament_helix_path
22
22
  from ._cfsem import rotate_filaments_about_path as em_rotate_filaments_about_path
23
+ from ._cfsem import (
24
+ vector_potential_circular_filament as em_vector_potential_circular_filament,
25
+ )
26
+ from ._cfsem import (
27
+ vector_potential_linear_filament as em_vector_potential_linear_filament,
28
+ )
23
29
 
24
30
 
25
31
  def flux_circular_filament(
@@ -28,6 +34,7 @@ def flux_circular_filament(
28
34
  zfil: NDArray[float64],
29
35
  rprime: NDArray[float64],
30
36
  zprime: NDArray[float64],
37
+ par: bool = True,
31
38
  ) -> NDArray[float64]:
32
39
  """
33
40
  Flux contributions from some circular filaments to some observation points,
@@ -37,17 +44,55 @@ def flux_circular_filament(
37
44
  (`rprime`, `zprime`) observation location with $\\hat{n}$ oriented parallel to the z-axis.
38
45
 
39
46
  A convenient interpretation of the flux is as the mutual inductance
40
- per secondary coil current between a filament at (`rfil`, `zfil`) and a secondary
47
+ between a circular filament at (`rfil`, `zfil`) and a second circular
41
48
  filament at (`rprime`, `zprime`); this can be used to get the mutual inductance
42
- between two filamentized coils as the sum of I1 * I2 * flux_from_coil_1_to_coil_2.
49
+ between two filamentized coils as the sum of flux contributions between each coil's filaments.
43
50
  Because mutual inductance is reflexive, the order of the coils can be reversed and
44
51
  the same result is obtained.
45
52
 
46
- This function is fairly well-optimized and runs about >10x faster than an
47
- equivalent implementation in numpy+scipy, with lower RAM usage
48
- (O(n) in number of observation locations) than what would be required
49
- for a tiled implementation. Numba does not have an implementation of
50
- elliptic integrals and will not accept scipy's.
53
+ Args:
54
+ ifil: [A] filament current
55
+ rfil: [m] filament R-coord
56
+ zfil: [m] filament Z-coord
57
+ rprime: [m] Observation point R-coord
58
+ zprime: [m] Observation point Z-coord
59
+ par: Whether to use CPU parallelism
60
+
61
+ Returns:
62
+ [Wb] or [T-m^2] or [V-s] psi, poloidal flux at each observation point
63
+ """
64
+ ifil = ascontiguousarray(ifil)
65
+ rfil = ascontiguousarray(rfil)
66
+ zfil = ascontiguousarray(zfil)
67
+ rprime = ascontiguousarray(rprime)
68
+ zprime = ascontiguousarray(zprime)
69
+ psi = em_flux_circular_filament(ifil, rfil, zfil, rprime, zprime, par)
70
+ return psi # [Wb] or [T-m^2] or [V-s]
71
+
72
+
73
+ def vector_potential_circular_filament(
74
+ ifil: NDArray[float64],
75
+ rfil: NDArray[float64],
76
+ zfil: NDArray[float64],
77
+ rprime: NDArray[float64],
78
+ zprime: NDArray[float64],
79
+ par: bool = True,
80
+ ) -> NDArray[float64]:
81
+ """
82
+ Vector potential contributions from some circular filaments to some observation points.
83
+ Off-axis A_phi component for a circular current filament in vacuum.
84
+
85
+ The vector potential of a loop has zero r- and z- components due to symmetry,
86
+ and does not vary in the phi-direction.
87
+
88
+ Note that to recover the B-field as the curl of A, the curl operator for cylindrical
89
+ coordinates must be used with the output of this function incorporated into a full
90
+ 3D A-field like [A_r, A_phi, A_z].
91
+
92
+ References:
93
+ [1] J. C. Simpson, J. E. Lane, C. D. Immer, R. C. Youngquist, and T. Steinrock,
94
+ “Simple Analytic Expressions for the Magnetic Field of a Circular Current Loop,”
95
+ Jan. 01, 2001. Accessed: Sep. 06, 2022. [Online]. Available: <https://ntrs.nasa.gov/citations/20010038494>
51
96
 
52
97
  Args:
53
98
  ifil: [A] filament current
@@ -55,17 +100,18 @@ def flux_circular_filament(
55
100
  zfil: [m] filament Z-coord
56
101
  rprime: [m] Observation point R-coord
57
102
  zprime: [m] Observation point Z-coord
103
+ par: Whether to use CPU parallelism
58
104
 
59
105
  Returns:
60
- [T-m^2] or [V-s] psi, poloidal flux at each observation point
106
+ [Wb/m] or [V-s/m] a_phi, vector potential in the toroidal direction
61
107
  """
62
108
  ifil = ascontiguousarray(ifil)
63
109
  rfil = ascontiguousarray(rfil)
64
110
  zfil = ascontiguousarray(zfil)
65
111
  rprime = ascontiguousarray(rprime)
66
112
  zprime = ascontiguousarray(zprime)
67
- psi = em_flux_circular_filament(ifil, rfil, zfil, rprime, zprime)
68
- return psi # [T-m^2] or [V-s]
113
+ a_phi = em_vector_potential_circular_filament(ifil, rfil, zfil, rprime, zprime, par)
114
+ return a_phi # [Wb/m] or [V-s/m]
69
115
 
70
116
 
71
117
  def flux_density_circular_filament(
@@ -74,6 +120,7 @@ def flux_density_circular_filament(
74
120
  zfil: NDArray[float64],
75
121
  rprime: NDArray[float64],
76
122
  zprime: NDArray[float64],
123
+ par: bool = True,
77
124
  ) -> tuple[NDArray[float64], NDArray[float64]]:
78
125
  """
79
126
  Off-axis Br,Bz components for a circular current filament in vacuum.
@@ -105,6 +152,7 @@ def flux_density_circular_filament(
105
152
  zfil: [m] filament Z-coord
106
153
  rprime: [m] Observation point R-coord
107
154
  zprime: [m] Observation point Z-coord
155
+ par: Whether to use CPU parallelism
108
156
 
109
157
  Returns:
110
158
  [T] (Br, Bz) flux density components
@@ -114,11 +162,11 @@ def flux_density_circular_filament(
114
162
  zfil = ascontiguousarray(zfil)
115
163
  rprime = ascontiguousarray(rprime)
116
164
  zprime = ascontiguousarray(zprime)
117
- Br, Bz = em_flux_density_circular_filament(ifil, rfil, zfil, rprime, zprime)
165
+ Br, Bz = em_flux_density_circular_filament(ifil, rfil, zfil, rprime, zprime, par)
118
166
  return Br, Bz # [T]
119
167
 
120
168
 
121
- def flux_density_biot_savart(
169
+ def flux_density_linear_filament(
122
170
  xyzp: Array3xN,
123
171
  xyzfil: Array3xN,
124
172
  dlxyzfil: Array3xN,
@@ -129,11 +177,6 @@ def flux_density_biot_savart(
129
177
  Biot-Savart law calculation for B-field contributions from many filament segments
130
178
  to many observation points.
131
179
 
132
- This calc is fairly well-optimized under the constraint to keep peak RAM usage to
133
- O(n) in n observation points, which precludes tiling. It comes out about 20% faster
134
- than an equivalent implementation in numba, which is about 4x faster than an
135
- equivalent implementation in numpy.
136
-
137
180
  Args:
138
181
  xyzp: [m] x,y,z coords of observation points
139
182
  xyzfil: [m] x,y,z coords of current filament origins (start of segment)
@@ -160,7 +203,50 @@ def flux_density_biot_savart(
160
203
  ascontiguousarray(dlxyzfil[2]),
161
204
  )
162
205
  ifil = ascontiguousarray(ifil)
163
- return em_flux_density_biot_savart(xyzp, xyzfil, dlxyzfil, ifil, par)
206
+ return em_flux_density_linear_filament(xyzp, xyzfil, dlxyzfil, ifil, par)
207
+
208
+
209
+ flux_density_biot_savart = flux_density_linear_filament # For backwards-compatibility
210
+
211
+
212
+ def vector_potential_linear_filament(
213
+ xyzp: Array3xN,
214
+ xyzfil: Array3xN,
215
+ dlxyzfil: Array3xN,
216
+ ifil: NDArray[float64],
217
+ par: bool = True,
218
+ ) -> Array3xN:
219
+ """
220
+ Vector potential calculation for A-field contribution from many current filament
221
+ segments to many observation points.
222
+
223
+ Args:
224
+ xyzp: [m] x,y,z coords of observation points
225
+ xyzfil: [m] x,y,z coords of current filament origins (start of segment)
226
+ dlxyzfil: [m] x,y,z length delta of current filaments
227
+ ifil: [A] current in each filament segment
228
+ par: Whether to use CPU parallelism
229
+
230
+ Returns:
231
+ [Wb/m] or [V-s/m] (Ax, Ay, Az) magnetic vector potential at observation points
232
+ """
233
+ xyzp = (
234
+ ascontiguousarray(xyzp[0]),
235
+ ascontiguousarray(xyzp[1]),
236
+ ascontiguousarray(xyzp[2]),
237
+ )
238
+ xyzfil = (
239
+ ascontiguousarray(xyzfil[0]),
240
+ ascontiguousarray(xyzfil[1]),
241
+ ascontiguousarray(xyzfil[2]),
242
+ )
243
+ dlxyzfil = (
244
+ ascontiguousarray(dlxyzfil[0]),
245
+ ascontiguousarray(dlxyzfil[1]),
246
+ ascontiguousarray(dlxyzfil[2]),
247
+ )
248
+ ifil = ascontiguousarray(ifil)
249
+ return em_vector_potential_linear_filament(xyzp, xyzfil, dlxyzfil, ifil, par)
164
250
 
165
251
 
166
252
  def inductance_piecewise_linear_filaments(
@@ -200,10 +286,10 @@ def inductance_piecewise_linear_filaments(
200
286
  References:
201
287
  [1] “Inductance,” Wikipedia. Dec. 12, 2022. Accessed: Jan. 23, 2023. [Online].
202
288
  Available: <https://en.wikipedia.org/w/index.php?title=Inductance>
203
-
289
+
204
290
  [2] F. E. Neumann, “Allgemeine Gesetze der inducirten elektrischen Ströme,”
205
291
  Jan. 1846, doi: [10.1002/andp.18461430103](https://doi.org/10.1002/andp.18461430103)
206
-
292
+
207
293
  [3] R. Dengler, “Self inductance of a wire loop as a curve integral,”
208
294
  AEM, vol. 5, no. 1, p. 1, Jan. 2016, doi: [10.7716/aem.v5i1.331](https://doi.org/10.7716/aem.v5i1.331)
209
295
 
@@ -0,0 +1 @@
1
+ <svg width="183" height="44" viewBox="0 0 183 44" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M22.591.294h-.83l-1.218 5.72 1.218 5.715h.83l1.218-5.714L22.591.294z" fill="#CCC8C3"/><path d="M22.591 32.25h-.83l-1.218 5.721 1.218 5.721h.83l1.218-5.72-1.218-5.722z" fill="#CCC8C3"/><path d="M15.148 1.463l-.78.283.811 5.788 3.1 4.958.782-.283-.818-5.788-3.095-4.958z" fill="#CCC8C3"/><path d="M26.084 31.488l-.787.289.818 5.788 3.094 4.958.781-.283-.811-5.788-3.095-4.964z" fill="#CCC8C3"/><path d="M8.55 5.104l-.64.535 2.75 5.161 4.607 3.6.634-.536-2.744-5.161L8.55 5.104z" fill="#CCC8C3"/><path d="M29.09 29.58l-.633.536 2.744 5.167 4.607 3.599.64-.535-2.75-5.161-4.607-3.605z" fill="#CCC8C3"/><path d="M3.598 10.782l-.418.72 4.343 3.912 5.56 1.809.419-.72-4.35-3.912-5.554-1.809z" fill="#CCC8C3"/><path d="M31.278 26.763l-.419.72 4.343 3.912 5.561 1.803.419-.72-4.343-3.906-5.561-1.809z" fill="#CCC8C3"/><path d="M.88 17.813l-.142.818 5.42 2.19 5.844-.203.147-.818-5.425-2.19-5.844.203z" fill="#CCC8C3"/><path d="M32.359 23.362l-.148.824 5.425 2.184 5.838-.203.148-.818-5.42-2.19-5.843.203z" fill="#CCC8C3"/><path d="M.738 25.349l.142.818 5.844.203 5.425-2.184-.147-.824-5.844-.203-5.42 2.19z" fill="#CCC8C3"/><path d="M32.21 19.8l.149.818 5.843.203 5.42-2.19-.148-.818-5.837-.203-5.426 2.19z" fill="#CCC8C3"/><path d="M3.18 32.478l.412.72 5.56-1.803 4.35-3.912-.418-.72-5.561 1.809-4.343 3.906z" fill="#CCC8C3"/><path d="M30.86 16.503l.418.72 5.56-1.809 4.343-3.912-.418-.72-5.56 1.809-4.344 3.912z" fill="#CCC8C3"/><path d="M7.91 38.347l.64.535 4.607-3.599 2.744-5.167-.64-.535-4.601 3.605-2.75 5.16z" fill="#CCC8C3"/><path d="M28.457 13.864l.634.535 4.607-3.598 2.75-5.162-.64-.535-4.607 3.599-2.744 5.16z" fill="#CCC8C3"/><path d="M14.363 42.24l.788.283 3.094-4.958.818-5.788-.787-.29-3.095 4.965-.818 5.788z" fill="#CCC8C3"/><path d="M25.297 12.21l.781.282 3.1-4.958.812-5.788-.78-.283-3.095 4.958-.818 5.788z" fill="#CCC8C3"/><path d="M72.669 10.93a4.072 4.072 0 00-1.477-1.015 5.629 5.629 0 00-3.937 0 3.984 3.984 0 00-1.457 1.009 4.36 4.36 0 00-.91 1.556 5.96 5.96 0 00-.32 2.024c-.012.682.096 1.36.32 2.005a4.54 4.54 0 00.91 1.563 3.9 3.9 0 001.457 1.015 5.629 5.629 0 003.937 0 4.072 4.072 0 001.477-1.015c.407-.452.718-.982.916-1.557a5.91 5.91 0 00.32-2.011c.008-.682-.1-1.36-.32-2.006a4.576 4.576 0 00-.916-1.568zm-1.63 5.678a2.172 2.172 0 01-1.846.768 2.147 2.147 0 01-1.79-.768 3.918 3.918 0 010-4.214 2.147 2.147 0 011.79-.77 2.171 2.171 0 011.846.77 3.918 3.918 0 010 4.214z" fill="#CCC8C3"/><path d="M85.06 9.558a3.02 3.02 0 00-1.5.338 3.923 3.923 0 00-1.009.75c-.05.08-.105.08-.154 0a2.952 2.952 0 00-.984-.799 3.223 3.223 0 00-1.458-.307c-.222 0-.443.03-.658.086-.21.057-.415.13-.615.215-.197.08-.386.177-.566.29-.146.09-.282.195-.406.313-.062.061-.117.05-.166-.037l-.191-.566a.184.184 0 00-.184-.11h-1.784a.086.086 0 00-.099.067v9.344a.086.086 0 00.074.099h2.091a.086.086 0 00.093-.074v-5.23a4.682 4.682 0 01.086-.922c.046-.26.14-.511.277-.738.127-.205.305-.375.516-.492.25-.13.53-.19.812-.178.287-.009.57.064.819.209.215.133.394.317.522.535.137.232.233.486.283.75.057.276.086.556.087.837v5.204a.086.086 0 00.067.099h2.092a.086.086 0 00.092-.074v-5.205c.001-.309.03-.618.086-.922a2.16 2.16 0 01.283-.738c.127-.206.305-.375.517-.492a1.61 1.61 0 01.806-.179c.287-.009.57.064.818.21.217.132.398.316.529.534.136.233.232.486.283.75.054.276.08.557.08.838v5.204a.087.087 0 00.041.087c.01.006.021.01.032.011h2.092a.086.086 0 00.092-.074v-5.345c0-1.47-.332-2.547-1.002-3.242a3.691 3.691 0 00-2.793-1.046z" fill="#CCC8C3"/><path d="M100.333 9.558a3.02 3.02 0 00-1.5.338c-.372.201-.709.46-.997.769-.056.08-.105.08-.154 0a2.952 2.952 0 00-.984-.8 3.223 3.223 0 00-1.458-.307c-.222 0-.444.03-.658.086a4.68 4.68 0 00-.646.215c-.197.08-.386.177-.566.29-.146.09-.282.195-.406.313-.062.061-.117.05-.166-.037l-.191-.566a.185.185 0 00-.185-.11h-1.784a.086.086 0 00-.098.067v9.344a.087.087 0 00.042.087c.01.006.02.01.032.012h2.091a.086.086 0 00.093-.074v-5.23a4.682 4.682 0 01.086-.922c.046-.26.14-.511.277-.738.127-.205.305-.375.516-.492.25-.13.53-.19.812-.179.287-.008.57.065.818.21.216.133.395.317.523.535.137.232.232.486.283.75.057.276.085.556.086.837v5.204a.086.086 0 00.068.099h2.092a.086.086 0 00.092-.074V13.98c.001-.31.03-.619.086-.923a2.16 2.16 0 01.283-.738c.127-.206.305-.375.517-.492a1.61 1.61 0 01.806-.179c.287-.009.57.064.818.21.217.132.398.316.529.534.136.233.232.486.283.751.054.275.081.556.08.837v5.204a.09.09 0 00.018.063.09.09 0 00.055.035h2.092a.086.086 0 00.092-.074v-5.364c0-1.47-.332-2.546-1.002-3.242a3.707 3.707 0 00-2.775-1.045z" fill="#CCC8C3"/><path d="M113.487 10.93a4.007 4.007 0 00-1.47-1.015 5.629 5.629 0 00-3.937 0 3.981 3.981 0 00-1.458 1.009 4.36 4.36 0 00-.911 1.556 5.976 5.976 0 00-.32 2.024 5.87 5.87 0 00.32 2.005 4.55 4.55 0 00.911 1.563c.401.446.9.793 1.458 1.015a5.629 5.629 0 003.937 0 4.006 4.006 0 001.47-1.015c.411-.45.725-.98.922-1.557a5.882 5.882 0 00.32-2.011c.008-.682-.1-1.36-.32-2.006a4.492 4.492 0 00-.922-1.568zm-1.624 5.678a2.177 2.177 0 01-1.846.768 2.145 2.145 0 01-1.79-.768 3.921 3.921 0 010-4.214 2.152 2.152 0 011.79-.77 2.172 2.172 0 011.846.77 3.921 3.921 0 010 4.214z" fill="#CCC8C3"/><path d="M123.952 10.911a3.214 3.214 0 00-1.23-.984 4.267 4.267 0 00-1.845-.37 2.47 2.47 0 00-.616.087 5.965 5.965 0 00-.615.203c-.193.08-.381.174-.56.283a2.6 2.6 0 00-.406.313c-.036.05-.073.068-.11.056a.164.164 0 01-.093-.074l-.209-.584c0-.062-.067-.093-.172-.093h-1.784a.077.077 0 00-.031.002.084.084 0 00-.051.037.079.079 0 00-.01.03v9.343a.083.083 0 00.014.063.087.087 0 00.054.036h2.091a.091.091 0 00.063-.018.087.087 0 00.035-.056v-5.34c0-.276.038-.551.111-.818.07-.26.193-.504.363-.713.176-.215.399-.386.652-.499.296-.133.617-.198.941-.19a1.902 1.902 0 011.606.78c.172.234.296.5.363.782.072.275.112.558.117.843v5.13a.083.083 0 00.014.063.085.085 0 00.053.036h2.092a.086.086 0 00.092-.074V14.03a6.84 6.84 0 00-.203-1.692 4.157 4.157 0 00-.726-1.427z" fill="#CCC8C3"/><path d="M140.375 9.749h-2.104c-.055 0-.092.036-.117.11l-1.501 7.253a.08.08 0 01-.014.036.088.088 0 01-.064.04.088.088 0 01-.038-.002.097.097 0 01-.074-.074l-1.729-7.253c-.024-.074-.067-.11-.129-.11h-3.008c-.062 0-.105.036-.129.11l-1.729 7.253c0 .068-.043.098-.098.098-.056 0-.08 0-.092-.098l-1.477-7.253a.144.144 0 00-.048-.079.142.142 0 00-.087-.031h-2.104c-.074 0-.098.036-.074.11l2.178 9.301c0 .062.061.099.117.099h3.211c.049 0 .086-.037.111-.099l1.617-7.283c0-.062.031-.092.093-.092.055 0 .086 0 .098.092l1.655 7.283c0 .062.061.099.111.099h3.211c.055 0 .092-.037.116-.099l2.178-9.3c.016-.075-.01-.111-.08-.111z" fill="#CCC8C3"/><path d="M145.742 9.589a4.71 4.71 0 00-1.845.35 4.143 4.143 0 00-1.433.997 4.423 4.423 0 00-.923 1.55 6.308 6.308 0 000 4.005c.203.57.516 1.095.923 1.544a4.04 4.04 0 001.433 1.008 4.82 4.82 0 001.845.351 4.92 4.92 0 002.762-.763 3.381 3.381 0 001.464-2.116v-.037h-2.257a.117.117 0 00-.117.068 1.341 1.341 0 01-.744.689c-.357.128-.735.19-1.114.184a2.771 2.771 0 01-.923-.153 2.394 2.394 0 01-.719-.419 1.939 1.939 0 01-.468-.615 1.813 1.813 0 01-.166-.756.112.112 0 01.005-.049.116.116 0 01.06-.073.12.12 0 01.046-.014h6.563s.037 0 .056-.067c.024-.106.043-.213.055-.32 0-.105 0-.253.037-.437a5.892 5.892 0 00-.326-1.987 4.581 4.581 0 00-.923-1.556 4.04 4.04 0 00-1.433-1.01 4.709 4.709 0 00-1.858-.374zm2.104 3.924h-4.263a.104.104 0 01-.104-.053.109.109 0 01-.013-.04c.004-.211.05-.42.136-.614a1.66 1.66 0 01.424-.615 2.33 2.33 0 01.714-.45 2.544 2.544 0 011.002-.184 2.68 2.68 0 011.009.172c.258.104.492.259.689.455.175.174.31.384.394.616.08.195.124.404.129.615a.11.11 0 01-.098.11l-.019-.012z" fill="#CCC8C3"/><path d="M162.299 6.027h2.067c.025 0 .049.01.068.026a.1.1 0 01.031.066V19.16a.1.1 0 01-.029.07.1.1 0 01-.07.028h-2.067a.1.1 0 01-.092-.098V6.119a.092.092 0 01.092-.092z" fill="#CCC8C3"/><path d="M171.835 9.748h-1.932a.08.08 0 01-.062-.016.074.074 0 01-.021-.026.06.06 0 01-.009-.031v-2.56a.112.112 0 00-.001-.031.079.079 0 00-.014-.029.077.077 0 00-.053-.032h-2.092a.08.08 0 00-.092.068v2.534a.092.092 0 01-.074.098h-1.562a.085.085 0 00-.098.068V11.6a.083.083 0 00.018.06.089.089 0 00.055.032h1.563a.085.085 0 01.087.042c.006.01.01.02.011.032v4.817a2.67 2.67 0 00.658 2.03 3.077 3.077 0 002.141.615h1.464a.086.086 0 00.092-.074v-1.9a.083.083 0 00-.014-.064.082.082 0 00-.053-.035h-1.329a.721.721 0 01-.48-.16.54.54 0 01-.197-.437v-4.743a.088.088 0 01.074-.098h1.938a.075.075 0 00.06-.014.085.085 0 00.022-.024.079.079 0 00.01-.03V9.841a.068.068 0 00-.001-.032.077.077 0 00-.037-.05.079.079 0 00-.029-.01h-.043z" fill="#CCC8C3"/><path d="M181.527 10.911a3.424 3.424 0 00-1.23-.984 3.922 3.922 0 00-1.772-.37c-.274 0-.548.038-.812.111a4.78 4.78 0 00-.677.259 2.91 2.91 0 00-.498.289c-.094.07-.185.147-.27.228-.05.049-.093.067-.13.055-.037-.013-.055-.05-.055-.11v-4.27a.084.084 0 00-.019-.06.083.083 0 00-.055-.032h-2.091a.081.081 0 00-.061.014.08.08 0 00-.021.024.08.08 0 00-.011.03V19.16a.093.093 0 00.014.063.087.087 0 00.054.036h2.092a.092.092 0 00.098-.074v-5.34a2.789 2.789 0 01.111-.812c.069-.251.193-.484.363-.683.182-.2.404-.362.652-.473a2.33 2.33 0 01.941-.179c.33-.008.657.064.953.21a1.94 1.94 0 011.015 1.285c.073.273.112.554.117.837v5.13a.093.093 0 00.014.063.087.087 0 00.054.036h2.091a.087.087 0 00.093-.074V14.03a5.867 5.867 0 00-.234-1.692 4.18 4.18 0 00-.726-1.427z" fill="#CCC8C3"/><path d="M155.601 13.39a6.148 6.148 0 00-1.802.246c-.465.138-.9.364-1.28.665a2.676 2.676 0 00-1.015 2.116c-.004.406.071.81.222 1.187.15.364.379.69.67.954.339.28.73.488 1.15.615a5.21 5.21 0 001.655.233 4.311 4.311 0 001.846-.356c.228-.11.447-.235.658-.376.055-.043.104-.08.154-.067a.24.24 0 01.104.086l.142.424a.165.165 0 00.166.099h1.79c.049 0 .074 0 .074-.074v-6.035a3.195 3.195 0 00-1.132-2.657 4.862 4.862 0 00-3.076-.898 5.888 5.888 0 00-1.575.209 3.942 3.942 0 00-1.31.615 3.134 3.134 0 00-.898 1.027 3 3 0 00-.332 1.44c0 .049 0 .074.073.074h2.049a.067.067 0 00.054-.02.064.064 0 00.016-.025 1.451 1.451 0 01.127-.51 1.03 1.03 0 01.326-.399 1.67 1.67 0 01.572-.277c.293-.075.595-.11.898-.104.371-.014.742.038 1.095.153.232.072.443.199.615.37.122.124.209.28.252.449.026.125.042.253.049.381a.419.419 0 01-.147.295.616.616 0 01-.437.136l-1.753.024zm-.4 4.023a2.389 2.389 0 01-.72-.092 1.412 1.412 0 01-.455-.24.783.783 0 01-.24-.32.795.795 0 01-.067-.326c.001-.136.023-.27.067-.4a.819.819 0 01.289-.387 1.85 1.85 0 01.616-.283 3.48 3.48 0 011.008-.11h2.141a.105.105 0 01.117.085 1.937 1.937 0 01-.154.72 1.852 1.852 0 01-.486.658 2.59 2.59 0 01-.849.48 3.65 3.65 0 01-1.267.215z" fill="#CCC8C3"/><path d="M55.046 9.275a3.396 3.396 0 014.127-1.052c.356.178.676.418.948.708.225.234.406.507.535.806.117.27.197.473.246.615.05.141.061.098.11.098h2.166c.01.002.022.001.033-.002a.08.08 0 00.06-.072 2.986 2.986 0 00-.124-.732 4.405 4.405 0 00-.393-.947 6.331 6.331 0 00-.708-1.009 4.811 4.811 0 00-1.052-.91 5.825 5.825 0 00-1.433-.659 6.44 6.44 0 00-1.846-.252 5.967 5.967 0 00-2.596.53 5.137 5.137 0 00-1.845 1.451 6.354 6.354 0 00-1.107 2.202 9.925 9.925 0 00-.37 2.756c-.012.922.113 1.84.37 2.725.221.77.598 1.486 1.107 2.104a4.922 4.922 0 001.846 1.36c.823.336 1.706.5 2.596.48.66.011 1.317-.08 1.95-.271a5.493 5.493 0 001.47-.708 4.78 4.78 0 001.033-.96 6.4 6.4 0 00.665-1.02c.154-.293.274-.603.356-.923.056-.201.09-.407.105-.615a.078.078 0 00-.015-.06.08.08 0 00-.053-.033H61.02c-.05 0-.086.031-.11.093-.038.11-.118.301-.235.572a3.34 3.34 0 01-.547.818 3.377 3.377 0 01-.954.683 3.2 3.2 0 01-1.488.307 3.132 3.132 0 01-2.645-1.23 5.469 5.469 0 01-.93-3.371 7.318 7.318 0 01.247-1.969 4.86 4.86 0 01.689-1.513z" fill="#CCC8C3"/><path d="M62.133 24.795h-9.19a.087.087 0 00-.098.068v13.065a.087.087 0 00.074.099h2.091a.074.074 0 00.033-.002.08.08 0 00.06-.072v-5.536a.063.063 0 01.01-.05.061.061 0 01.045-.024h5.247a.086.086 0 00.098-.068v-1.944a.086.086 0 00-.074-.092h-5.253a.063.063 0 01-.05-.01.064.064 0 01-.024-.045v-3.291a.063.063 0 01.011-.05.061.061 0 01.045-.024h6.975a.078.078 0 00.06-.015.08.08 0 00.033-.053v-1.907a.08.08 0 00-.074-.092l-.018.043z" fill="#CCC8C3"/><path d="M71.146 28.486h-2.085a.086.086 0 00-.099.067V33.9a2.7 2.7 0 01-.11.824 2.037 2.037 0 01-.363.708 1.926 1.926 0 01-.652.498 2.148 2.148 0 01-.942.185 1.882 1.882 0 01-1.605-.782 2.21 2.21 0 01-.363-.769 3.074 3.074 0 01-.117-.855v-5.13a.082.082 0 00-.014-.06.081.081 0 00-.053-.032H62.65a.079.079 0 00-.06.014.08.08 0 00-.032.053v5.13a6.79 6.79 0 00.203 1.692c.129.52.366 1.006.695 1.428.344.424.782.763 1.28.99a4.712 4.712 0 001.974.363 2.6 2.6 0 001.175-.283c.325-.153.62-.36.873-.615.037-.056.074-.074.111-.062a.2.2 0 01.092.08l.191.56a.203.203 0 00.19.117h1.785a.082.082 0 00.062-.017.08.08 0 00.03-.057v-9.295c0-.065-.025-.098-.074-.098z" fill="#CCC8C3"/><path d="M83.94 24.05a1.353 1.353 0 00-1.354 1.348v.012a1.354 1.354 0 002.7 0 1.347 1.347 0 00-1.347-1.347v-.012z" fill="#CCC8C3"/><path d="M82.901 28.486h2.073a.092.092 0 01.092.092v9.32a.099.099 0 01-.092.098h-2.073a.099.099 0 01-.092-.098v-9.314a.092.092 0 01.092-.098z" fill="#CCC8C3"/><path d="M94.853 29.673a4.04 4.04 0 00-1.476-1.015 5.629 5.629 0 00-3.937 0 3.913 3.913 0 00-1.452 1.009c-.409.45-.721.98-.916 1.556a6.644 6.644 0 000 4.036 4.68 4.68 0 00.916 1.556c.4.444.897.791 1.452 1.015a5.629 5.629 0 003.937 0 3.967 3.967 0 001.476-1.015c.411-.45.725-.98.923-1.556a6.496 6.496 0 000-4.017 4.495 4.495 0 00-.923-1.569zm-1.624 5.678a2.504 2.504 0 01-3.61 0 3.937 3.937 0 010-4.208 2.46 2.46 0 013.61 0 3.937 3.937 0 010 4.208z" fill="#CCC8C3"/><path d="M105.343 29.654a3.214 3.214 0 00-1.23-.984 4.27 4.27 0 00-1.846-.369 2.464 2.464 0 00-.615.086 4.867 4.867 0 00-.615.21 3.39 3.39 0 00-.56.276 2.56 2.56 0 00-.406.314c-.037.05-.074.068-.11.055a.16.16 0 01-.093-.073l-.209-.616c0-.061-.068-.092-.172-.092h-1.784a.078.078 0 00-.06.015.08.08 0 00-.032.053v9.344a.087.087 0 00.067.098h2.092a.085.085 0 00.064-.016.086.086 0 00.034-.058v-5.34c0-.276.037-.55.11-.817.07-.261.194-.504.364-.714.177-.213.4-.383.652-.498a2.15 2.15 0 01.941-.19 1.908 1.908 0 011.606.78c.169.235.292.5.362.782.073.275.112.558.117.843v5.13a.093.093 0 00.014.063.087.087 0 00.054.035h2.092a.083.083 0 00.062-.017.075.075 0 00.021-.025.088.088 0 00.009-.032v-5.105a6.841 6.841 0 00-.203-1.692 4.163 4.163 0 00-.726-1.446z" fill="#CCC8C3"/><path d="M120.793 31.605a3.417 3.417 0 00-.922-.702 5.737 5.737 0 00-1.083-.443 7.934 7.934 0 00-1.132-.252 23.454 23.454 0 00-1.064-.141 6.62 6.62 0 01-.781-.111 4.218 4.218 0 01-.862-.258 1.744 1.744 0 01-.615-.456 1.03 1.03 0 01-.24-.713c-.003-.25.061-.497.185-.714.125-.235.295-.444.498-.615.218-.175.466-.308.732-.394.293-.099.601-.146.911-.141a3.17 3.17 0 011.045.16c.278.097.537.242.763.43.196.153.358.346.474.566a1.39 1.39 0 01.161.647.077.077 0 00.037.05c.009.006.019.01.029.01h2.178c.049 0 .074-.03.074-.091a3.251 3.251 0 00-.394-1.625 3.65 3.65 0 00-1.058-1.193 4.978 4.978 0 00-1.532-.744 6.2 6.2 0 00-1.808-.252 5.615 5.615 0 00-1.716.264 4.82 4.82 0 00-1.495.757c-.437.331-.799.75-1.064 1.23a3.385 3.385 0 00-.4 1.655 3.61 3.61 0 00.357 1.685c.235.457.585.845 1.015 1.126a5.099 5.099 0 001.587.677 11.81 11.81 0 001.907.301 3.925 3.925 0 012.085.658 1.414 1.414 0 01.431 1.926 1.946 1.946 0 01-.573.615 3.01 3.01 0 01-.861.418c-.35.112-.715.166-1.082.16a3.733 3.733 0 01-1.108-.148 3.347 3.347 0 01-.885-.387 1.887 1.887 0 01-.616-.554 1.144 1.144 0 01-.217-.69.067.067 0 00-.014-.03.08.08 0 00-.058-.03h-2.183a.08.08 0 00-.082.038.08.08 0 00-.011.03c.004.463.101.92.283 1.347.199.473.504.894.892 1.23a4.66 4.66 0 001.587.892 6.97 6.97 0 002.387.344 7.26 7.26 0 002.122-.276 4.59 4.59 0 001.6-.819 3.571 3.571 0 001.027-1.236c.241-.527.361-1.1.351-1.68.012-.5-.068-.998-.234-1.47a3.256 3.256 0 00-.628-1.051z" fill="#CCC8C3"/><path d="M131.615 28.486h-2.183c-.05 0-.087.03-.111.092l-2.276 7.29a.093.093 0 01-.111.067.093.093 0 01-.058-.018.088.088 0 01-.034-.05l-2.276-7.277c-.025-.061-.062-.092-.111-.092h-2.202c-.086 0-.117.03-.092.092l3.137 9.326c.08.246.141.48.19.701a1.36 1.36 0 010 .616.634.634 0 01-.27.418 1.232 1.232 0 01-.671.147h-2.257a.08.08 0 00-.082.038.103.103 0 00-.011.03v1.796a.082.082 0 00.042.083c.01.006.021.009.032.01h2.787a2.936 2.936 0 001.913-.585 3.807 3.807 0 001.126-1.784l.492-1.445 3.119-9.326c.037-.086.006-.13-.093-.13z" fill="#CCC8C3"/><path d="M147.198 28.486h-1.907a.075.075 0 01-.06-.014.085.085 0 01-.022-.024.079.079 0 01-.01-.03v-2.565a.082.082 0 00-.042-.083.08.08 0 00-.032-.01h-2.092a.08.08 0 00-.092.068v2.565a.085.085 0 01-.001.032.077.077 0 01-.037.05.077.077 0 01-.03.01h-1.581a.08.08 0 00-.092.068v1.809a.078.078 0 00.002.034.107.107 0 00.014.03.088.088 0 00.058.034h1.556a.083.083 0 01.061.015.081.081 0 01.021.023.08.08 0 01.011.03v4.817a2.677 2.677 0 00.658 2.03 3.075 3.075 0 002.141.615h1.47a.078.078 0 00.062-.017.076.076 0 00.021-.025.088.088 0 00.009-.032v-1.9a.082.082 0 00-.042-.083.082.082 0 00-.032-.01h-1.353a.751.751 0 01-.48-.16.519.519 0 01-.197-.443v-4.774a.084.084 0 01.001-.031.078.078 0 01.014-.029.077.077 0 01.053-.032h1.944a.082.082 0 00.033-.002.087.087 0 00.029-.014.074.074 0 00.021-.026.087.087 0 00.009-.032v-1.808a.082.082 0 00-.042-.083.082.082 0 00-.032-.01l-.012.007z" fill="#CCC8C3"/><path d="M168.767 28.295a3.074 3.074 0 00-1.507.338 4.06 4.06 0 00-.997.775c-.049.074-.098.074-.147 0a2.812 2.812 0 00-.985-.8 3.145 3.145 0 00-2.116-.227 5.05 5.05 0 00-.652.215 4.098 4.098 0 00-.56.296 2.23 2.23 0 00-.406.307c-.061.062-.116.05-.166-.037l-.19-.566a.192.192 0 00-.191-.11h-1.784a.075.075 0 00-.06.014.085.085 0 00-.022.023.079.079 0 00-.01.03v9.35a.068.068 0 00.001.032.077.077 0 00.037.05c.009.006.019.01.029.01h2.092a.087.087 0 00.065-.015.09.09 0 00.033-.058v-5.229a4.618 4.618 0 01.08-.916 2.15 2.15 0 01.283-.745c.129-.204.307-.373.517-.492a1.77 1.77 0 01.812-.178 1.57 1.57 0 01.812.21c.216.134.397.317.529.534.136.232.232.486.283.75.053.277.082.556.086.837v5.21a.085.085 0 00.001.032c.003.01.007.02.013.029a.093.093 0 00.024.021c.009.006.019.01.03.01h2.122a.08.08 0 00.092-.073v-5.229a5.12 5.12 0 01.086-.916 2.15 2.15 0 01.283-.745c.129-.204.307-.373.517-.492.25-.123.527-.184.806-.178.287-.006.57.066.818.21.214.136.395.318.529.534.135.233.228.487.277.75.056.276.085.556.086.837v5.21a.082.082 0 00.042.083.08.08 0 00.032.01h2.091a.083.083 0 00.063-.017.09.09 0 00.021-.025.088.088 0 00.009-.032v-5.34c0-1.463-.337-2.544-1.009-3.241a3.693 3.693 0 00-2.799-1.046z" fill="#CCC8C3"/><path d="M79.555 32.669a5.269 5.269 0 00-1.41-.394l-1.377-.178c-.21 0-.418-.056-.615-.093a4.11 4.11 0 01-.542-.141 1.04 1.04 0 01-.387-.234.524.524 0 01-.154-.393.75.75 0 01.43-.652 3.217 3.217 0 012.154-.123c.223.057.432.16.615.3.14.111.25.255.32.42a1.084 1.084 0 01.093.424.08.08 0 00.067.061h2.023a.075.075 0 00.059-.015.073.073 0 00.028-.052 3.194 3.194 0 00-.302-1.385 2.958 2.958 0 00-.843-1.027 3.586 3.586 0 00-1.28-.615 6.152 6.152 0 00-1.641-.21 5.14 5.14 0 00-1.588.235 3.99 3.99 0 00-1.23.615 2.768 2.768 0 00-.787.916 2.64 2.64 0 00.086 2.516c.242.353.567.64.947.837.413.21.855.356 1.31.437.492.086.985.153 1.465.203.19.03.4.049.615.067.208.019.414.058.615.117.181.048.35.136.492.258a.615.615 0 01.203.499.861.861 0 01-.197.578 1.23 1.23 0 01-.486.338c-.197.078-.404.13-.615.154-1.015.11-1.74 0-2.14-.363a1.157 1.157 0 01-.468-.836.104.104 0 000-.062.104.104 0 00-.062 0h-2.085a.098.098 0 00-.055 0 .08.08 0 000 .05 2.935 2.935 0 001.076 2.226c.396.323.846.573 1.329.738.595.196 1.219.29 1.845.277 1.07.06 2.132-.22 3.033-.8a2.669 2.669 0 001.107-2.3 2.461 2.461 0 00-.443-1.538 2.965 2.965 0 00-1.206-.855z" fill="#CCC8C3"/><path d="M181.899 33.512a2.966 2.966 0 00-1.101-.843 5.274 5.274 0 00-1.409-.394l-1.378-.178c-.209 0-.418-.056-.615-.093a3.862 3.862 0 01-.541-.14 1.044 1.044 0 01-.388-.235.524.524 0 01-.154-.393.75.75 0 01.431-.652 3.217 3.217 0 012.153-.123c.213.061.411.164.584.3.141.111.251.255.32.42a1.072 1.072 0 01.094.425c.002.01.007.02.013.028a.077.077 0 00.053.032h2.024a.073.073 0 00.06-.017.073.073 0 00.02-.025.084.084 0 00.006-.032 3.182 3.182 0 00-.301-1.384 2.968 2.968 0 00-.843-1.027 3.584 3.584 0 00-1.279-.615 6.161 6.161 0 00-1.643-.21 5.142 5.142 0 00-1.587.234 4.003 4.003 0 00-1.23.616 2.694 2.694 0 00-.788.916 2.643 2.643 0 00.086 2.516c.246.35.573.636.954.83.415.21.859.356 1.316.437.492.087.978.154 1.464.203.191.031.4.05.616.068.208.019.414.058.615.117.181.047.35.136.492.258a.62.62 0 01.203.498.863.863 0 01-.197.579 1.341 1.341 0 01-.486.338 2.46 2.46 0 01-.615.154c-1.015.11-1.741 0-2.141-.363a1.188 1.188 0 01-.474-.837.093.093 0 000-.061.104.104 0 00-.061 0h-2.085a.1.1 0 00-.056 0 .083.083 0 000 .049 2.887 2.887 0 001.077 2.227c.395.323.845.573 1.328.738a5.54 5.54 0 001.846.277 5.08 5.08 0 003.033-.8 2.69 2.69 0 001.107-2.3 2.46 2.46 0 00-.523-1.538z" fill="#CCC8C3"/><path d="M152.969 28.338a4.715 4.715 0 00-1.846.351 4.152 4.152 0 00-1.433.997c-.406.45-.72.977-.923 1.55a6.321 6.321 0 000 4.004c.203.571.517 1.096.923 1.544.4.436.889.78 1.433 1.01a4.83 4.83 0 001.846.35 4.922 4.922 0 002.762-.763 3.396 3.396 0 001.47-2.116v-.012h-2.264a.117.117 0 00-.117.067 1.307 1.307 0 01-.744.69 3.112 3.112 0 01-1.113.184 2.774 2.774 0 01-.923-.154 2.47 2.47 0 01-.72-.418 1.797 1.797 0 01-.633-1.372.13.13 0 01.004-.048.136.136 0 01.023-.043.121.121 0 01.083-.044h6.564s.043 0 .062-.068c.018-.068.036-.215.049-.32.012-.104 0-.252.037-.436a5.892 5.892 0 00-.326-1.987 4.408 4.408 0 00-.923-1.557 4.05 4.05 0 00-1.433-1.009 4.715 4.715 0 00-1.858-.4zm2.104 3.925h-4.239a.111.111 0 01-.117-.092 1.59 1.59 0 01.136-.615c.099-.223.243-.422.424-.585.21-.19.451-.342.714-.449.318-.128.659-.19 1.003-.184.346-.008.69.05 1.014.172.258.104.493.259.689.455a1.723 1.723 0 01.517 1.23.104.104 0 01-.092.111l-.049-.043z" fill="#CCC8C3"/><path d="M139.001 32.669a5.347 5.347 0 00-1.409-.394l-1.384-.178c-.209 0-.418-.056-.615-.093a4.136 4.136 0 01-.542-.141 1.04 1.04 0 01-.387-.234.517.517 0 01-.154-.394.75.75 0 01.431-.652 3.217 3.217 0 012.153-.122c.223.057.432.16.615.3.14.111.25.255.32.42a1.245 1.245 0 01.1.426.082.082 0 00.04.05.12.12 0 00.032.01h2.018c.01 0 .02 0 .03-.002a.077.077 0 00.028-.014.064.064 0 00.019-.023.076.076 0 00.009-.03 3.081 3.081 0 00-.302-1.384 2.954 2.954 0 00-.842-1.027 3.597 3.597 0 00-1.28-.615 6.149 6.149 0 00-1.642-.21 5.138 5.138 0 00-1.587.235 4.094 4.094 0 00-1.231.615 2.778 2.778 0 00-.787.916c-.18.346-.275.73-.277 1.12a2.46 2.46 0 00.363 1.396c.226.341.53.624.886.825.413.209.854.356 1.31.436.492.086.984.154 1.464.203.191.031.4.05.615.068.209.019.415.058.616.117.183.048.353.136.498.258a.616.616 0 01.197.498.863.863 0 01-.197.579 1.23 1.23 0 01-.486.338 2.41 2.41 0 01-.615.154c-1.015.11-1.741 0-2.141-.363a1.165 1.165 0 01-.468-.837.093.093 0 000-.061.097.097 0 00-.055 0h-2.091a.1.1 0 00-.056 0 2.94 2.94 0 001.077 2.276 4.23 4.23 0 001.335.738 5.452 5.452 0 001.845.277c1.07.06 2.132-.22 3.033-.8a2.665 2.665 0 001.107-2.3 2.46 2.46 0 00-.443-1.538 3 3 0 00-1.15-.843z" fill="#CCC8C3"/></svg>
@@ -1,8 +1,6 @@
1
1
  # Magnetic flux and flux density (B field)
2
2
 
3
- ::: cfsem.flux_circular_filament
4
-
5
- ::: cfsem.flux_density_biot_savart
3
+ ::: cfsem.flux_density_linear_filament
6
4
 
7
5
  ::: cfsem.flux_density_circular_filament
8
6
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## Mutual inductance
4
4
 
5
+ ::: cfsem.flux_circular_filament
6
+
5
7
  ::: cfsem.mutual_inductance_of_circular_filaments
6
8
 
7
9
  ::: cfsem.mutual_inductance_of_cylindrical_coils
@@ -0,0 +1,5 @@
1
+ # Vector Potential
2
+
3
+ ::: cfsem.vector_potential_circular_filament
4
+
5
+ ::: cfsem.vector_potential_linear_filament
@@ -0,0 +1,4 @@
1
+ :root {
2
+ --md-primary-fg-color: #152536;
3
+ --md-accent-fg-color: #AF5B3A;
4
+ }
@@ -1,7 +1,11 @@
1
1
  site_name: cfsem
2
- site_url: https://cfsem.readthedocs.io
2
+ site_url: https://cfsem-py.readthedocs.io
3
3
  theme:
4
4
  name: material
5
+ logo: assets/logo.svg
6
+ palette:
7
+ primary: custom
8
+ accent: custom
5
9
 
6
10
  plugins:
7
11
  - search
@@ -20,6 +24,7 @@ nav:
20
24
  - "Python API":
21
25
  - "python/filament.md"
22
26
  - "python/flux_density.md"
27
+ - "python/vector_potential.md"
23
28
  - "python/inductance.md"
24
29
  - "python/grad_shafranov.md"
25
30
  - "python/math.md"
@@ -46,6 +51,9 @@ extra_javascript:
46
51
  - https://polyfill.io/v3/polyfill.min.js?features=es6
47
52
  - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js
48
53
 
54
+ extra_css:
55
+ - stylesheets/extra.css
56
+
49
57
  watch:
50
58
  - cfsem
51
59
 
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "cfsem"
7
- version = "2.0.4"
7
+ version = "2.1.0"
8
8
  description = "Quasi-steady electromagnetics including filamentized approximations, Biot-Savart, and Grad-Shafranov."
9
9
  authors = [{author = "Commonwealth Fusion Systems", email = "jlogan@cfs.energy"}]
10
10
  requires-python = ">=3.9, <3.13"
@@ -16,7 +16,7 @@ classifiers = [
16
16
  dynamic = ["version"]
17
17
  dependencies = [
18
18
  "numpy >= 2",
19
- "interpn >= 0.2.2",
19
+ "interpn >= 0.2.3",
20
20
  ]
21
21
  license = "MIT"
22
22
 
@@ -25,7 +25,7 @@ dev = [
25
25
  # Tests
26
26
  "pytest >= 7.1.3",
27
27
  "coverage >= 6.5.0",
28
- "ruff >= 0.4.2",
28
+ "ruff >= 0.6.2",
29
29
  "pyright >= 1.1.331",
30
30
  "scipy >= 1.8",
31
31
  # Docs
@@ -128,6 +128,7 @@ fn flux_circular_filament<'py>(
128
128
  z: Bound<'py, PyArray1<f64>>,
129
129
  rprime: Bound<'py, PyArray1<f64>>,
130
130
  zprime: Bound<'py, PyArray1<f64>>,
131
+ par: bool,
131
132
  ) -> PyResult<Py<PyArray1<f64>>> {
132
133
  // Get references to contiguous data as slice
133
134
  // or error if data is not contiguous
@@ -148,8 +149,14 @@ fn flux_circular_filament<'py>(
148
149
  // Initialize output
149
150
  let mut psi = vec![0.0; m];
150
151
 
152
+ // Select variant
153
+ let func = match par {
154
+ true => physics::circular_filament::flux_circular_filament_par,
155
+ false => physics::circular_filament::flux_circular_filament,
156
+ };
157
+
151
158
  // Do calculations
152
- match physics::flux_circular_filament(current, r, z, rprime, zprime, &mut psi[..]) {
159
+ match func(current, r, z, rprime, zprime, &mut psi[..]) {
153
160
  Ok(_) => {}
154
161
  Err(x) => {
155
162
  let err: PyErr = PyInteropError::DimensionalityError { msg: x.to_string() }.into();
@@ -163,6 +170,56 @@ fn flux_circular_filament<'py>(
163
170
  })
164
171
  }
165
172
 
173
+ /// Python bindings for cfsemrs::physics::circular_filament::vector_potential_circular_filament
174
+ #[pyfunction]
175
+ fn vector_potential_circular_filament<'py>(
176
+ current: Bound<'py, PyArray1<f64>>,
177
+ r: Bound<'py, PyArray1<f64>>,
178
+ z: Bound<'py, PyArray1<f64>>,
179
+ rprime: Bound<'py, PyArray1<f64>>,
180
+ zprime: Bound<'py, PyArray1<f64>>,
181
+ par: bool,
182
+ ) -> PyResult<Py<PyArray1<f64>>> {
183
+ // Get references to contiguous data as slice
184
+ // or error if data is not contiguous
185
+ let current_readonly = current.readonly();
186
+ let current = current_readonly.as_slice()?;
187
+ let r_readonly = r.readonly();
188
+ let r = r_readonly.as_slice()?;
189
+ let z_readonly = z.readonly();
190
+ let z = z_readonly.as_slice()?;
191
+ let rprime_readonly = rprime.readonly();
192
+ let rprime = rprime_readonly.as_slice()?;
193
+ let zprime_readonly = zprime.readonly();
194
+ let zprime = zprime_readonly.as_slice()?;
195
+
196
+ // Get array shapes, make sure they make sense
197
+ let m = rprime.len();
198
+
199
+ // Initialize output
200
+ let mut out = vec![0.0; m];
201
+
202
+ // Select variant
203
+ let func = match par {
204
+ true => physics::circular_filament::vector_potential_circular_filament_par,
205
+ false => physics::circular_filament::vector_potential_circular_filament,
206
+ };
207
+
208
+ // Do calculations
209
+ match func(current, r, z, rprime, zprime, &mut out[..]) {
210
+ Ok(_) => {}
211
+ Err(x) => {
212
+ let err: PyErr = PyInteropError::DimensionalityError { msg: x.to_string() }.into();
213
+ return Err(err);
214
+ }
215
+ }
216
+
217
+ // Acquire global interpreter lock, which will be released when it goes out of scope
218
+ Python::with_gil(|py| {
219
+ Ok(PyArray1::from_vec_bound(py, out).unbind()) // Make PyObject
220
+ })
221
+ }
222
+
166
223
  /// Python bindings for cfsemrs::physics::flux_density_circular_filament
167
224
  #[pyfunction]
168
225
  fn flux_density_circular_filament<'py>(
@@ -171,6 +228,7 @@ fn flux_density_circular_filament<'py>(
171
228
  zfil: Bound<'py, PyArray1<f64>>,
172
229
  rprime: Bound<'py, PyArray1<f64>>,
173
230
  zprime: Bound<'py, PyArray1<f64>>,
231
+ par: bool,
174
232
  ) -> PyResult<(Py<PyArray1<f64>>, Py<PyArray1<f64>>)> {
175
233
  // Get references to contiguous data as slice
176
234
  // or error if data is not contiguous
@@ -190,10 +248,14 @@ fn flux_density_circular_filament<'py>(
190
248
  let mut br = vec![0.0; n];
191
249
  let mut bz = vec![0.0; n];
192
250
 
251
+ // Select variant
252
+ let func = match par {
253
+ true => physics::circular_filament::flux_density_circular_filament_par,
254
+ false => physics::circular_filament::flux_density_circular_filament,
255
+ };
256
+
193
257
  // Do calculations
194
- match physics::flux_density_circular_filament(
195
- &current, &rfil, &zfil, &rprime, &zprime, &mut br, &mut bz,
196
- ) {
258
+ match func(&current, &rfil, &zfil, &rprime, &zprime, &mut br, &mut bz) {
197
259
  Ok(_) => {}
198
260
  Err(x) => {
199
261
  let err: PyErr = PyInteropError::DimensionalityError { msg: x.to_string() }.into();
@@ -210,9 +272,9 @@ fn flux_density_circular_filament<'py>(
210
272
  })
211
273
  }
212
274
 
213
- /// Python bindings for cfsemrs::physics::biotsavart::flux_density_biot_savart
275
+ /// Python bindings for cfsemrs::physics::linear_filament::flux_density_linear_filament
214
276
  #[pyfunction]
215
- fn flux_density_biot_savart<'py>(
277
+ fn flux_density_linear_filament<'py>(
216
278
  xyzp: (
217
279
  Bound<'py, PyArray1<f64>>,
218
280
  Bound<'py, PyArray1<f64>>,
@@ -259,8 +321,8 @@ fn flux_density_biot_savart<'py>(
259
321
  let (mut bx, mut by, mut bz) = (vec![0.0; n], vec![0.0; n], vec![0.0; n]);
260
322
 
261
323
  let func = match par {
262
- true => physics::biotsavart::flux_density_biot_savart_par,
263
- false => physics::biotsavart::flux_density_biot_savart,
324
+ true => physics::linear_filament::flux_density_linear_filament_par,
325
+ false => physics::linear_filament::flux_density_linear_filament,
264
326
  };
265
327
  match func(xyzp, xyzfil, dlxyzfil, ifil, (&mut bx, &mut by, &mut bz)) {
266
328
  Ok(x) => x,
@@ -280,6 +342,82 @@ fn flux_density_biot_savart<'py>(
280
342
  })
281
343
  }
282
344
 
345
+ /// Python bindings for cfsemrs::physics::linear_filament::vector_potential_linear_filament
346
+ #[pyfunction]
347
+ fn vector_potential_linear_filament<'py>(
348
+ xyzp: (
349
+ Bound<'py, PyArray1<f64>>,
350
+ Bound<'py, PyArray1<f64>>,
351
+ Bound<'py, PyArray1<f64>>,
352
+ ), // [m] Test point coords
353
+ xyzfil: (
354
+ Bound<'py, PyArray1<f64>>,
355
+ Bound<'py, PyArray1<f64>>,
356
+ Bound<'py, PyArray1<f64>>,
357
+ ), // [m] Filament origin coords (start of segment)
358
+ dlxyzfil: (
359
+ Bound<'py, PyArray1<f64>>,
360
+ Bound<'py, PyArray1<f64>>,
361
+ Bound<'py, PyArray1<f64>>,
362
+ ), // [m] Filament length delta
363
+ ifil: Bound<'py, PyArray1<f64>>, // [A] filament current
364
+ par: bool,
365
+ ) -> PyResult<(Py<PyArray1<f64>>, Py<PyArray1<f64>>, Py<PyArray1<f64>>)> {
366
+ // Get references to contiguous data as slice
367
+ // or error if data is not contiguous
368
+ let xpro = xyzp.0.readonly();
369
+ let ypro = xyzp.1.readonly();
370
+ let zpro = xyzp.2.readonly();
371
+ let xyzp = (xpro.as_slice()?, ypro.as_slice()?, zpro.as_slice()?);
372
+
373
+ let xfilro = xyzfil.0.readonly();
374
+ let yfilro = xyzfil.1.readonly();
375
+ let zfilro = xyzfil.2.readonly();
376
+ let xyzfil = (xfilro.as_slice()?, yfilro.as_slice()?, zfilro.as_slice()?);
377
+
378
+ let dlxfilro = dlxyzfil.0.readonly();
379
+ let dlyfilro = dlxyzfil.1.readonly();
380
+ let dlzfilro = dlxyzfil.2.readonly();
381
+ let dlxyzfil = (
382
+ dlxfilro.as_slice()?,
383
+ dlyfilro.as_slice()?,
384
+ dlzfilro.as_slice()?,
385
+ );
386
+ let ifilro = ifil.readonly();
387
+ let ifil = ifilro.as_slice()?;
388
+
389
+ // Do calculations
390
+ let n = xyzp.0.len();
391
+ let (mut outx, mut outy, mut outz) = (vec![0.0; n], vec![0.0; n], vec![0.0; n]);
392
+
393
+ let func = match par {
394
+ true => physics::linear_filament::vector_potential_linear_filament_par,
395
+ false => physics::linear_filament::vector_potential_linear_filament,
396
+ };
397
+ match func(
398
+ xyzp,
399
+ xyzfil,
400
+ dlxyzfil,
401
+ ifil,
402
+ (&mut outx, &mut outy, &mut outz),
403
+ ) {
404
+ Ok(x) => x,
405
+ Err(x) => {
406
+ let err: PyErr = PyInteropError::DimensionalityError { msg: x.to_string() }.into();
407
+ return Err(err);
408
+ }
409
+ };
410
+
411
+ // Acquire global interpreter lock, which will be released when it goes out of scope
412
+ Python::with_gil(|py| {
413
+ let bx: Py<PyArray1<f64>> = PyArray1::from_vec_bound(py, outx).unbind();
414
+ let by: Py<PyArray1<f64>> = PyArray1::from_vec_bound(py, outy).unbind();
415
+ let bz: Py<PyArray1<f64>> = PyArray1::from_vec_bound(py, outz).unbind();
416
+
417
+ Ok((bx, by, bz))
418
+ })
419
+ }
420
+
283
421
  #[pyfunction]
284
422
  fn inductance_piecewise_linear_filaments<'py>(
285
423
  xyzfil0: (
@@ -430,15 +568,27 @@ fn ellipk(x: f64) -> f64 {
430
568
  fn _cfsem<'py>(_py: Python, m: Bound<'py, PyModule>) -> PyResult<()> {
431
569
  m.add_function(wrap_pyfunction!(flux_circular_filament, m.clone())?)?;
432
570
  m.add_function(wrap_pyfunction!(flux_density_circular_filament, m.clone())?)?;
433
- m.add_function(wrap_pyfunction!(flux_density_biot_savart, m.clone())?)?;
571
+ m.add_function(wrap_pyfunction!(
572
+ vector_potential_circular_filament,
573
+ m.clone()
574
+ )?)?;
575
+
576
+ m.add_function(wrap_pyfunction!(flux_density_linear_filament, m.clone())?)?;
577
+ m.add_function(wrap_pyfunction!(
578
+ vector_potential_linear_filament,
579
+ m.clone()
580
+ )?)?;
434
581
  m.add_function(wrap_pyfunction!(
435
582
  inductance_piecewise_linear_filaments,
436
583
  m.clone()
437
584
  )?)?;
585
+
438
586
  m.add_function(wrap_pyfunction!(gs_operator_order2, m.clone())?)?;
439
587
  m.add_function(wrap_pyfunction!(gs_operator_order4, m.clone())?)?;
588
+
440
589
  m.add_function(wrap_pyfunction!(ellipe, m.clone())?)?;
441
590
  m.add_function(wrap_pyfunction!(ellipk, m.clone())?)?;
591
+
442
592
  m.add_function(wrap_pyfunction!(filament_helix_path, m.clone())?)?;
443
593
  m.add_function(wrap_pyfunction!(rotate_filaments_about_path, m.clone())?)?;
444
594
  Ok(())
@@ -39,12 +39,13 @@ def test_self_inductance_piecewise_linear_filaments(r, z, h):
39
39
  @mark.parametrize("r1", [0.5, np.pi])
40
40
  @mark.parametrize("r2", [0.1, np.pi / 10.0])
41
41
  @mark.parametrize("z", [0.0, np.e / 2, -np.e / 2])
42
- def test_mutual_inductance_piecewise_linear_filaments(r1, r2, z):
42
+ @mark.parametrize("par", [True, False])
43
+ def test_mutual_inductance_piecewise_linear_filaments(r1, r2, z, par):
43
44
  # Test against calc for mutual inductance of circular filaments
44
45
  rzn1 = np.array([[r1], [z], [1.0]])
45
46
  rzn2 = np.array([[r2], [-z / np.e], [1.0]])
46
47
 
47
- m_circular = cfsem.mutual_inductance_of_circular_filaments(rzn1, rzn2)
48
+ m_circular = cfsem.mutual_inductance_of_circular_filaments(rzn1, rzn2, par)
48
49
 
49
50
  n = 100
50
51
 
@@ -126,7 +127,7 @@ def test_biot_savart_against_flux_density_circular_filament(r, z, par):
126
127
  # Circular filament calc
127
128
  # [T]
128
129
  Br_circular, Bz_circular = cfsem.flux_density_circular_filament(
129
- np.ones(1), np.array([r]), np.array([z]), rprime, zprime
130
+ np.ones(1), np.array([r]), np.array([z]), rprime, zprime, par
130
131
  )
131
132
 
132
133
  # Biot-Savart calc
@@ -149,7 +150,10 @@ def test_biot_savart_against_flux_density_circular_filament(r, z, par):
149
150
 
150
151
  @mark.parametrize("r", [0.775, np.pi])
151
152
  @mark.parametrize("z", [0.0, np.e / 2, -np.e / 2])
152
- def test_flux_circular_filament_against_mutual_inductance_of_cylindrical_coils(r, z):
153
+ @mark.parametrize("par", [True, False])
154
+ def test_flux_circular_filament_against_mutual_inductance_of_cylindrical_coils(
155
+ r, z, par
156
+ ):
153
157
  # Two single-turn coils with irrelevant cross-section,
154
158
  # each discretized into a single filament
155
159
  rc1 = r # Coil center radii
@@ -165,7 +169,7 @@ def test_flux_circular_filament_against_mutual_inductance_of_cylindrical_coils(r
165
169
  # Calculate mutual inductance between these two filaments
166
170
  f1 = np.array((r1, z1, n1))
167
171
  f2 = np.array((r2, z2, n2))
168
- m_filaments = cfsem.mutual_inductance_of_cylindrical_coils(f1, f2)
172
+ m_filaments = cfsem.mutual_inductance_of_cylindrical_coils(f1, f2, par)
169
173
 
170
174
  # Calculate mutual inductance via python test calc
171
175
  # and test the mutual inductance of coils calc.
@@ -175,8 +179,8 @@ def test_flux_circular_filament_against_mutual_inductance_of_cylindrical_coils(r
175
179
  assert abs(1 - m_filaments / m_filaments_test) < 1e-6
176
180
 
177
181
  # Do flux calcs
178
- psi_2to1 = np.sum(n1 * cfsem.flux_circular_filament(n2, r2, z2, r1, z1))
179
- psi_1to2 = np.sum(n2 * cfsem.flux_circular_filament(n1, r1, z1, r2, z2))
182
+ psi_2to1 = np.sum(n1 * cfsem.flux_circular_filament(n2, r2, z2, r1, z1, par))
183
+ psi_1to2 = np.sum(n2 * cfsem.flux_circular_filament(n1, r1, z1, r2, z2, par))
180
184
 
181
185
  # Because the integrated poloidal flux at a given location is the same as mutual inductance,
182
186
  # we should get the same number using our mutual inductance calc
@@ -191,7 +195,8 @@ def test_flux_circular_filament_against_mutual_inductance_of_cylindrical_coils(r
191
195
 
192
196
  @mark.parametrize("r", [0.775, np.pi])
193
197
  @mark.parametrize("z", [0.0, np.e / 2, -np.e / 2])
194
- def test_flux_density_circular_filament_against_flux_circular_filament(r, z):
198
+ @mark.parametrize("par", [True, False])
199
+ def test_flux_density_circular_filament_against_flux_circular_filament(r, z, par):
195
200
  rzn1 = cfsem.filament_coil(r, z, 0.05, 0.05, 1.0, 4, 4)
196
201
  rfil, zfil, _ = rzn1.T
197
202
  ifil = np.ones_like(rfil)
@@ -204,7 +209,7 @@ def test_flux_density_circular_filament_against_flux_circular_filament(r, z):
204
209
  zprime = Z.flatten()
205
210
 
206
211
  Br, Bz = cfsem.flux_density_circular_filament(
207
- ifil, rfil, zfil, rprime, zprime
212
+ ifil, rfil, zfil, rprime, zprime, par
208
213
  ) # [T]
209
214
 
210
215
  # We can also get B from the derivative of the flux function (Wesson eqn 3.2.2),
@@ -216,12 +221,12 @@ def test_flux_density_circular_filament_against_flux_circular_filament(r, z):
216
221
 
217
222
  dr = 1e-4
218
223
  dz = 1e-4
219
- psi = cfsem.flux_circular_filament(ifil, rfil, zfil, rprime, zprime)
224
+ psi = cfsem.flux_circular_filament(ifil, rfil, zfil, rprime, zprime, par)
220
225
  dpsidz = (
221
- cfsem.flux_circular_filament(ifil, rfil, zfil, rprime, zprime + dz) - psi
226
+ cfsem.flux_circular_filament(ifil, rfil, zfil, rprime, zprime + dz, par) - psi
222
227
  ) / dz
223
228
  dpsidr = (
224
- cfsem.flux_circular_filament(ifil, rfil, zfil, rprime + dr, zprime) - psi
229
+ cfsem.flux_circular_filament(ifil, rfil, zfil, rprime + dr, zprime, par) - psi
225
230
  ) / dr
226
231
 
227
232
  Br_from_psi = -dpsidz / rprime / (2.0 * np.pi) # [T]
@@ -232,7 +237,8 @@ def test_flux_density_circular_filament_against_flux_circular_filament(r, z):
232
237
 
233
238
 
234
239
  @mark.parametrize("r", [np.e / 100, 0.775, np.pi])
235
- def test_flux_density_circular_filament_against_ideal_solenoid(r):
240
+ @mark.parametrize("par", [True, False])
241
+ def test_flux_density_circular_filament_against_ideal_solenoid(r, par):
236
242
  # We can also check against the ideal solenoid calc to make sure we don't have a systematic
237
243
  # offset or scaling error
238
244
 
@@ -245,14 +251,15 @@ def test_flux_density_circular_filament_against_ideal_solenoid(r):
245
251
  current=1.0, num_turns=ifil.size, length=length
246
252
  ) # [T] ideal solenoid Bz at origin
247
253
  _, bz_origin = cfsem.flux_density_circular_filament(
248
- ifil, rfil, zfil, np.zeros(1), np.zeros(1)
254
+ ifil, rfil, zfil, np.zeros(1), np.zeros(1), par
249
255
  )
250
256
 
251
257
  assert np.allclose(np.array([b_ideal]), bz_origin, rtol=1e-2)
252
258
 
253
259
 
254
260
  @mark.parametrize("r", [np.e / 100, 0.775, np.pi])
255
- def test_flux_density_circular_filament_against_ideal_loop(r):
261
+ @mark.parametrize("par", [True, False])
262
+ def test_flux_density_circular_filament_against_ideal_loop(r, par):
256
263
  # We can also check against an ideal current loop calc
257
264
  # http://hyperphysics.phy-astr.gsu.edu/hbase/magnetic/curloo.html
258
265
 
@@ -263,7 +270,7 @@ def test_flux_density_circular_filament_against_ideal_loop(r):
263
270
 
264
271
  b_ideal = cfsem.MU_0 * current / (2.0 * r) # [T] ideal loop Bz at origin
265
272
  _, bz_origin = cfsem.flux_density_circular_filament(
266
- ifil, rfil, zfil, np.zeros(1), np.zeros(1)
273
+ ifil, rfil, zfil, np.zeros(1), np.zeros(1), par
267
274
  )
268
275
 
269
276
  assert np.allclose(np.array([b_ideal]), bz_origin, rtol=1e-6)
@@ -271,7 +278,8 @@ def test_flux_density_circular_filament_against_ideal_loop(r):
271
278
 
272
279
  @mark.parametrize("a", [0.775, np.pi])
273
280
  @mark.parametrize("z", [0.0, np.e / 2, -np.e / 2])
274
- def test_flux_density_circular_filament_against_numerical(a, z):
281
+ @mark.parametrize("par", [True, False])
282
+ def test_flux_density_circular_filament_against_numerical(a, z, par):
275
283
  # Test the elliptic-integral calc for B-field of a loop against numerical integration
276
284
 
277
285
  n = 10
@@ -286,7 +294,7 @@ def test_flux_density_circular_filament_against_numerical(a, z):
286
294
 
287
295
  # Calc using elliptic integral fits
288
296
  Br, Bz = cfsem.flux_density_circular_filament(
289
- np.array([I]), np.array([a]), np.array([z]), rprime, zprime
297
+ np.array([I]), np.array([a]), np.array([z]), rprime, zprime, par
290
298
  ) # [T]
291
299
 
292
300
  # Calc using numerical integration around the loop
@@ -302,7 +310,8 @@ def test_flux_density_circular_filament_against_numerical(a, z):
302
310
  assert np.allclose(Bz, Bz_num)
303
311
 
304
312
 
305
- def test_self_inductance_lyle6_against_filamentization_and_distributed():
313
+ @mark.parametrize("par", [True, False])
314
+ def test_self_inductance_lyle6_against_filamentization_and_distributed(par):
306
315
  # Test that the Lyle approximation gives a similar result to
307
316
  # a case done by brute-force filamentization w/ a heuristic for self-inductance of a loop
308
317
  r, z, dr, dz, nt, nr, nz = (0.8, 0.0, 0.5, 2.0, 3.0, 20, 20)
@@ -323,11 +332,11 @@ def test_self_inductance_lyle6_against_filamentization_and_distributed():
323
332
  # Do filamentized psi and B calcs for convenience,
324
333
  # although ideally we'd do a grad-shafranov solve here for a smoother field
325
334
  psi = cfsem.flux_circular_filament(
326
- current, rfil, zfil, rmesh.flatten(), zmesh.flatten()
335
+ current, rfil, zfil, rmesh.flatten(), zmesh.flatten(), par
327
336
  )
328
337
  psi = psi.reshape(rmesh.shape)
329
338
  br, bz = cfsem.flux_density_circular_filament(
330
- current, rfil, zfil, rmesh.flatten(), zmesh.flatten()
339
+ current, rfil, zfil, rmesh.flatten(), zmesh.flatten(), par
331
340
  )
332
341
  br = br.reshape(rmesh.shape)
333
342
  bz = bz.reshape(rmesh.shape)
@@ -505,3 +514,77 @@ def test_self_inductance_annular_ring(major_radius, a, b):
505
514
  with raises(ValueError):
506
515
  # Larger outer than major
507
516
  cfsem.self_inductance_annular_ring(0.1, 0.01, 0.11)
517
+
518
+
519
+ @mark.parametrize("r", [0.775, 1.51])
520
+ @mark.parametrize("z", [0.0, np.pi])
521
+ @mark.parametrize("par", [True, False])
522
+ def test_vector_potential_axisymmetric(r, z, par):
523
+ # Spot-check vector potential against inductance calcs.
524
+ # More detailed three-way tests against both B-field and inductance
525
+ # are done in the Rust library.
526
+
527
+ # Filament
528
+ ifil = np.atleast_1d([1.0]) # [A] 1A total reference current
529
+ rfil = np.atleast_1d([r])
530
+ zfil = np.atleast_1d([z])
531
+
532
+ # Observation points
533
+ rgrid = np.arange(0.5, 2.0, 0.05)
534
+ zgrid = np.arange(-3.0, 3.0, 0.05)
535
+ rmesh, zmesh = np.meshgrid(rgrid, zgrid, indexing="ij")
536
+
537
+ psi = cfsem.flux_circular_filament(
538
+ ifil, rfil, zfil, rmesh.flatten(), zmesh.flatten(), par
539
+ )
540
+ a_phi = cfsem.vector_potential_circular_filament(
541
+ ifil, rfil, zfil, rmesh.flatten(), zmesh.flatten(), par
542
+ )
543
+
544
+ # Integrate vector potential around a loop to get the flux
545
+ psi_from_a = 2.0 * np.pi * rmesh.flatten() * a_phi # [Wb]
546
+
547
+ # We should not be able to tell the difference above a single roundoff
548
+ assert np.allclose(psi, psi_from_a, rtol=1e-16, atol=1e-16)
549
+
550
+
551
+ @mark.parametrize("r", [0.775, np.pi])
552
+ @mark.parametrize("z", [0.0, np.e / 2, -np.e / 2])
553
+ @mark.parametrize("par", [True, False])
554
+ def test_vector_potential_linear_against_circular_filament(r, z, par):
555
+ # Note we are mapping between (x, y, z) and (r, phi, z) coordinates here
556
+
557
+ # Biot-Savart filaments in cartesian coords
558
+ n_filaments = int(1e4)
559
+ phi = np.linspace(0.0, 2.0 * np.pi, n_filaments)
560
+ xfils = r * np.cos(phi)
561
+ yfils = r * np.sin(phi)
562
+ zfils = np.ones_like(xfils) * z
563
+
564
+ # Observation grid
565
+ rs = np.linspace(0.01, r - 0.1, 10)
566
+ zs = np.linspace(-1.0, 1.0, 10)
567
+
568
+ rmesh, zmesh = np.meshgrid(rs, zs, indexing="ij")
569
+ rprime = rmesh.flatten()
570
+ zprime = zmesh.flatten()
571
+
572
+ # Circular filament calc
573
+ a_phi = cfsem.vector_potential_circular_filament(
574
+ np.ones(1), np.array([r]), np.array([z]), rprime, zprime, par
575
+ ) # [V-s/m]
576
+
577
+ # Biot-Savart calc
578
+ xyzp = (rprime, np.zeros_like(zprime), zprime)
579
+ xyzfil = (xfils[1:], yfils[1:], zfils[1:])
580
+ dlxyzfil = (xfils[1:] - xfils[:-1], yfils[1:] - yfils[:-1], zfils[1:] - zfils[:-1])
581
+ ifil = np.ones_like(xfils[1:])
582
+ ax, ay, az = cfsem.vector_potential_linear_filament(
583
+ xyzp, xyzfil, dlxyzfil, ifil, par
584
+ ) # [V-s/m]
585
+
586
+ assert np.allclose(a_phi, ay, rtol=1e-12, atol=1e-12) # Should match circular calc
587
+ assert np.allclose(
588
+ az, np.zeros_like(az), atol=1e-9
589
+ ) # Should sum to zero everywhere
590
+ assert np.allclose(ax, np.zeros_like(ax), atol=1e-9) # ...
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes