cavsim2d 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cavsim2d-0.1.0/LICENSE +21 -0
- cavsim2d-0.1.0/MANIFEST.in +6 -0
- cavsim2d-0.1.0/PKG-INFO +666 -0
- cavsim2d-0.1.0/README.md +622 -0
- cavsim2d-0.1.0/cavsim2d/__init__.py +83 -0
- cavsim2d-0.1.0/cavsim2d/analysis/__init__.py +0 -0
- cavsim2d-0.1.0/cavsim2d/analysis/impedance.py +366 -0
- cavsim2d-0.1.0/cavsim2d/analysis/lorentz/__init__.py +7 -0
- cavsim2d-0.1.0/cavsim2d/analysis/lorentz/pressure.py +226 -0
- cavsim2d-0.1.0/cavsim2d/analysis/multipacting/__init__.py +21 -0
- cavsim2d-0.1.0/cavsim2d/analysis/multipacting/driver.py +487 -0
- cavsim2d-0.1.0/cavsim2d/analysis/multipacting/fields.py +134 -0
- cavsim2d-0.1.0/cavsim2d/analysis/multipacting/integrators.py +825 -0
- cavsim2d-0.1.0/cavsim2d/analysis/multipacting/metrics.py +82 -0
- cavsim2d-0.1.0/cavsim2d/analysis/multipacting/particles.py +283 -0
- cavsim2d-0.1.0/cavsim2d/analysis/multipacting/plots.py +831 -0
- cavsim2d-0.1.0/cavsim2d/analysis/multipacting/sey.py +47 -0
- cavsim2d-0.1.0/cavsim2d/analysis/optimisation.py +1423 -0
- cavsim2d-0.1.0/cavsim2d/analysis/tune/__init__.py +0 -0
- cavsim2d-0.1.0/cavsim2d/analysis/tune/pyTuner.py +935 -0
- cavsim2d-0.1.0/cavsim2d/analysis/tune/tuner.py +207 -0
- cavsim2d-0.1.0/cavsim2d/analysis/uq/__init__.py +7 -0
- cavsim2d-0.1.0/cavsim2d/analysis/uq/models.py +140 -0
- cavsim2d-0.1.0/cavsim2d/analysis/uq/plots.py +229 -0
- cavsim2d-0.1.0/cavsim2d/analysis/uq/sobol_sa.py +204 -0
- cavsim2d-0.1.0/cavsim2d/analysis/wakefield/__init__.py +0 -0
- cavsim2d-0.1.0/cavsim2d/config_files/optimisation_config.py +56 -0
- cavsim2d-0.1.0/cavsim2d/config_files/uq_config.py +19 -0
- cavsim2d-0.1.0/cavsim2d/constants.py +119 -0
- cavsim2d-0.1.0/cavsim2d/data_module/__init__.py +0 -0
- cavsim2d-0.1.0/cavsim2d/data_module/abci_data.py +759 -0
- cavsim2d-0.1.0/cavsim2d/data_module/operating_points.py +311 -0
- cavsim2d-0.1.0/cavsim2d/data_module/sey/sey +23 -0
- cavsim2d-0.1.0/cavsim2d/geometry/__init__.py +35 -0
- cavsim2d-0.1.0/cavsim2d/geometry/beampipes.py +187 -0
- cavsim2d-0.1.0/cavsim2d/geometry/contours.py +209 -0
- cavsim2d-0.1.0/cavsim2d/geometry/plotting.py +653 -0
- cavsim2d-0.1.0/cavsim2d/geometry/primitives.py +152 -0
- cavsim2d-0.1.0/cavsim2d/geometry/profile.py +1241 -0
- cavsim2d-0.1.0/cavsim2d/geometry/tangency.py +390 -0
- cavsim2d-0.1.0/cavsim2d/geometry/writers/__init__.py +11 -0
- cavsim2d-0.1.0/cavsim2d/geometry/writers/cst.py +93 -0
- cavsim2d-0.1.0/cavsim2d/geometry/writers/gmsh.py +1719 -0
- cavsim2d-0.1.0/cavsim2d/geometry/writers/multipac.py +783 -0
- cavsim2d-0.1.0/cavsim2d/models/__init__.py +39 -0
- cavsim2d-0.1.0/cavsim2d/models/assembly.py +726 -0
- cavsim2d-0.1.0/cavsim2d/models/base.py +3510 -0
- cavsim2d-0.1.0/cavsim2d/models/beampipe.py +200 -0
- cavsim2d-0.1.0/cavsim2d/models/bellows.py +283 -0
- cavsim2d-0.1.0/cavsim2d/models/bla.py +203 -0
- cavsim2d-0.1.0/cavsim2d/models/elliptical.py +1524 -0
- cavsim2d-0.1.0/cavsim2d/models/elliptical_flattop.py +772 -0
- cavsim2d-0.1.0/cavsim2d/models/pillbox.py +326 -0
- cavsim2d-0.1.0/cavsim2d/models/rfgun.py +585 -0
- cavsim2d-0.1.0/cavsim2d/models/spline.py +567 -0
- cavsim2d-0.1.0/cavsim2d/models/taper.py +206 -0
- cavsim2d-0.1.0/cavsim2d/processes/__init__.py +5 -0
- cavsim2d-0.1.0/cavsim2d/processes/eigenmode.py +168 -0
- cavsim2d-0.1.0/cavsim2d/processes/tune.py +1259 -0
- cavsim2d-0.1.0/cavsim2d/processes/uq.py +728 -0
- cavsim2d-0.1.0/cavsim2d/processes/wakefield.py +423 -0
- cavsim2d-0.1.0/cavsim2d/solvers/ABCI/abci.py +148 -0
- cavsim2d-0.1.0/cavsim2d/solvers/NGSolve/__init__.py +0 -0
- cavsim2d-0.1.0/cavsim2d/solvers/NGSolve/eigen_ngsolve.py +3310 -0
- cavsim2d-0.1.0/cavsim2d/solvers/NGSolve/eigen_ports.py +615 -0
- cavsim2d-0.1.0/cavsim2d/solvers/NGSolve/frequency_domain_ngsolve.py +217 -0
- cavsim2d-0.1.0/cavsim2d/solvers/NGSolve/ports.py +266 -0
- cavsim2d-0.1.0/cavsim2d/solvers/__init__.py +16 -0
- cavsim2d-0.1.0/cavsim2d/solvers/eigenmode_result.py +97 -0
- cavsim2d-0.1.0/cavsim2d/solvers/objectives.py +186 -0
- cavsim2d-0.1.0/cavsim2d/solvers/solver_objects.py +4496 -0
- cavsim2d-0.1.0/cavsim2d/solvers/wakefield/__init__.py +33 -0
- cavsim2d-0.1.0/cavsim2d/solvers/wakefield/abci.py +139 -0
- cavsim2d-0.1.0/cavsim2d/solvers/wakefield/base.py +118 -0
- cavsim2d-0.1.0/cavsim2d/study.py +4274 -0
- cavsim2d-0.1.0/cavsim2d/utils/__init__.py +13 -0
- cavsim2d-0.1.0/cavsim2d/utils/config_validation.py +229 -0
- cavsim2d-0.1.0/cavsim2d/utils/cubature.py +709 -0
- cavsim2d-0.1.0/cavsim2d/utils/data_utils.py +337 -0
- cavsim2d-0.1.0/cavsim2d/utils/printing.py +106 -0
- cavsim2d-0.1.0/cavsim2d/utils/quadrature.py +852 -0
- cavsim2d-0.1.0/cavsim2d/utils/run_log.py +76 -0
- cavsim2d-0.1.0/cavsim2d/utils/sampling.py +402 -0
- cavsim2d-0.1.0/cavsim2d/utils/sensitivity.py +808 -0
- cavsim2d-0.1.0/cavsim2d/utils/shapes.py +449 -0
- cavsim2d-0.1.0/cavsim2d/utils/shared_classes.py +5 -0
- cavsim2d-0.1.0/cavsim2d/utils/shared_functions.py +30 -0
- cavsim2d-0.1.0/cavsim2d/utils/slope_triangles.py +165 -0
- cavsim2d-0.1.0/cavsim2d/utils/style.py +157 -0
- cavsim2d-0.1.0/cavsim2d/utils/surrogate.py +385 -0
- cavsim2d-0.1.0/cavsim2d/utils/welcome.py +40 -0
- cavsim2d-0.1.0/cavsim2d.egg-info/PKG-INFO +666 -0
- cavsim2d-0.1.0/cavsim2d.egg-info/SOURCES.txt +116 -0
- cavsim2d-0.1.0/cavsim2d.egg-info/dependency_links.txt +1 -0
- cavsim2d-0.1.0/cavsim2d.egg-info/requires.txt +28 -0
- cavsim2d-0.1.0/cavsim2d.egg-info/top_level.txt +1 -0
- cavsim2d-0.1.0/pyproject.toml +66 -0
- cavsim2d-0.1.0/setup.cfg +4 -0
- cavsim2d-0.1.0/tests/test_api_guards.py +132 -0
- cavsim2d-0.1.0/tests/test_assembly.py +672 -0
- cavsim2d-0.1.0/tests/test_cavity_types.py +121 -0
- cavsim2d-0.1.0/tests/test_config_validation.py +120 -0
- cavsim2d-0.1.0/tests/test_dielectric.py +790 -0
- cavsim2d-0.1.0/tests/test_eigenmode.py +1204 -0
- cavsim2d-0.1.0/tests/test_geometry.py +881 -0
- cavsim2d-0.1.0/tests/test_impedance.py +423 -0
- cavsim2d-0.1.0/tests/test_multipacting.py +649 -0
- cavsim2d-0.1.0/tests/test_null.py +2 -0
- cavsim2d-0.1.0/tests/test_objectives.py +158 -0
- cavsim2d-0.1.0/tests/test_optimisation.py +203 -0
- cavsim2d-0.1.0/tests/test_plot_compare.py +95 -0
- cavsim2d-0.1.0/tests/test_plotting_style.py +124 -0
- cavsim2d-0.1.0/tests/test_slope_triangles.py +43 -0
- cavsim2d-0.1.0/tests/test_standalone.py +49 -0
- cavsim2d-0.1.0/tests/test_sweep.py +124 -0
- cavsim2d-0.1.0/tests/test_tune.py +528 -0
- cavsim2d-0.1.0/tests/test_uq.py +445 -0
- cavsim2d-0.1.0/tests/test_wakefield.py +482 -0
cavsim2d-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2022] [Sosoho-Abasi Udongwo]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# ABCI is free to use but not open source, and KEK distributes it only from
|
|
2
|
+
# https://abci.kek.jp/abci.htm. A local copy kept in a checkout (gitignored) must
|
|
3
|
+
# never reach the sdist or the wheel, even when an old egg-info manifest lists it.
|
|
4
|
+
exclude cavsim2d/solvers/ABCI/*.exe
|
|
5
|
+
prune cavsim2d/solvers/ABCI/TopDrawer*
|
|
6
|
+
global-exclude *.py[cod]
|
cavsim2d-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cavsim2d
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Quick 2D axisymmetric RF cavity analysis: eigenmode, tuning, wakefield, UQ and optimisation.
|
|
5
|
+
Author-email: Sosoho-Abasi Udongwo <numurho@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Dark-Elektron/cavsim2d
|
|
8
|
+
Project-URL: Repository, https://github.com/Dark-Elektron/cavsim2d
|
|
9
|
+
Keywords: rf-cavity,accelerator,eigenmode,wakefield,finite-element,ngsolve
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.26.4
|
|
19
|
+
Requires-Dist: scipy>=1.13.1
|
|
20
|
+
Requires-Dist: pandas>=2.2.2
|
|
21
|
+
Requires-Dist: matplotlib>=3.8.4
|
|
22
|
+
Requires-Dist: ngsolve>=6.2.2402
|
|
23
|
+
Requires-Dist: gmsh>=4.13
|
|
24
|
+
Requires-Dist: paretoset>=1.2.4
|
|
25
|
+
Requires-Dist: termcolor>=2.1.0
|
|
26
|
+
Requires-Dist: tqdm>=4.66.4
|
|
27
|
+
Requires-Dist: openpyxl
|
|
28
|
+
Requires-Dist: jinja2
|
|
29
|
+
Requires-Dist: joblib
|
|
30
|
+
Requires-Dist: scikit-learn>=1.6
|
|
31
|
+
Requires-Dist: SALib
|
|
32
|
+
Provides-Extra: jupyter
|
|
33
|
+
Requires-Dist: ipython>=8.26.0; extra == "jupyter"
|
|
34
|
+
Requires-Dist: ipywidgets>=8.1.3; extra == "jupyter"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest; extra == "dev"
|
|
37
|
+
Requires-Dist: flake8; extra == "dev"
|
|
38
|
+
Provides-Extra: docs
|
|
39
|
+
Requires-Dist: sphinx-immaterial>=0.11; extra == "docs"
|
|
40
|
+
Requires-Dist: sphinxcontrib-bibtex; extra == "docs"
|
|
41
|
+
Requires-Dist: numpydoc; extra == "docs"
|
|
42
|
+
Requires-Dist: myst-nb; extra == "docs"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+

|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
# cavsim2d
|
|
50
|
+
|
|
51
|
+
`cavsim2d` is a Python toolkit for designing and analysing 2D axisymmetric RF
|
|
52
|
+
structures. It provides, through one small object-oriented API:
|
|
53
|
+
|
|
54
|
+
- **Eigenmode analysis** — fundamental (monopole) *and* higher-order-mode
|
|
55
|
+
(dipole, quadrupole, sextupole, …) passbands, with the usual figures of merit
|
|
56
|
+
(frequency, R/Q, G, Q, Epk/Eacc, Bpk/Eacc, field flatness, cell-to-cell
|
|
57
|
+
coupling).
|
|
58
|
+
- **Frequency tuning** to a target frequency.
|
|
59
|
+
- **Wakefield / impedance analysis** via the ABCI solver.
|
|
60
|
+
- **Uncertainty quantification (UQ)** on any of the above.
|
|
61
|
+
- **Multi-objective shape optimisation.**
|
|
62
|
+
- **Plotting and comparison** helpers for all of the above.
|
|
63
|
+
|
|
64
|
+
The eigenmode solver is built on [NGSolve](https://ngsolve.org); wakefield
|
|
65
|
+
analysis uses the free [ABCI](https://abci.kek.jp/abci.htm) code, which you
|
|
66
|
+
download from KEK yourself (see [Installation](#installation)).
|
|
67
|
+
|
|
68
|
+
## Cavity types and what is supported
|
|
69
|
+
|
|
70
|
+
Every cavity type supports every analysis; the notes below the table cover the
|
|
71
|
+
caveats:
|
|
72
|
+
|
|
73
|
+
| Cavity type | Eigenmode | Tuning | Wakefield | UQ / Optimisation |
|
|
74
|
+
|---|---|---|---|---|
|
|
75
|
+
| `EllipticalCavity` | ✅ | ✅ | ✅ | ✅ |
|
|
76
|
+
| `EllipticalCavityFlatTop` | ✅ | ✅ | ✅ | ✅ |
|
|
77
|
+
| `Pillbox` | ✅ | ✅ | ✅ | ✅ |
|
|
78
|
+
| `SplineCavity` | ✅ | ✅² | ✅ | ✅² |
|
|
79
|
+
| `RFGun` | ✅¹ | ✅ | ✅ | ✅ |
|
|
80
|
+
|
|
81
|
+
✅ supported and exercised · ¹ ² see notes.
|
|
82
|
+
|
|
83
|
+
Every type builds its mesh from the same `Profile` blueprint, implements a single
|
|
84
|
+
`rebuild()` hook, and declares its own tunable parameters via `tune_variables()`.
|
|
85
|
+
Adding a geometry therefore needs no edit to any central list:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
tesla.tune_variables() # {'A', 'B', 'a', 'b', 'Ri', 'L', 'Req'}
|
|
89
|
+
gun.tune_variables() # {'y1', 'R2', 'T2', 'L3', ..., 'R6', ..., 'x'}
|
|
90
|
+
spline.tune_variables() # {'p0_z', 'p0_r', ..., 'p5_z', 'p5_r'}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Any of those names can be a tune variable (`tune_config['cell_type']`) or a UQ /
|
|
94
|
+
optimisation variable. A geometry imported from a mesh or CAD file has no
|
|
95
|
+
parameters, and says so rather than reporting an unknown variable name. Unless
|
|
96
|
+
noted, examples below use `EllipticalCavity`.
|
|
97
|
+
|
|
98
|
+
¹ RF-gun eigenmode gives correct frequencies and fields; the length-normalised
|
|
99
|
+
QOIs (`Eacc`, `Epk/Eacc`) default to the on-axis field extent as the active
|
|
100
|
+
length (override with `eigenmode_config['normalization_length']`, mm). Its
|
|
101
|
+
`Epk/Eacc` does not converge under mesh refinement — the cathode corner is a
|
|
102
|
+
field singularity, so treat that number as mesh-dependent.
|
|
103
|
+
|
|
104
|
+
² A `SplineCavity` is parameterised by control points rather than by scalars, so
|
|
105
|
+
its handles are the point *coordinates*: `'p3_r'` moves the radial coordinate of
|
|
106
|
+
`p3` and leaves `p3_z` alone.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Installation
|
|
111
|
+
|
|
112
|
+
Install the released package from PyPI:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install cavsim2d
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
or clone the repository and install it in editable mode, to follow development or
|
|
119
|
+
change the code:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
git clone https://github.com/Dark-Elektron/cavsim2d.git
|
|
123
|
+
cd cavsim2d
|
|
124
|
+
pip install -e .
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Either way this installs the core dependencies (NGSolve, gmsh, numpy, scipy,
|
|
128
|
+
pandas, matplotlib, …). For the example notebooks and the Jupyter banner, also
|
|
129
|
+
install the optional extra:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pip install "cavsim2d[jupyter]" # from PyPI
|
|
133
|
+
pip install -e ".[jupyter]" # from a clone
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Requirements: **Python ≥ 3.10**. NGSolve (the finite-element backend) and gmsh
|
|
137
|
+
are installed from PyPI as dependencies.
|
|
138
|
+
|
|
139
|
+
### Third-party code — ABCI (wakefield only)
|
|
140
|
+
|
|
141
|
+
Wakefield analysis uses ABCI, the electromagnetic time-domain solver written by
|
|
142
|
+
Yong Ho Chin at KEK. ABCI is free to use but not open source, and KEK asks that
|
|
143
|
+
it be obtained from its own page, so **cavsim2d does not include it**. To run
|
|
144
|
+
wakefield analyses:
|
|
145
|
+
|
|
146
|
+
1. Download ABCI from the [ABCI page at KEK](https://abci.kek.jp/abci.htm)
|
|
147
|
+
(for 64-bit Windows, `ABCI_MP64_12_5.zip` at the time of writing) and unzip it
|
|
148
|
+
anywhere. No installation is needed.
|
|
149
|
+
2. Point cavsim2d at the executable with the `CAVSIM2D_ABCI` environment
|
|
150
|
+
variable. It may also name the folder that holds `ABCI.exe`.
|
|
151
|
+
|
|
152
|
+
```shell
|
|
153
|
+
# Windows, persistent for new terminals and Jupyter sessions
|
|
154
|
+
setx CAVSIM2D_ABCI "C:\ABCI\ABCI_MP64_12_5.exe"
|
|
155
|
+
# Linux / macOS
|
|
156
|
+
export CAVSIM2D_ABCI="$HOME/abci/ABCI_MP64_12_5.exe"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
or from Python, before the first wakefield run:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import os
|
|
163
|
+
os.environ['CAVSIM2D_ABCI'] = r'C:\ABCI\ABCI_MP64_12_5.exe'
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
If you know someone else who wants ABCI, point them to the KEK page rather than
|
|
167
|
+
passing on a copy. In a source checkout, a copy at
|
|
168
|
+
`cavsim2d/solvers/ABCI/ABCI.exe` is used when `CAVSIM2D_ABCI` is unset; that path
|
|
169
|
+
is gitignored and is never packaged.
|
|
170
|
+
|
|
171
|
+
ABCI is a Windows binary. On Windows it runs directly; on Linux/macOS it is
|
|
172
|
+
launched through [`wine`](https://www.winehq.org) if that is installed. If ABCI
|
|
173
|
+
or `wine` is missing, wakefield runs fail early with a message saying where to
|
|
174
|
+
get it. Eigenmode, tuning, UQ and optimisation do not need ABCI.
|
|
175
|
+
|
|
176
|
+
> [!TIP]
|
|
177
|
+
> `pip install pprintpp` gives nicer nested-dict printing for the quantities of
|
|
178
|
+
> interest, but is not required.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Quickstart
|
|
183
|
+
|
|
184
|
+
Everything is driven by two classes: **`Study`**, a container that owns a
|
|
185
|
+
project folder, and **`Cavity`** (here `EllipticalCavity`), a single cavity.
|
|
186
|
+
Results are written under the project folder and cached on the objects.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
import pprint
|
|
190
|
+
pp = pprint.PrettyPrinter(indent=4)
|
|
191
|
+
from cavsim2d import Study, EllipticalCavity
|
|
192
|
+
|
|
193
|
+
# A project folder — all results and geometry are written here.
|
|
194
|
+
cavs = Study(r'/path/to/my_project')
|
|
195
|
+
|
|
196
|
+
# A 1-cell TESLA mid-cell: A, B, a, b, Ri, L, Req [mm]
|
|
197
|
+
midcell = [42, 42, 12, 19, 35, 57.7, 103.353]
|
|
198
|
+
tesla = EllipticalCavity(1, midcell, midcell, midcell, beampipe='none')
|
|
199
|
+
cavs.add_cavity([tesla], names=['TESLA'])
|
|
200
|
+
|
|
201
|
+
# Fundamental-mode eigenmode analysis.
|
|
202
|
+
cavs.run_eigenmode({'processes': 1, 'rerun': True, 'boundary_conditions': 'mm'})
|
|
203
|
+
pp.pprint(cavs.eigenmode_qois)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
For the 1-cell TESLA cavity this reports `freq ≈ 1300 MHz`, `R/Q ≈ 113 Ω`,
|
|
207
|
+
`G ≈ 271 Ω`, `Epk/Eacc ≈ 2.04`, `Bpk/Eacc ≈ 4.16 mT/(MV/m)`.
|
|
208
|
+
|
|
209
|
+
> [!TIP]
|
|
210
|
+
> Prefer runnable scripts to prose? The [`examples/`](https://github.com/Dark-Elektron/cavsim2d/tree/master/examples) directory has
|
|
211
|
+
> one self-contained script per feature (`01_eigenmode.py` … `06_optimisation.py`
|
|
212
|
+
> and `run_all.py`) that writes results and plots to disk. They are the quickest
|
|
213
|
+
> way to see each workflow end to end.
|
|
214
|
+
|
|
215
|
+
### Project folder layout
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
my_project/
|
|
219
|
+
└── cavities/
|
|
220
|
+
└── TESLA/
|
|
221
|
+
├── geometry/ geodata.geo, snapshot
|
|
222
|
+
├── eigenmode/
|
|
223
|
+
│ ├── monopole/ qois.json, qois_all_modes.json, fields, mesh
|
|
224
|
+
│ ├── dipole/ (if requested)
|
|
225
|
+
│ └── quadrupole/ (if requested)
|
|
226
|
+
├── tuned/ tuned cavity + tune_info/
|
|
227
|
+
├── wakefield/
|
|
228
|
+
│ ├── longitudinal/ ABCI output
|
|
229
|
+
│ └── transversal/
|
|
230
|
+
└── uq/ UQ nodes and statistics
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Eigenmode analysis
|
|
236
|
+
|
|
237
|
+
`run_eigenmode` accepts a configuration dictionary; every key is optional:
|
|
238
|
+
|
|
239
|
+
| key | meaning | default |
|
|
240
|
+
|-----|---------|---------|
|
|
241
|
+
| `processes` | parallel worker processes | 1 |
|
|
242
|
+
| `rerun` | recompute even if results exist | True |
|
|
243
|
+
| `boundary_conditions` | end-wall BCs, e.g. `'mm'` (magnetic-magnetic) | `'mm'` |
|
|
244
|
+
| `polarisation` | azimuthal order(s) to solve — see below | `'monopole'` |
|
|
245
|
+
| `n_modes` | physical modes per m-pole solve | `n_cells + 2` |
|
|
246
|
+
| `mesh_config` | `{'h': ..., 'p': ..., 'adaptive': ...}` mesh size / order / adaptive refinement | h=20, p=3 |
|
|
247
|
+
| `conductivity` | wall conductivity [S/m] for Q/Ploss (normal conductor) | copper, 5.96e7 |
|
|
248
|
+
| `surface_resistance` | fixed surface resistance [Ω] (e.g. SRF) — overrides `conductivity` | — |
|
|
249
|
+
| `uq_config` | enable uncertainty quantification (see below) | — |
|
|
250
|
+
|
|
251
|
+
> [!TIP]
|
|
252
|
+
> Q, Rsh and wall power depend on the surface resistance. By default the walls
|
|
253
|
+
> are copper; pass `conductivity` for another normal conductor, or
|
|
254
|
+
> `surface_resistance` (a fixed Rs, e.g. `1e-8` Ω) to model an SRF cavity. The
|
|
255
|
+
> geometric factor `G` is material-independent.
|
|
256
|
+
|
|
257
|
+
> [!TIP]
|
|
258
|
+
> **Adaptive mesh refinement.** Set `mesh_config['adaptive']` to `True` (e.g.
|
|
259
|
+
> `{'h': 20, 'p': 3, 'adaptive': True}`) and the solver refines the mesh where
|
|
260
|
+
> the modes need it, stopping once every mode's recovery error is below the
|
|
261
|
+
> tolerance, then reports the QOIs on the finest mesh. Use a dict to tune it:
|
|
262
|
+
> `{'adaptive': {'tol': 1e-12, 'max_refinements': 6, 'max_ndof': 80000,
|
|
263
|
+
> 'theta': 0.25}}`. The per-level history is written to
|
|
264
|
+
> `eigenmode/monopole/adaptive_history.json`. `cav.study_mesh_convergence()`
|
|
265
|
+
> uses the same machinery to sweep error-vs-DOF at each polynomial order.
|
|
266
|
+
|
|
267
|
+
Access results either on the container or per cavity:
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
cavs.eigenmode_qois # {name: {qoi: value}} for the fundamental mode
|
|
271
|
+
tesla.get_eigenmode_qois() # populate tesla.eigenmode_qois, tesla.freq, ...
|
|
272
|
+
tesla.eigenmode.qois # fundamental-mode QOIs (solver-object API)
|
|
273
|
+
tesla.eigenmode.modes # list of all computed modes
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Compare several cavities:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
cavs.add_cavity([EllipticalCavity(1,
|
|
280
|
+
[53.58, 36.58, 8.08, 9.84, 35, 57.7, 98.27], # a re-entrant mid-cell
|
|
281
|
+
[53.58, 36.58, 8.08, 9.84, 35, 57.7, 98.27],
|
|
282
|
+
[53.58, 36.58, 8.08, 9.84, 35, 57.7, 98.27], beampipe='none')], ['reentrant'])
|
|
283
|
+
cavs.run_eigenmode({'processes': 1, 'rerun': True, 'boundary_conditions': 'mm'})
|
|
284
|
+
cavs.eigenmode.plot_fm_bar()
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Visualising the mesh and fields
|
|
288
|
+
|
|
289
|
+
Meshes and fields belong to a **`Cavity`** (not the container), so index or name
|
|
290
|
+
the cavity:
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
cavs['TESLA'].plot_mesh()
|
|
294
|
+
cavs['TESLA'].plot_fields(mode=1, which='E') # |E| of the fundamental
|
|
295
|
+
cavs['TESLA'].plot_fields(mode=1, which='H') # |H|
|
|
296
|
+
cavs['TESLA'].plot_axis_field() # on-axis Ez and field flatness
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
All `plot*` methods return a `matplotlib` axes/figure so you can restyle or save
|
|
300
|
+
them, and share one warm-cast, publication-oriented palette and font (STIX;
|
|
301
|
+
`cavsim2d.utils.style`, `apply_style()` to make it your session default). For
|
|
302
|
+
multi-cell cavities, `plot_dispersion()` draws the Brillouin diagram: it overlays
|
|
303
|
+
every computed polarisation, colour-coded, and shows all modes (grouped into
|
|
304
|
+
passbands of `n_cells`). Options:
|
|
305
|
+
|
|
306
|
+
- `pol=` — a name ('dipole'), a list, or `None` for all computed polarisations;
|
|
307
|
+
- `bands=` — 1-based passband selection; a flat `[1, 2]` for all polarisations or
|
|
308
|
+
a nested `[[1, 2], [1]]` for one selection per polarisation;
|
|
309
|
+
- `break_axis=` (default `True`) — splits the y-axis where passbands are far
|
|
310
|
+
apart so the empty space between them is not wasted; the split points are found
|
|
311
|
+
automatically from the frequency grouping. Pass `breaks=[(1350, 1600), (1900,
|
|
312
|
+
2350)]` to place them by hand (each is an approximate `(low, high)` gap).
|
|
313
|
+
- `light_line=` (default `True`) — overlays the speed-of-light line `f = c·μ/(2π·d)`.
|
|
314
|
+
Because the diagram is a reduced (folded) zone, it appears as a triangle wave
|
|
315
|
+
crossing every band; the accelerating π-mode sits on it. Skipped for geometries
|
|
316
|
+
with no defined cell period.
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Higher-order-mode (m-pole) eigenmodes
|
|
321
|
+
|
|
322
|
+
Dipole, quadrupole, and higher azimuthal modes ($m \ge 1$) are solved on the same
|
|
323
|
+
2D meridian mesh via an $H(\mathrm{curl}) \times H^1$ product-space formulation.
|
|
324
|
+
Select them with the `polarisation` key (names or azimuthal mode numbers, a
|
|
325
|
+
single value or a list):
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
cavs.run_eigenmode({
|
|
329
|
+
'processes': 1,
|
|
330
|
+
'rerun': True,
|
|
331
|
+
'boundary_conditions': 'mm',
|
|
332
|
+
'polarisation': ['monopole', 'dipole', 'quadrupole'], # or [0, 1, 2]
|
|
333
|
+
'n_modes': 4,
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
# Per-polarisation results:
|
|
337
|
+
tesla.eigenmode.mpole_qois('dipole') # all dipole modes, by index
|
|
338
|
+
tesla.eigenmode.mpole_modes('quadrupole') # list of EigenmodeResult
|
|
339
|
+
tesla.plot_fields(mode=0, which='E', pol='dipole') # in-plane |E| envelope
|
|
340
|
+
tesla.plot_fields(mode=0, which='Ephi', pol='dipole') # azimuthal envelope
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Each polarisation is written to its own folder (`eigenmode/monopole/`,
|
|
344
|
+
`eigenmode/dipole/`, …). Reruns are per polarisation: rerunning the dipole never
|
|
345
|
+
overwrites monopole results, and `rerun=False` only solves the polarisations
|
|
346
|
+
whose results are missing.
|
|
347
|
+
|
|
348
|
+
For $m \ge 1$ the accelerating quantities are the **transverse** analogues: since
|
|
349
|
+
$E_z \sim r^m$ vanishes on axis, the longitudinal voltage is evaluated off-axis
|
|
350
|
+
and converted to a transverse kick voltage via Panofsky–Wenzel. The QOI keys
|
|
351
|
+
`Vacc`/`Eacc`/`R/Q` hold these transverse values, and `R/Q_t [Ohm/m^(2(m-1))]`
|
|
352
|
+
is normalised so it is independent of the evaluation radius (equal to `R/Q` for
|
|
353
|
+
the dipole).
|
|
354
|
+
|
|
355
|
+
**Conventions to note.** The headline `qois.json` for an m-pole solve is the
|
|
356
|
+
**lowest** mode (index 0), whereas the monopole headline is the accelerating
|
|
357
|
+
π-mode (index `n_cells`). Use `mpole_qois(pol)` for the full set. The `kcc [%]`
|
|
358
|
+
cell-to-cell coupling for $m \ge 1$ reuses the monopole passband formula
|
|
359
|
+
(relative to mode index 1) and is of limited physical meaning for a deflecting
|
|
360
|
+
passband. Plot the passband with `cav.plot_dispersion(pol='dipole')`.
|
|
361
|
+
|
|
362
|
+
M-pole QOIs are not yet available as UQ or optimisation objectives (those still
|
|
363
|
+
operate on the monopole figures of merit).
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Cavity tuning
|
|
368
|
+
|
|
369
|
+
Tuning drives a chosen geometry parameter until the fundamental hits a target
|
|
370
|
+
frequency. Give a target `freqs` and a `cell_type` mapping (cell → parameter to
|
|
371
|
+
vary); a nested `eigenmode_config` controls the solves.
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
cavs = Study(r'/path/to/my_project')
|
|
375
|
+
midcell = [42, 42, 12, 19, 35, 57.7, 100] # deliberately wrong Req
|
|
376
|
+
tesla = EllipticalCavity(1, midcell, midcell, midcell, beampipe='none')
|
|
377
|
+
cavs.add_cavity([tesla], ['TESLA'])
|
|
378
|
+
|
|
379
|
+
cavs.run_tune({
|
|
380
|
+
'freqs': 1300, # MHz
|
|
381
|
+
'cell_type': {'mid-cell': 'Req'}, # vary Req of the mid-cell
|
|
382
|
+
'processes': 1,
|
|
383
|
+
'rerun': True,
|
|
384
|
+
'eigenmode_config': {'processes': 1, 'rerun': True, 'boundary_conditions': 'mm'},
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
pp.pprint(tesla.tune.qois) # tuned parameters and achieved frequency
|
|
388
|
+
tuned = tesla.tuned # the tuned Cavity (its own folder + results)
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
`Req` converges to ≈ 103.35 mm and the frequency to 1300 MHz. The tuned cavity is
|
|
392
|
+
a full `Cavity` living under `<cavity>/tuned/`, so you can run eigenmode/wakefield
|
|
393
|
+
on it directly (`tesla.tuned.run_eigenmode(...)`).
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Wakefield analysis
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
cavs = Study(r'/path/to/my_project')
|
|
401
|
+
midcell = [42, 42, 12, 19, 35, 57.7, 103.353]
|
|
402
|
+
endcell_l = [40.34, 40.34, 10, 13.5, 39, 55.716, 103.353]
|
|
403
|
+
endcell_r = [42, 42, 9, 12.8, 39, 56.815, 103.353]
|
|
404
|
+
tesla = EllipticalCavity(9, midcell, endcell_l, endcell_r, beampipe='both')
|
|
405
|
+
cavs.add_cavity([tesla], ['TESLA'])
|
|
406
|
+
|
|
407
|
+
cavs.run_wakefield({
|
|
408
|
+
'processes': 1,
|
|
409
|
+
'rerun': True,
|
|
410
|
+
'MROT': 'both', # 'monopole'/'longitudinal' (0), 'dipole'/'transverse' (1), 'both' (2)
|
|
411
|
+
'wakelength': 50, # metres
|
|
412
|
+
'bunch_length': 25, # mm
|
|
413
|
+
})
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
> [!NOTE]
|
|
417
|
+
> In **wakefield** configs the beam mode is `MROT` (`'polarisation'` is accepted
|
|
418
|
+
> as a deprecated alias). This is *not* the same as `polarisation` in an
|
|
419
|
+
> **eigenmode** config, which selects the azimuthal mode order.
|
|
420
|
+
|
|
421
|
+
ABCI requires a beam pipe at **each** end of the structure, of at least five mesh
|
|
422
|
+
lengths. Any end that does not already have one is given a pipe of **three times
|
|
423
|
+
the device's axial length**; ends that already have one are left alone. Override
|
|
424
|
+
with `wakefield_config['beampipe_length']` (metres). This means geometries built
|
|
425
|
+
with `beampipe='none'` still run — the pipes exist only in the ABCI deck, not in
|
|
426
|
+
the cavity you defined.
|
|
427
|
+
|
|
428
|
+
Plot the impedance spectra and wake potentials:
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
ax = tesla.plot('zl') # longitudinal impedance |Z_L|
|
|
432
|
+
ax = tesla.plot('zt', ax) # transverse impedance |Z_T| on the same axes
|
|
433
|
+
tesla.plot('wpl'); tesla.plot('wpt') # wake potentials
|
|
434
|
+
|
|
435
|
+
# Or via the solver-object API, which returns DataFrames:
|
|
436
|
+
tesla.wakefield.wake_z # f [MHz], |Z|, Re(Z), Im(Z), s [m], W
|
|
437
|
+
tesla.wakefield.plot_z(quantity='impedance')
|
|
438
|
+
tesla.wakefield.plot_t(quantity='wake')
|
|
439
|
+
|
|
440
|
+
# The main run always reports the loss/kick factors:
|
|
441
|
+
tesla.wakefield.qois # {'|k_loss| [V/pC]', 'k_FM [V/pC]',
|
|
442
|
+
# 'k_loss_HOM [V/pC]', '|k_kick| [V/pC/m]'}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Any wakefield solver plugs in behind `cav.wakefield` — ABCI is the default
|
|
446
|
+
backend, selected by `wakefield_config['solver']` (`'abci'`). The results above
|
|
447
|
+
are read through a normalised schema, so nothing above changes when the solver
|
|
448
|
+
does.
|
|
449
|
+
|
|
450
|
+
### Loss/kick factors and HOM power at operating points
|
|
451
|
+
|
|
452
|
+
The main run's loss/kick factors are always in `cav.wakefield.qois`. To also get
|
|
453
|
+
higher-order-mode *power* at machine operating points, pass an `operating_points`
|
|
454
|
+
dictionary (this re-runs per bunch length and needs R/Q from a prior eigenmode
|
|
455
|
+
run); those results are reported separately in `cav.wakefield.qois_op`:
|
|
456
|
+
|
|
457
|
+
```python
|
|
458
|
+
op_points = {
|
|
459
|
+
"Z": {
|
|
460
|
+
"freq [MHz]": 400.79, "E [GeV]": 45.6, "I0 [mA]": 1280, "V [GV]": 0.12,
|
|
461
|
+
"Eacc [MV/m]": 5.72, "nu_s []": 0.0370, "alpha_p [1e-5]": 2.85,
|
|
462
|
+
"tau_z [ms]": 354.91, "tau_xy [ms]": 709.82, "f_rev [kHz]": 3.07,
|
|
463
|
+
"beta_xy [m]": 56, "N_c []": 56, "T [K]": 4.5,
|
|
464
|
+
"sigma_SR [mm]": 4.32, "sigma_BS [mm]": 15.2, "Nb [1e11]": 2.76,
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
cavs.run_wakefield({
|
|
468
|
+
'processes': 1, 'rerun': True,
|
|
469
|
+
'bunch_length': 25, 'wakelength': 50,
|
|
470
|
+
'operating_points': op_points,
|
|
471
|
+
})
|
|
472
|
+
pp.pprint(cavs.wakefield_qois)
|
|
473
|
+
cavs.wakefield.plot_hom_bar('Z_SR_4.32mm')
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Objective names
|
|
479
|
+
|
|
480
|
+
Monopole and m-pole results share QOI key names — `freq [MHz]`, `R/Q [Ohm]`,
|
|
481
|
+
`G [Ohm]` and 15 others appear in every polarisation's `qois.json`. So every UQ
|
|
482
|
+
and optimisation objective must say which polarisation it means:
|
|
483
|
+
|
|
484
|
+
```text
|
|
485
|
+
monopole:R/Q [Ohm] # the polarisation's primary mode of interest
|
|
486
|
+
dipole:R/Q_t [Ohm/m^(2(m-1))] # a dipole-only QOI
|
|
487
|
+
dipole:2:freq [MHz] # dipole, mode 2 (1-based)
|
|
488
|
+
1:freq [MHz] # azimuthal number instead of a name
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
The separator is `:`; no QOI name contains one, and only the first two fields
|
|
492
|
+
are split, so the QOI itself may contain anything. A bare `'freq [MHz]'` is
|
|
493
|
+
**rejected** — it used to silently mean *the monopole's*, which becomes a wrong
|
|
494
|
+
answer the moment a second polarisation is solved. An objective naming a QOI or
|
|
495
|
+
mode that does not exist raises a `ValueError` listing what is available;
|
|
496
|
+
previously it was dropped without a word and the statistics were quietly
|
|
497
|
+
computed over whichever objectives happened to match.
|
|
498
|
+
|
|
499
|
+
Naming a polarisation in an objective is enough — the UQ run adds it to
|
|
500
|
+
`eigenmode_config['polarisation']` automatically. Wakefield objectives (`ZL`,
|
|
501
|
+
`ZT`) carry no polarisation and are written as before.
|
|
502
|
+
|
|
503
|
+
### Which mode is reported: `mode_of_interest`
|
|
504
|
+
|
|
505
|
+
`eigenmode_config['mode_of_interest']` selects the modes whose QOIs are reported.
|
|
506
|
+
It is **1-based** (mode 1 is the lowest of the passband) and may be an int, a list
|
|
507
|
+
of **any length**, or a dict keyed by polarisation — each polarisation may carry a
|
|
508
|
+
different number of modes. Order is preserved and duplicates collapse. The
|
|
509
|
+
**first** mode listed is the primary one and lands in `qois.json`; all of them are
|
|
510
|
+
written to `qois_moi.json` keyed by 1-based mode, and each is addressable as an
|
|
511
|
+
objective (`monopole:4:freq [MHz]`).
|
|
512
|
+
|
|
513
|
+
```python
|
|
514
|
+
'mode_of_interest': 9 # every polarisation
|
|
515
|
+
'mode_of_interest': [1, 2, 3] # three, for every one
|
|
516
|
+
'mode_of_interest': {'monopole': [1, 2, 3, 4], 'dipole': 1} # different counts
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Defaults: **monopole → `n_cells`**, the π-mode — the operating mode of an
|
|
520
|
+
accelerating structure. A 1-cell cavity, gun or pillbox therefore gets mode 1,
|
|
521
|
+
so the convention degrades gracefully to non-accelerator geometries. **m-pole →
|
|
522
|
+
mode 1**, the lowest of the deflecting passband (an m-pole spectrum has no
|
|
523
|
+
spurious DC mode to skip).
|
|
524
|
+
|
|
525
|
+
## Uncertainty quantification
|
|
526
|
+
|
|
527
|
+
Any analysis can carry uncertainty quantification by adding a `uq_config`. It
|
|
528
|
+
propagates geometry uncertainties through the solve and reports the mean and
|
|
529
|
+
standard deviation of each objective. Result keys are the canonical objective
|
|
530
|
+
names, e.g. `uq.json['dipole:freq [MHz]']['expe'][0]`.
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
cavs.run_eigenmode({
|
|
534
|
+
'processes': 1,
|
|
535
|
+
'rerun': True,
|
|
536
|
+
'boundary_conditions': 'mm',
|
|
537
|
+
'uq_config': {
|
|
538
|
+
'variables': ['A', 'B', 'a', 'b'], # varied parameters
|
|
539
|
+
# Objectives must name a polarisation (see "Objective names" below)
|
|
540
|
+
'objectives': ["monopole:freq [MHz]", "monopole:R/Q [Ohm]", "monopole:Epk/Eacc []",
|
|
541
|
+
"monopole:Bpk/Eacc [mT/MV/m]", "monopole:G [Ohm]"],
|
|
542
|
+
'delta': [0.05, 0.05, 0.05, 0.05], # 5% std on each
|
|
543
|
+
'processes': 1,
|
|
544
|
+
'distribution': 'gaussian',
|
|
545
|
+
'method': ['Quadrature', 'Stroud3'],
|
|
546
|
+
'cell_type': 'mid-cell',
|
|
547
|
+
'cell_complexity': 'simplecell',
|
|
548
|
+
},
|
|
549
|
+
})
|
|
550
|
+
pp.pprint(cavs.uq_fm_results)
|
|
551
|
+
cavs.eigenmode.plot_fm_bar(uq=True) # bar chart with error bars
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
> [!IMPORTANT]
|
|
555
|
+
> The key is `cell_complexity` (underscore). If a config contains an unrecognised
|
|
556
|
+
> key, `cavsim2d` emits a `UserWarning` with a "did you mean …?" suggestion —
|
|
557
|
+
> useful for catching typos that would otherwise be silently ignored.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Optimisation
|
|
562
|
+
|
|
563
|
+
Multi-objective shape optimisation uses a genetic algorithm. Each candidate is
|
|
564
|
+
tuned to the target frequency (via a nested `tune_config`) before its objectives
|
|
565
|
+
are evaluated.
|
|
566
|
+
|
|
567
|
+
```python
|
|
568
|
+
cavs = Study(r'/path/to/my_project')
|
|
569
|
+
|
|
570
|
+
optimisation_config = {
|
|
571
|
+
'initial_points': 6,
|
|
572
|
+
'no_of_generation': 2,
|
|
573
|
+
'method': {'LHS': {'seed': 5}},
|
|
574
|
+
'bounds': { # search space; fix a variable with equal bounds
|
|
575
|
+
'A': [38, 46], 'B': [38, 46], 'a': [10, 15], 'b': [15, 22],
|
|
576
|
+
'Ri': [33, 37], 'L': [57.7, 57.7], 'Req': [100, 106],
|
|
577
|
+
},
|
|
578
|
+
'objectives': [
|
|
579
|
+
['min', 'monopole:Epk/Eacc []'],
|
|
580
|
+
['min', 'monopole:Bpk/Eacc [mT/MV/m]'],
|
|
581
|
+
# ['max', 'monopole:R/Q [Ohm]'],
|
|
582
|
+
# ['min', 'dipole:R/Q_t [Ohm/m^(2(m-1))]'], # deflecting-mode impedance
|
|
583
|
+
# ['min', 'ZL', [1, 2, 5]], # impedance peak in a frequency window (needs wakefield)
|
|
584
|
+
],
|
|
585
|
+
'tune_config': {
|
|
586
|
+
'freqs': 1300,
|
|
587
|
+
'cell_type': {'mid-cell': 'Req'},
|
|
588
|
+
'processes': 1,
|
|
589
|
+
'eigenmode_config': {'n_cells': 1, 'processes': 1, 'boundary_conditions': 'mm'},
|
|
590
|
+
},
|
|
591
|
+
# GA controls — these are integer offspring counts, not fractions:
|
|
592
|
+
'mutation_factor': 4,
|
|
593
|
+
'crossover_factor': 4,
|
|
594
|
+
'elites_for_crossover': 2,
|
|
595
|
+
'chaos_factor': 2,
|
|
596
|
+
'weights': [1, 1],
|
|
597
|
+
}
|
|
598
|
+
cavs.run_optimisation(optimisation_config)
|
|
599
|
+
|
|
600
|
+
opt = cavs.optimisation
|
|
601
|
+
opt.history # every evaluated candidate (DataFrame)
|
|
602
|
+
opt.pareto # Pareto-optimal candidates
|
|
603
|
+
opt.plot_pareto(kind='scatter')
|
|
604
|
+
opt.plot_convergence()
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
Supported objective quantities: `freq [MHz]`, `Epk/Eacc []`,
|
|
608
|
+
`Bpk/Eacc [mT/MV/m]`, `R/Q [Ohm]`, `G [Ohm]`, `Q []`, and the wakefield impedance
|
|
609
|
+
peaks `ZL`, `ZT` (with a `[low, ..., high]` frequency window as the third
|
|
610
|
+
element). Each objective is `['min'|'max'|'equal', name]` (plus a target value
|
|
611
|
+
for `'equal'`, or the window for `ZL`/`ZT`).
|
|
612
|
+
|
|
613
|
+
> [!NOTE]
|
|
614
|
+
> `mutation_factor`, `crossover_factor` and `chaos_factor` are **counts** of
|
|
615
|
+
> offspring / random individuals per generation (integers), not probabilities.
|
|
616
|
+
|
|
617
|
+
---
|
|
618
|
+
|
|
619
|
+
## Configuration dictionaries
|
|
620
|
+
|
|
621
|
+
Every analysis is driven by a config dict, and they nest consistently. UQ slots
|
|
622
|
+
into any solver config via `uq_config`; optimisation wraps a `tune_config` (which
|
|
623
|
+
itself wraps an `eigenmode_config`), and optionally a `wakefield_config`:
|
|
624
|
+
|
|
625
|
+
```
|
|
626
|
+
optimisation_config
|
|
627
|
+
├── tune_config
|
|
628
|
+
│ └── eigenmode_config
|
|
629
|
+
│ └── uq_config
|
|
630
|
+
├── wakefield_config
|
|
631
|
+
│ └── uq_config
|
|
632
|
+
├── bounds, objectives, initial_points, no_of_generation, ...
|
|
633
|
+
└── mutation_factor, crossover_factor, elites_for_crossover, chaos_factor
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Unrecognised keys are reported as warnings (with suggestions), so a mistyped key
|
|
637
|
+
never silently changes behaviour. For the full accepted-key reference, see
|
|
638
|
+
`cavsim2d/utils/config_validation.py`, or `help(cavs.run_eigenmode)` etc.
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## Parallelisation
|
|
643
|
+
|
|
644
|
+
Set `processes` in any config to run cavities in parallel. When UQ is enabled, a
|
|
645
|
+
second `processes` inside `uq_config` parallelises the quadrature/sample points.
|
|
646
|
+
The default is a single process (which also gives the cleanest tracebacks and
|
|
647
|
+
Jupyter output).
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## Running the tests
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
pip install -e ".[dev]"
|
|
655
|
+
pytest tests/ -q
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
The solver tests self-skip where NGSolve/gmsh are unavailable, and the wakefield
|
|
659
|
+
tests skip unless ABCI is available on Windows (see above), so the suite stays
|
|
660
|
+
green in minimal environments.
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## License
|
|
665
|
+
|
|
666
|
+
MIT — see [LICENSE](https://github.com/Dark-Elektron/cavsim2d/blob/master/LICENSE).
|