cfsem 2.4.3__tar.gz → 2.6.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 (70) hide show
  1. {cfsem-2.4.3 → cfsem-2.6.0}/.github/workflows/test_python.yml +4 -1
  2. cfsem-2.6.0/CHANGELOG.md +32 -0
  3. cfsem-2.4.3/CHANGELOG_PYTHON.md → cfsem-2.6.0/CHANGELOG_PYTHON_ARCHIVE.md +11 -0
  4. cfsem-2.4.3/CHANGELOG_RUST.md → cfsem-2.6.0/CHANGELOG_RUST_ARCHIVE.md +1 -1
  5. {cfsem-2.4.3 → cfsem-2.6.0}/Cargo.lock +133 -22
  6. {cfsem-2.4.3 → cfsem-2.6.0}/Cargo.toml +5 -5
  7. cfsem-2.6.0/PKG-INFO +27 -0
  8. cfsem-2.6.0/cfsem/solenoid_stress/solenoid_1d.py +317 -0
  9. cfsem-2.6.0/cfsem/solenoid_stress/solenoid_handcalc.py +91 -0
  10. cfsem-2.6.0/cfsem/solenoid_stress/thick_wall_cylinder_handcalc.py +69 -0
  11. cfsem-2.6.0/docs/python/solenoid_stress.md +5 -0
  12. {cfsem-2.4.3 → cfsem-2.6.0}/examples/helmholtz.py +3 -1
  13. {cfsem-2.4.3 → cfsem-2.6.0}/examples/inductance.py +1 -0
  14. {cfsem-2.4.3 → cfsem-2.6.0}/mkdocs.yml +2 -0
  15. {cfsem-2.4.3 → cfsem-2.6.0}/pyproject.toml +5 -2
  16. {cfsem-2.4.3 → cfsem-2.6.0}/src/lib.rs +1 -1
  17. {cfsem-2.4.3 → cfsem-2.6.0}/src/math.rs +29 -0
  18. {cfsem-2.4.3 → cfsem-2.6.0}/src/physics/circular_filament.rs +1 -1
  19. {cfsem-2.4.3 → cfsem-2.6.0}/src/physics/linear_filament.rs +20 -9
  20. cfsem-2.6.0/test/__init__.py +0 -0
  21. {cfsem-2.4.3 → cfsem-2.6.0}/test/test_electromagnetics.py +2 -2
  22. {cfsem-2.4.3 → cfsem-2.6.0}/test/test_funcs.py +2 -1
  23. cfsem-2.6.0/test/test_solenoid_stress.py +290 -0
  24. {cfsem-2.4.3 → cfsem-2.6.0}/uv.lock +240 -110
  25. cfsem-2.4.3/PKG-INFO +0 -124
  26. {cfsem-2.4.3 → cfsem-2.6.0}/.cargo/config.toml +0 -0
  27. {cfsem-2.4.3 → cfsem-2.6.0}/.github/workflows/release_python.yml +0 -0
  28. {cfsem-2.4.3 → cfsem-2.6.0}/.github/workflows/release_rust.yml +0 -0
  29. {cfsem-2.4.3 → cfsem-2.6.0}/.github/workflows/test_rust.yml +0 -0
  30. {cfsem-2.4.3 → cfsem-2.6.0}/.gitignore +0 -0
  31. {cfsem-2.4.3 → cfsem-2.6.0}/.readthedocs.yml +0 -0
  32. {cfsem-2.4.3 → cfsem-2.6.0}/CONTRIBUTING.md +0 -0
  33. {cfsem-2.4.3 → cfsem-2.6.0}/LICENSE +0 -0
  34. {cfsem-2.4.3 → cfsem-2.6.0}/README.md +0 -0
  35. {cfsem-2.4.3 → cfsem-2.6.0}/benches/circular_filament.rs +0 -0
  36. {cfsem-2.4.3 → cfsem-2.6.0}/benches/linear_filament.rs +0 -0
  37. {cfsem-2.4.3 → cfsem-2.6.0}/build_docs.sh +0 -0
  38. {cfsem-2.4.3 → cfsem-2.6.0}/cfsem/__init__.py +0 -0
  39. {cfsem-2.4.3 → cfsem-2.6.0}/cfsem/bindings.py +0 -0
  40. {cfsem-2.4.3 → cfsem-2.6.0}/cfsem/py.typed +0 -0
  41. {cfsem-2.4.3/test → cfsem-2.6.0/cfsem/solenoid_stress}/__init__.py +0 -0
  42. {cfsem-2.4.3 → cfsem-2.6.0}/cfsem/types.py +0 -0
  43. {cfsem-2.4.3 → cfsem-2.6.0}/docs/assets/logo.svg +0 -0
  44. {cfsem-2.4.3 → cfsem-2.6.0}/docs/index.md +0 -0
  45. {cfsem-2.4.3 → cfsem-2.6.0}/docs/javascripts/mathjax.js +0 -0
  46. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/example_outputs/.placeholder +0 -0
  47. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/examples.md +0 -0
  48. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/filament.md +0 -0
  49. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/flux_density.md +0 -0
  50. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/force.md +0 -0
  51. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/grad_shafranov.md +0 -0
  52. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/inductance.md +0 -0
  53. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/math.md +0 -0
  54. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/types.md +0 -0
  55. {cfsem-2.4.3 → cfsem-2.6.0}/docs/python/vector_potential.md +0 -0
  56. {cfsem-2.4.3 → cfsem-2.6.0}/docs/requirements.txt +0 -0
  57. {cfsem-2.4.3 → cfsem-2.6.0}/docs/stylesheets/extra.css +0 -0
  58. {cfsem-2.4.3 → cfsem-2.6.0}/katex-header.html +0 -0
  59. {cfsem-2.4.3 → cfsem-2.6.0}/src/mesh.rs +0 -0
  60. {cfsem-2.4.3 → cfsem-2.6.0}/src/physics/biotsavart.rs +0 -0
  61. {cfsem-2.4.3 → cfsem-2.6.0}/src/physics/gradshafranov.rs +0 -0
  62. {cfsem-2.4.3 → cfsem-2.6.0}/src/physics/mesh_filament.rs +0 -0
  63. {cfsem-2.4.3 → cfsem-2.6.0}/src/physics/mod.rs +0 -0
  64. {cfsem-2.4.3 → cfsem-2.6.0}/src/physics/point_source.rs +0 -0
  65. {cfsem-2.4.3 → cfsem-2.6.0}/src/python.rs +0 -0
  66. {cfsem-2.4.3 → cfsem-2.6.0}/src/testing.rs +0 -0
  67. {cfsem-2.4.3 → cfsem-2.6.0}/test/test_examples.py +0 -0
  68. {cfsem-2.4.3 → cfsem-2.6.0}/test/test_filaments.py +0 -0
  69. {cfsem-2.4.3 → cfsem-2.6.0}/test/test_gradshafranov.py +0 -0
  70. {cfsem-2.4.3 → cfsem-2.6.0}/test/test_math.py +0 -0
@@ -5,7 +5,10 @@ name: test_python
5
5
 
6
6
  on:
7
7
  # push: [] # Save time on actions
8
- pull_request: []
8
+ pull_request:
9
+ branches: [ "*" ]
10
+ push:
11
+ branches: [ "main", "develop", "release" ]
9
12
  workflow_dispatch:
10
13
  tag: "Manual Run"
11
14
  workflow_call:
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ See archived changelogs for versions prior to 2.6.0.
4
+
5
+ ## 2.6.0 2025-10-15
6
+
7
+ Substantial performance improvement for `flux_density_linear_filament` Biot-Savart methods.
8
+ This also improves performance in calculations that use these methods, such as linear filament
9
+ body force density calcs.
10
+
11
+ ### Added
12
+
13
+ * Rust
14
+ * Add `dot3f` and `cross3f` 32-bit float variants
15
+
16
+ ### Changed
17
+
18
+ * Rust
19
+ * Use mixed-precision method for `flux_density_linear_filament_scalar`
20
+ * High-dynamic-range part of the calc is still done using 64-bit floats
21
+ * Low-dynamic-range part of the calc is now done using 32-bit floats
22
+ * _All_ addition operations in 32-bit section are done using
23
+ fused multiply-add operations, usually chained to defer
24
+ roundoff to final operation. As a result, total roundoff error
25
+ accumulated in this section is minimal.
26
+ * Return is upcast back to 64-bit float to support precise summation downstream
27
+ * 1.4-2x speedup without any meaningful loss of precision
28
+ * No change to unit test tolerances needed; unlike an all-32-bit implementation,
29
+ this mixed-precision method passes all the same tests as the 64-bit-only method
30
+ * Python
31
+ * Update dep versions
32
+ * Use latest rust backend version, which includes 1.4-2x speedup for flux_density_linear_filament Biot-Savart calcs
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.5.0 2025-08-05
4
+
5
+ ### Added
6
+
7
+ * Add `solenoid_stress` module
8
+ * 1D finite-difference solver for solenoid stress given arbitrary B-field, current density, and surface load
9
+ * Generalizes over both long-solenoid stress and ideal thick-walled pressure vessel stress
10
+ * Tested against handcalcs for both ideal solenoid & thick-walled pressure vessel
11
+ * 1D handcalc for solenoid stress under uniform current density and linear B-field profile
12
+ * 1D handcalc for thick-walled pressure vessel
13
+
3
14
  ## 2.4.3 2025-07-07
4
15
 
5
16
  ### Changed
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 2.4.3 2025-07-07
3
+ ## 2.4.3 2025-07-07, 2.5.0 2025-08-05
4
4
 
5
5
  ### Changed
6
6
 
@@ -64,7 +64,7 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
64
64
 
65
65
  [[package]]
66
66
  name = "cfsem"
67
- version = "2.4.3"
67
+ version = "2.6.0"
68
68
  dependencies = [
69
69
  "criterion",
70
70
  "itertools 0.14.0",
@@ -201,6 +201,102 @@ version = "1.15.0"
201
201
  source = "registry+https://github.com/rust-lang/crates.io-index"
202
202
  checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
203
203
 
204
+ [[package]]
205
+ name = "glam"
206
+ version = "0.14.0"
207
+ source = "registry+https://github.com/rust-lang/crates.io-index"
208
+ checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da"
209
+
210
+ [[package]]
211
+ name = "glam"
212
+ version = "0.15.2"
213
+ source = "registry+https://github.com/rust-lang/crates.io-index"
214
+ checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b"
215
+
216
+ [[package]]
217
+ name = "glam"
218
+ version = "0.16.0"
219
+ source = "registry+https://github.com/rust-lang/crates.io-index"
220
+ checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16"
221
+
222
+ [[package]]
223
+ name = "glam"
224
+ version = "0.17.3"
225
+ source = "registry+https://github.com/rust-lang/crates.io-index"
226
+ checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776"
227
+
228
+ [[package]]
229
+ name = "glam"
230
+ version = "0.18.0"
231
+ source = "registry+https://github.com/rust-lang/crates.io-index"
232
+ checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34"
233
+
234
+ [[package]]
235
+ name = "glam"
236
+ version = "0.19.0"
237
+ source = "registry+https://github.com/rust-lang/crates.io-index"
238
+ checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca"
239
+
240
+ [[package]]
241
+ name = "glam"
242
+ version = "0.20.5"
243
+ source = "registry+https://github.com/rust-lang/crates.io-index"
244
+ checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f"
245
+
246
+ [[package]]
247
+ name = "glam"
248
+ version = "0.21.3"
249
+ source = "registry+https://github.com/rust-lang/crates.io-index"
250
+ checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815"
251
+
252
+ [[package]]
253
+ name = "glam"
254
+ version = "0.22.0"
255
+ source = "registry+https://github.com/rust-lang/crates.io-index"
256
+ checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774"
257
+
258
+ [[package]]
259
+ name = "glam"
260
+ version = "0.23.0"
261
+ source = "registry+https://github.com/rust-lang/crates.io-index"
262
+ checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
263
+
264
+ [[package]]
265
+ name = "glam"
266
+ version = "0.24.2"
267
+ source = "registry+https://github.com/rust-lang/crates.io-index"
268
+ checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"
269
+
270
+ [[package]]
271
+ name = "glam"
272
+ version = "0.25.0"
273
+ source = "registry+https://github.com/rust-lang/crates.io-index"
274
+ checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
275
+
276
+ [[package]]
277
+ name = "glam"
278
+ version = "0.27.0"
279
+ source = "registry+https://github.com/rust-lang/crates.io-index"
280
+ checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9"
281
+
282
+ [[package]]
283
+ name = "glam"
284
+ version = "0.28.0"
285
+ source = "registry+https://github.com/rust-lang/crates.io-index"
286
+ checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94"
287
+
288
+ [[package]]
289
+ name = "glam"
290
+ version = "0.29.3"
291
+ source = "registry+https://github.com/rust-lang/crates.io-index"
292
+ checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
293
+
294
+ [[package]]
295
+ name = "glam"
296
+ version = "0.30.8"
297
+ source = "registry+https://github.com/rust-lang/crates.io-index"
298
+ checksum = "e12d847aeb25f41be4c0ec9587d624e9cd631bc007a8fd7ce3f5851e064c6460"
299
+
204
300
  [[package]]
205
301
  name = "half"
206
302
  version = "2.6.0"
@@ -319,11 +415,27 @@ dependencies = [
319
415
 
320
416
  [[package]]
321
417
  name = "nalgebra"
322
- version = "0.33.2"
418
+ version = "0.34.1"
323
419
  source = "registry+https://github.com/rust-lang/crates.io-index"
324
- checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b"
420
+ checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f"
325
421
  dependencies = [
326
422
  "approx",
423
+ "glam 0.14.0",
424
+ "glam 0.15.2",
425
+ "glam 0.16.0",
426
+ "glam 0.17.3",
427
+ "glam 0.18.0",
428
+ "glam 0.19.0",
429
+ "glam 0.20.5",
430
+ "glam 0.21.3",
431
+ "glam 0.22.0",
432
+ "glam 0.23.0",
433
+ "glam 0.24.2",
434
+ "glam 0.25.0",
435
+ "glam 0.27.0",
436
+ "glam 0.28.0",
437
+ "glam 0.29.3",
438
+ "glam 0.30.8",
327
439
  "matrixmultiply",
328
440
  "nalgebra-macros",
329
441
  "num-complex",
@@ -335,9 +447,9 @@ dependencies = [
335
447
 
336
448
  [[package]]
337
449
  name = "nalgebra-macros"
338
- version = "0.2.2"
450
+ version = "0.3.0"
339
451
  source = "registry+https://github.com/rust-lang/crates.io-index"
340
- checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc"
452
+ checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889"
341
453
  dependencies = [
342
454
  "proc-macro2",
343
455
  "quote",
@@ -410,9 +522,9 @@ dependencies = [
410
522
 
411
523
  [[package]]
412
524
  name = "numpy"
413
- version = "0.25.0"
525
+ version = "0.26.0"
414
526
  source = "registry+https://github.com/rust-lang/crates.io-index"
415
- checksum = "29f1dee9aa8d3f6f8e8b9af3803006101bb3653866ef056d530d53ae68587191"
527
+ checksum = "9b2dba356160b54f5371b550575b78130a54718b4c6e46b3f33a6da74a27e78b"
416
528
  dependencies = [
417
529
  "libc",
418
530
  "ndarray",
@@ -496,9 +608,9 @@ dependencies = [
496
608
 
497
609
  [[package]]
498
610
  name = "pyo3"
499
- version = "0.25.1"
611
+ version = "0.26.0"
500
612
  source = "registry+https://github.com/rust-lang/crates.io-index"
501
- checksum = "8970a78afe0628a3e3430376fc5fd76b6b45c4d43360ffd6cdd40bdde72b682a"
613
+ checksum = "7ba0117f4212101ee6544044dae45abe1083d30ce7b29c4b5cbdfa2354e07383"
502
614
  dependencies = [
503
615
  "indoc",
504
616
  "libc",
@@ -513,19 +625,18 @@ dependencies = [
513
625
 
514
626
  [[package]]
515
627
  name = "pyo3-build-config"
516
- version = "0.25.1"
628
+ version = "0.26.0"
517
629
  source = "registry+https://github.com/rust-lang/crates.io-index"
518
- checksum = "458eb0c55e7ece017adeba38f2248ff3ac615e53660d7c71a238d7d2a01c7598"
630
+ checksum = "4fc6ddaf24947d12a9aa31ac65431fb1b851b8f4365426e182901eabfb87df5f"
519
631
  dependencies = [
520
- "once_cell",
521
632
  "target-lexicon",
522
633
  ]
523
634
 
524
635
  [[package]]
525
636
  name = "pyo3-ffi"
526
- version = "0.25.1"
637
+ version = "0.26.0"
527
638
  source = "registry+https://github.com/rust-lang/crates.io-index"
528
- checksum = "7114fe5457c61b276ab77c5055f206295b812608083644a5c5b2640c3102565c"
639
+ checksum = "025474d3928738efb38ac36d4744a74a400c901c7596199e20e45d98eb194105"
529
640
  dependencies = [
530
641
  "libc",
531
642
  "pyo3-build-config",
@@ -533,9 +644,9 @@ dependencies = [
533
644
 
534
645
  [[package]]
535
646
  name = "pyo3-macros"
536
- version = "0.25.1"
647
+ version = "0.26.0"
537
648
  source = "registry+https://github.com/rust-lang/crates.io-index"
538
- checksum = "a8725c0a622b374d6cb051d11a0983786448f7785336139c3c94f5aa6bef7e50"
649
+ checksum = "2e64eb489f22fe1c95911b77c44cc41e7c19f3082fc81cce90f657cdc42ffded"
539
650
  dependencies = [
540
651
  "proc-macro2",
541
652
  "pyo3-macros-backend",
@@ -545,9 +656,9 @@ dependencies = [
545
656
 
546
657
  [[package]]
547
658
  name = "pyo3-macros-backend"
548
- version = "0.25.1"
659
+ version = "0.26.0"
549
660
  source = "registry+https://github.com/rust-lang/crates.io-index"
550
- checksum = "4109984c22491085343c05b0dbc54ddc405c3cf7b4374fc533f5c3313a572ccc"
661
+ checksum = "100246c0ecf400b475341b8455a9213344569af29a3c841d29270e53102e0fcf"
551
662
  dependencies = [
552
663
  "heck",
553
664
  "proc-macro2",
@@ -573,9 +684,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
573
684
 
574
685
  [[package]]
575
686
  name = "rayon"
576
- version = "1.10.0"
687
+ version = "1.11.0"
577
688
  source = "registry+https://github.com/rust-lang/crates.io-index"
578
- checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
689
+ checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
579
690
  dependencies = [
580
691
  "either",
581
692
  "rayon-core",
@@ -583,9 +694,9 @@ dependencies = [
583
694
 
584
695
  [[package]]
585
696
  name = "rayon-core"
586
- version = "1.12.1"
697
+ version = "1.13.0"
587
698
  source = "registry+https://github.com/rust-lang/crates.io-index"
588
- checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
699
+ checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
589
700
  dependencies = [
590
701
  "crossbeam-deque",
591
702
  "crossbeam-utils",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "cfsem"
3
- version = "2.4.3"
3
+ version = "2.6.0"
4
4
  edition = "2024"
5
5
  authors = ["Commonwealth Fusion Systems <jlogan@cfs.energy>"]
6
6
  license = "MIT"
@@ -15,11 +15,11 @@ name = "cfsem"
15
15
  crate-type = ["cdylib", "rlib"]
16
16
 
17
17
  [dependencies]
18
- pyo3 = { version="0.25.1", features=["extension-module"], optional=true }
19
- numpy = { version="0.25.0", optional=true } # This must match pyo3 version!
18
+ pyo3 = { version="0.26.0", features=["extension-module"], optional=true }
19
+ numpy = { version="0.26.0", optional=true } # This must match pyo3 version!
20
20
 
21
- nalgebra = "^0.33.2"
22
- rayon = "^1.10.0"
21
+ nalgebra = "^0.34.1"
22
+ rayon = "^1.11.0"
23
23
  libm = "^0.2"
24
24
  num-traits = { version = "0.2.19", features = ["libm"] }
25
25
 
cfsem-2.6.0/PKG-INFO ADDED
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: cfsem
3
+ Version: 2.6.0
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.6.1
9
+ Requires-Dist: findiff>=0.12.1
10
+ Requires-Dist: pydantic>=2
11
+ Requires-Dist: pydantic-numpy>=6
12
+ Requires-Dist: pytest>=7.1.3 ; extra == 'dev'
13
+ Requires-Dist: coverage>=6.5.0 ; extra == 'dev'
14
+ Requires-Dist: ruff>=0.6.2 ; extra == 'dev'
15
+ Requires-Dist: pyright==1.1.402 ; extra == 'dev'
16
+ Requires-Dist: scipy>=1.8 ; extra == 'dev'
17
+ Requires-Dist: mkdocs>=1.6.0 ; extra == 'dev'
18
+ Requires-Dist: mkdocs-material>=9.5.25 ; extra == 'dev'
19
+ Requires-Dist: mkdocstrings[python]>=0.25.1 ; extra == 'dev'
20
+ Requires-Dist: matplotlib>=3.9 ; extra == 'dev'
21
+ Provides-Extra: dev
22
+ License-File: LICENSE
23
+ Summary: Quasi-steady electromagnetics including filamentized approximations, Biot-Savart, and Grad-Shafranov.
24
+ Home-Page: https://github.com/cfs-energy/cfsem-py
25
+ Author-email: Commonwealth Fusion Systems <jlogan@cfs.energy>
26
+ License-Expression: MIT
27
+ Requires-Python: >=3.9, <3.14
@@ -0,0 +1,317 @@
1
+ """
2
+ Finite-difference solution to structural PDE for pancake or
3
+ infinite-length solenoid coil, following Iwasa 2e section 3.6.
4
+
5
+ Assumes
6
+ * Uniform current density within each r-section
7
+ * Zero R-Z shear (deck-of-cards structure)
8
+ * Zero Z-force,stress,strain (no axial compression accounted here; can be evaluated separately;
9
+ usually relatively small)
10
+ * Isotropic material
11
+ * This method could be extended to handle orthotropic material, with some effort
12
+ * Single material region
13
+ * This method could be extended to handle multiple regions of isotropic materials
14
+
15
+ Supports
16
+ * Non-uniform r-grid
17
+ * Arbitrary current density and B-field defined on r-grid
18
+ * Nonzero pressure on inner/outer wall (fluid pressure, bucking load, etc)
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from dataclasses import dataclass
24
+ from functools import cached_property
25
+ from logging import getLogger
26
+ from pathlib import Path
27
+ from typing import Callable, Literal
28
+
29
+ import findiff
30
+ import numpy as np
31
+ from numpy.typing import NDArray
32
+ from pydantic import ConfigDict
33
+ from pydantic_numpy.model import NumpyModel
34
+ from pydantic_numpy.typing import NpNDArray # Array of any type or dimensionality
35
+ from scipy import io, sparse
36
+ from scipy.sparse import csc_matrix as CSC
37
+ from scipy.sparse import csr_matrix as CSR
38
+ from scipy.sparse.linalg import factorized
39
+
40
+
41
+ def solenoid_1d_structural_factor(elasticity_modulus: float, poisson_ratio: float) -> float:
42
+ """Structural factor applied to RHS of solenoid stress solve"""
43
+ c = (1.0 - poisson_ratio**2) / elasticity_modulus # [m/N]
44
+ return c
45
+
46
+
47
+ def solenoid_1d_structural_rhs(
48
+ c: float,
49
+ j: NDArray | list[float],
50
+ bz: NDArray | list[float],
51
+ pi: float = 0.0,
52
+ po: float = 0.0,
53
+ ) -> NDArray:
54
+ """
55
+ Right-hand-side for solenoid stress solve,
56
+ including zero values at the BCs.
57
+
58
+ From Iwasa 2e eqn. 3.64a
59
+
60
+ Recommend padding the grid with a dummy value at either end
61
+ to make room for the BCs without losing accounting of nonzero
62
+ current density at the inner/outer radius.
63
+
64
+ Padding for BCs can be done like:
65
+ `rgrid = np.array([r0 - 1e-6] + rgrid.tolist() + [r1 + 1e-6])`
66
+
67
+ Padding region is ultimately treated as structural material,
68
+ so the padded region should be small to avoid introducing error,
69
+ but not so small that it causes numerical error in the finite difference scheme.
70
+
71
+ Args:
72
+ c: [m/N] scalar structural factor; see `solenoid_1d_structural_factor()`
73
+ j: [A/m^2] with shape (n x 1), current density at each point in the r-grid
74
+ bz: [T] with shape (n x 1), Z-axis B-field at each point in the r-grid
75
+ pi: [Pa] scalar pressure on inner wall, defined in +r direction
76
+ po: [Pa] scalar pressure on outer wall, defined in -r direction
77
+
78
+ Returns:
79
+ -c * j * bz, [1/m^2] with shape (n x 1), the right-hand side of the solenoid stress PDE
80
+ """
81
+ # Guarantee arrays
82
+ j = np.array(j)
83
+ bz = np.array(bz)
84
+
85
+ # RHS without BCs
86
+ rhs = -c * j * bz
87
+
88
+ # BC for r-stress at inner and outer radius
89
+ # is the fluid or mechanical pressure, which is usually going to be set to zero
90
+ # to represent an unsupported system, but could be set to a nonzero value
91
+ # to represent a surface load.
92
+ rhs[0] = -pi # Sign convention: compression is negative stress
93
+ rhs[-1] = -po
94
+
95
+ return rhs
96
+
97
+
98
+ class SolenoidStress1D(NumpyModel):
99
+ model_config = ConfigDict(validate_assignment=True, frozen=True, extra="forbid")
100
+
101
+ rgrid: NpNDArray
102
+ """[m] 1D grid of r-coordinates"""
103
+ elasticity_modulus: float
104
+ """[Pa] diagonal terms in material property matrix"""
105
+ poisson_ratio: float
106
+ """[dimensionless] factor determining off-diagonal terms in material property matrix"""
107
+ order: Literal[2, 4] = 4
108
+ """Finite-difference stencil polynomial order.
109
+ Higher order operators produce excessive numerical error under typical use."""
110
+ direct_inverse: bool = False
111
+ """Whether to generate fully-dense direct inverse of the system, which
112
+ can be useful as a linear operator. Alternatively, the system can be solved
113
+ using an LU solver with reduced memory usage and better numerical conditioning."""
114
+
115
+ @cached_property
116
+ def operators(self) -> SolenoidStress1DOperators:
117
+ """
118
+ Linear operators for solving stress and strain in a pancake coil
119
+ following Iwasa 2e section 3.6.
120
+ """
121
+ return solenoid_1d_structural_operators(
122
+ np.array(self.rgrid), self.elasticity_modulus, self.poisson_ratio, self.order, self.direct_inverse
123
+ )
124
+
125
+ @cached_property
126
+ def displacement_solver(self) -> Callable[[NDArray], NDArray]:
127
+ """LU solver for load-displacement relation (A_ub)
128
+ as an alternative to taking a direct inverse of A_bu"""
129
+ return factorized(self.operators.a_bu)
130
+
131
+
132
+ @dataclass(frozen=True)
133
+ class SolenoidStress1DOperators:
134
+ """
135
+ Linear operators for solving stress and strain in a pancake coil
136
+ following Iwasa 2e section 3.6.
137
+
138
+ A_bu, (n x n) sparse operator mapping displacement to the RHS like A @ u_r = -c * j * bz
139
+ A_ub, (n x n) fully-dense direct inverse of A_bu mapping RHS to displacement
140
+ A_eu (2n x n), A_eu_radial (n x n), A_eu_hoop (n x n), sparse operators mapping displacement to strain
141
+ * First entry is combined operator producing both strain components
142
+ * Second and third entries are split operators, which are equivalent because they are fully decoupled
143
+ A_se (2n x 2n), sparse operator mapping strain to stress
144
+ """
145
+
146
+ a_bu: CSC
147
+ """(n x n) sparse operator mapping displacement to the RHS like A @ u_r = -c * j * bz"""
148
+ a_ub: NDArray | None
149
+ """(n x n) fully-dense direct inverse of A_bu mapping RHS to displacement.
150
+ Only generated if `direct_inverse` flag is set."""
151
+ a_eu: CSR
152
+ """(2n x n), sparse operator mapping displacement to strain; contains both radial and hoop components"""
153
+ a_eu_radial: CSR
154
+ """(n x n), sparse operators mapping displacement to strain; radial component only"""
155
+ a_eu_hoop: CSR
156
+ """(n x n), sparse operators mapping displacement to strain; hoop component only"""
157
+ a_se: CSR
158
+ """(2n x 2n), sparse operator mapping strain to stress"""
159
+
160
+ def write_mat(self, dst: str | Path) -> str:
161
+ """Write the collection of operators in .mat format.
162
+
163
+ Args:
164
+ dst: Target directory to place the file named "stress_operators.mat"
165
+
166
+ Raises:
167
+ IOError: If the directory does not exist
168
+ """
169
+ # Check directory
170
+ dst = Path(dst).absolute()
171
+ fpath = dst / "stress_operators.mat"
172
+ getLogger("cfsem").info(f"Saving stress operator data to {fpath}")
173
+
174
+ to_save = {
175
+ "A_bu": self.a_bu,
176
+ "A_ub": self.a_ub,
177
+ "A_eu": self.a_eu,
178
+ "A_eu_radial": self.a_eu_radial,
179
+ "A_eu_hoop": self.a_eu_hoop,
180
+ "A_se": self.a_se,
181
+ }
182
+
183
+ if self.a_ub is None: # savemat fails on None value
184
+ to_save.pop("A_ub")
185
+
186
+ # Note this will implicitly convert all CSR matrices to CSC, which is .mat's preferred I/O
187
+ io.savemat(fpath, to_save)
188
+
189
+ return f"{fpath}"
190
+
191
+
192
+ def solenoid_1d_structural_operators(
193
+ rgrid: NDArray | list,
194
+ elasticity_modulus: float,
195
+ poisson_ratio: float,
196
+ order: Literal[2, 4],
197
+ direct_inverse: bool = False,
198
+ ) -> SolenoidStress1DOperators:
199
+ """
200
+ Linear operators for solving stress and strain in a pancake coil
201
+ following Iwasa 2e section 3.6.
202
+
203
+ Assumes
204
+ * Uniform current density within each r-section
205
+ * Zero R-Z shear (deck-of-cards structure)
206
+ * Zero Z-force,stress,strain (no axial compression accounted here; can be evaluated separately;
207
+ usually relatively small)
208
+ * Isotropic material
209
+ * This method could be extended to handle orthotropic material, with some effort
210
+ * Single material region
211
+ * This method could be extended to handle multiple regions of isotropic materials
212
+
213
+ Supports
214
+ * Non-uniform r-grid
215
+ * Arbitrary current density and B-field defined on r-grid
216
+
217
+ Args:
218
+ rgrid: [m] with shape (n x 1), Grid of r-coordinates. Must be sorted ascending.
219
+ elasticity_modulus: [N/m] Material property; Young's modulus
220
+ poisson_ratio: [dimensionless] Material property; off-axis stress coupling term
221
+ order: Finite-difference stencil polynomial order
222
+ direct_inverse: Whether to generate fully-dense direct inverse of the system, which
223
+ can be useful as a linear operator.
224
+ Alternatively, the system can be solved using an LU solver with
225
+ reduced memory usage and better numerical conditioning.
226
+
227
+ Returns:
228
+ object containing linear operators
229
+ """
230
+ # Guarantee arrays
231
+ rgrid = np.array(rgrid)
232
+ nr = len(rgrid)
233
+
234
+ #
235
+ # Stencils / differential operators
236
+ #
237
+
238
+ # Note these will include the 1/dr and 1/dr^2 scalings,
239
+ # not just the normalized stencil
240
+ ddr = findiff.Diff(0, rgrid, acc=order)
241
+ d2dr2 = ddr**2
242
+
243
+ ddr = ddr.matrix((nr,))
244
+ d2dr2 = d2dr2.matrix((nr,))
245
+
246
+ rinv = CSR(np.diag(1.0 / rgrid))
247
+ rinv2 = CSR(np.diag(1.0 / rgrid**2))
248
+
249
+ #
250
+ # Displacement-load relation
251
+ #
252
+
253
+ # The first and last row of the u-b relation will be modified later
254
+ # to include the boundary conditions
255
+ #
256
+ # Iwasa 2e eqn. 3.64a left hand side
257
+ #
258
+ # This one is converted to CSC because, while it's easiest to build it as CSR,
259
+ # it is more useful for solving a system as CSC
260
+ a_bu = CSC(d2dr2 + (rinv @ ddr) - rinv2) # Operator maps displacement TO rhs (-c*j*B)
261
+
262
+ # Stress-strain relation components
263
+ # that are needed for BCs
264
+ # Iwasa 2e eqns. 3.63g
265
+ a_stress_strain = np.array(
266
+ [
267
+ [1.0 / elasticity_modulus, -poisson_ratio / elasticity_modulus],
268
+ [-poisson_ratio / elasticity_modulus, 1.0 / elasticity_modulus],
269
+ ]
270
+ )
271
+ a_strain_stress = np.linalg.inv(a_stress_strain)
272
+ diag_term = a_strain_stress[0, 0]
273
+ off_diag_term = a_strain_stress[1, 0]
274
+
275
+ # We only need the first and last row of this matrix.
276
+ # Actualizing the whole thing is easy but unnecessary
277
+ # This is extracted by hand-calc starting from
278
+ # Iwasa 2e eqns. 3.63a-d
279
+ bcmat = diag_term * ddr + off_diag_term * rinv
280
+
281
+ # Apply BC to displacement operator
282
+ a_bu[0, :] = bcmat[0, :]
283
+ a_bu[-1, :] = bcmat[-1, :]
284
+
285
+ # Invert A_bu directly to get A_ub
286
+ # This is fully dense, which may be prohibitive in some situations
287
+ a_ub = (
288
+ None if not direct_inverse else np.linalg.inv(a_bu.todense())
289
+ ) # Operator maps RHS=-c*j*bz to displacement
290
+
291
+ #
292
+ # Strain-displacement operator(s)
293
+ #
294
+ # Iwasa 2e eqns. 3.63a-d
295
+ a_eu_hoop = rinv # Just the hoop part
296
+ a_eu_radial = ddr # Just the radial part
297
+ # fmt: off
298
+ a_eu = CSR(sparse.block_array(
299
+ [[a_eu_radial],
300
+ [a_eu_hoop]]
301
+ )) # Operator for getting strain from displacement
302
+ # fmt: on
303
+
304
+ #
305
+ # Stress-strain operators
306
+ #
307
+ # In this case, we're manually assembling the inverse of the A_es matrix
308
+ # because we know all the components from handcalc arithmetic
309
+ eye = sparse.eye(nr, nr)
310
+ # fmt: off
311
+ a_se = CSR(sparse.block_array(
312
+ [[ diag_term * eye, off_diag_term * eye],
313
+ [off_diag_term * eye, diag_term * eye]]
314
+ )) # Operator maps strain to stress
315
+ # fmt: on
316
+
317
+ return SolenoidStress1DOperators(a_bu, a_ub, a_eu, a_eu_radial, a_eu_hoop, a_se)