|
7 | 7 | "source": [ |
8 | 8 | "# Resistive Wall Wakefield\n", |
9 | 9 | "\n", |
10 | | - "The `ResistiveWallWakefield` object implements the pseudomode (damped oscillator) fits from SLAC-PUB-10707 to efficiently calculate the resistive wall wakefield from round and flat geometries, for arbitrary conductivities and relaxation times." |
| 10 | + "The `ResistiveWallWakefield` class implements the pseudomode (damped oscillator) fits from SLAC-PUB-10707 to efficiently calculate the short-range resistive wall wakefield for round and flat geometries.\n", |
| 11 | + "\n", |
| 12 | + "The wakefield is represented as a single damped sinusoid:\n", |
| 13 | + "$$W(z) = A \\, e^{d z} \\sin(k_r z + \\phi)$$\n", |
| 14 | + "where $A$, $d$, $k_r$, and $\\phi$ are derived from polynomial fits of digitized data from Figures 4, 8, and 14 of SLAC-PUB-10707.\n", |
| 15 | + "\n", |
| 16 | + "**References:**\n", |
| 17 | + "- Bane & Stupakov, [SLAC-PUB-10707](https://www.slac.stanford.edu/cgi-wrap/getdoc/slac-pub-10707.pdf) (2004)\n", |
| 18 | + "- Bane, Stupakov, Tu, [EPAC 2006 THPCH073](https://accelconf.web.cern.ch/e06/PAPERS/THPCH073.PDF)" |
11 | 19 | ] |
12 | 20 | }, |
13 | 21 | { |
|
17 | 25 | "metadata": {}, |
18 | 26 | "outputs": [], |
19 | 27 | "source": [ |
20 | | - "from pmd_beamphysics.wakefields.resistive_wall import ResistiveWallWakefield\n", |
21 | | - "\n", |
| 28 | + "from pmd_beamphysics.wakefields import ResistiveWallWakefield\n", |
22 | 29 | "from pmd_beamphysics import ParticleGroup\n", |
23 | 30 | "from pmd_beamphysics.units import epsilon_0\n", |
| 31 | + "\n", |
24 | 32 | "import numpy as np\n", |
25 | 33 | "import matplotlib.pyplot as plt" |
26 | 34 | ] |
27 | 35 | }, |
| 36 | + { |
| 37 | + "cell_type": "markdown", |
| 38 | + "id": "c36da92f-d4a6-46c5-a0ff-307d5d69bde7", |
| 39 | + "metadata": {}, |
| 40 | + "source": [ |
| 41 | + "## Basic Usage\n", |
| 42 | + "\n", |
| 43 | + "Create a wakefield by specifying the pipe geometry and material properties:" |
| 44 | + ] |
| 45 | + }, |
28 | 46 | { |
29 | 47 | "cell_type": "code", |
30 | 48 | "execution_count": null, |
31 | | - "id": "393a7b50-478f-47a0-acf8-268a34b2af45", |
| 49 | + "id": "d045a156-5b8d-439c-b3a0-0434f01657ba", |
32 | 50 | "metadata": {}, |
33 | 51 | "outputs": [], |
34 | 52 | "source": [ |
35 | | - "?ResistiveWallWakefield" |
| 53 | + "# Create a wakefield for a 2.5 mm radius copper pipe\n", |
| 54 | + "wake = ResistiveWallWakefield(\n", |
| 55 | + " radius=0.0025,\n", |
| 56 | + " conductivity=6.5e7, # σ [S/m]\n", |
| 57 | + " relaxation_time=27e-15, # τ [s]\n", |
| 58 | + " geometry=\"round\",\n", |
| 59 | + ")\n", |
| 60 | + "wake" |
36 | 61 | ] |
37 | 62 | }, |
38 | 63 | { |
39 | 64 | "cell_type": "markdown", |
40 | | - "id": "c36da92f-d4a6-46c5-a0ff-307d5d69bde7", |
| 65 | + "id": "aac561ff", |
41 | 66 | "metadata": {}, |
42 | 67 | "source": [ |
43 | | - "# Basic ins" |
| 68 | + "The built-in `plot()` method shows the wakefield as a function of trailing distance:" |
44 | 69 | ] |
45 | 70 | }, |
46 | 71 | { |
47 | 72 | "cell_type": "code", |
48 | 73 | "execution_count": null, |
49 | | - "id": "d045a156-5b8d-439c-b3a0-0434f01657ba", |
| 74 | + "id": "0dfb3db6", |
50 | 75 | "metadata": {}, |
51 | 76 | "outputs": [], |
52 | 77 | "source": [ |
53 | | - "wake = ResistiveWallWakefield(\n", |
54 | | - " radius=0.0025, geometry=\"round\", conductivity=6.5e7, relaxation_time=27e-15\n", |
55 | | - ")\n", |
56 | | - "\n", |
57 | 78 | "wake.plot()" |
58 | 79 | ] |
59 | 80 | }, |
| 81 | + { |
| 82 | + "cell_type": "markdown", |
| 83 | + "id": "13301e26", |
| 84 | + "metadata": {}, |
| 85 | + "source": [ |
| 86 | + "### Material Presets\n", |
| 87 | + "\n", |
| 88 | + "Common materials are available via `from_material()`:" |
| 89 | + ] |
| 90 | + }, |
60 | 91 | { |
61 | 92 | "cell_type": "code", |
62 | 93 | "execution_count": null, |
63 | | - "id": "47eb70ea-ff85-4b1d-8de0-f9b9f44b5f9b", |
| 94 | + "id": "4903016f", |
64 | 95 | "metadata": {}, |
65 | 96 | "outputs": [], |
66 | 97 | "source": [ |
67 | | - "print(wake.to_bmad())" |
| 98 | + "# Available material presets\n", |
| 99 | + "list(ResistiveWallWakefield.MATERIALS)" |
| 100 | + ] |
| 101 | + }, |
| 102 | + { |
| 103 | + "cell_type": "code", |
| 104 | + "execution_count": null, |
| 105 | + "id": "59ec3e57", |
| 106 | + "metadata": {}, |
| 107 | + "outputs": [], |
| 108 | + "source": [ |
| 109 | + "wake_cu = ResistiveWallWakefield.from_material(\"copper-slac-pub-10707\", radius=2.5e-3)\n", |
| 110 | + "wake_cu" |
68 | 111 | ] |
69 | 112 | }, |
70 | 113 | { |
71 | 114 | "cell_type": "markdown", |
72 | | - "id": "ad1249db-19e5-42a2-a976-2ff48843a91d", |
| 115 | + "id": "c0acf32d", |
73 | 116 | "metadata": {}, |
74 | 117 | "source": [ |
75 | | - "# Compare with SLAC-PUB-10707\n", |
| 118 | + "### Bmad Export\n", |
76 | 119 | "\n", |
77 | | - "Here we check the function against the plots in SLAC-PUB-10707." |
| 120 | + "The wakefield can be exported in Bmad format:" |
78 | 121 | ] |
79 | 122 | }, |
80 | 123 | { |
81 | 124 | "cell_type": "code", |
82 | 125 | "execution_count": null, |
83 | | - "id": "56a82d9d-9079-45d5-8bcd-6c8f5f2bdd32", |
| 126 | + "id": "47eb70ea-ff85-4b1d-8de0-f9b9f44b5f9b", |
84 | 127 | "metadata": {}, |
85 | 128 | "outputs": [], |
86 | 129 | "source": [ |
87 | | - "radius_ref = 2.5e-3\n", |
88 | | - "\n", |
89 | | - "wake = ResistiveWallWakefield.from_material(\n", |
90 | | - " material=\"copper-slac-pub-10707\", radius=radius_ref, geometry=\"round\"\n", |
91 | | - ")\n", |
92 | | - "\n", |
93 | | - "wake.plot()" |
| 130 | + "print(wake_cu.to_bmad())" |
94 | 131 | ] |
95 | 132 | }, |
96 | 133 | { |
97 | | - "cell_type": "code", |
98 | | - "execution_count": null, |
99 | | - "id": "19cff809-238b-4cbc-90d4-1cbac8032022", |
| 134 | + "cell_type": "markdown", |
| 135 | + "id": "ad1249db-19e5-42a2-a976-2ff48843a91d", |
100 | 136 | "metadata": {}, |
101 | | - "outputs": [], |
102 | 137 | "source": [ |
103 | | - "raw_data = np.loadtxt(\"../data/SLAC-PUB-10707-digitized-Fig4-AC-Cu.csv\", delimiter=\",\")\n", |
| 138 | + "## Validation Against SLAC-PUB-10707\n", |
104 | 139 | "\n", |
105 | | - "# Convert to SI\n", |
106 | | - "zref, Wref = (\n", |
107 | | - " raw_data[:, 0] * 1e-6,\n", |
108 | | - " raw_data[:, 1] * 4 / radius_ref**2 / (4 * np.pi * epsilon_0),\n", |
109 | | - ")\n", |
110 | | - "plt.plot(zref, Wref)" |
| 140 | + "Compare the pseudomode fit against digitized data from Figure 4 (AC-Cu, round pipe):" |
111 | 141 | ] |
112 | 142 | }, |
113 | 143 | { |
114 | 144 | "cell_type": "code", |
115 | 145 | "execution_count": null, |
116 | | - "id": "eabb80d7-969c-45e6-bd56-60cd05b8d494", |
| 146 | + "id": "56a82d9d-9079-45d5-8bcd-6c8f5f2bdd32", |
117 | 147 | "metadata": {}, |
118 | 148 | "outputs": [], |
119 | 149 | "source": [ |
120 | | - "zlist = np.linspace(0, 300e-6, 200)\n", |
121 | | - "Wz = wake.pseudomode(-zlist)\n", |
| 150 | + "# Load digitized reference data\n", |
| 151 | + "raw_data = np.loadtxt(\"../data/SLAC-PUB-10707-digitized-Fig4-AC-Cu.csv\", delimiter=\",\")\n", |
| 152 | + "radius_ref = 2.5e-3\n", |
122 | 153 | "\n", |
123 | | - "fig, ax = plt.subplots()\n", |
124 | | - "ax.plot(zlist * 1e6, Wz * 1e-12, label=f\"ResistiveWallWakefield {wake.geometry}\")\n", |
| 154 | + "# Convert CGS units to SI\n", |
| 155 | + "zref = raw_data[:, 0] * 1e-6 # µm → m\n", |
| 156 | + "Wref = raw_data[:, 1] * 4 / radius_ref**2 / (4 * np.pi * epsilon_0) # V/pC/m\n", |
125 | 157 | "\n", |
126 | | - "ax.plot(zref * 1e6, Wref * 1e-12, \"--\", label=\"Fig. 4 AC-Cu from SLAC-PUB-10707 2004\")\n", |
127 | | - "plt.legend()\n", |
| 158 | + "# Create wakefield with same parameters\n", |
| 159 | + "wake_ref = ResistiveWallWakefield.from_material(\n", |
| 160 | + " \"copper-slac-pub-10707\", radius=radius_ref\n", |
| 161 | + ")\n", |
| 162 | + "zlist = np.linspace(0, 300e-6, 200)\n", |
| 163 | + "Wz = wake_ref(-zlist) # Evaluate at trailing positions\n", |
128 | 164 | "\n", |
| 165 | + "# Plot comparison\n", |
| 166 | + "fig, ax = plt.subplots()\n", |
| 167 | + "ax.plot(zlist * 1e6, Wz * 1e-12, label=f\"ResistiveWallWakefield ({wake_ref.geometry})\")\n", |
| 168 | + "ax.plot(zref * 1e6, Wref * 1e-12, \"--\", label=\"Fig. 4 AC-Cu (digitized)\")\n", |
| 169 | + "ax.legend()\n", |
129 | 170 | "ax.set_xlabel(r\"$-z$ (µm)\")\n", |
130 | | - "ax.set_ylabel(r\"$W_z$ (V/pC/m)\")" |
| 171 | + "ax.set_ylabel(r\"$W_z$ (V/pC/m)\")\n", |
| 172 | + "ax.set_title(\"Validation: Round Copper Pipe\")" |
131 | 173 | ] |
132 | 174 | }, |
133 | 175 | { |
134 | 176 | "cell_type": "markdown", |
135 | 177 | "id": "5d112e6f-839f-4d62-bc24-3130c91b2e9d", |
136 | 178 | "metadata": {}, |
137 | 179 | "source": [ |
138 | | - "# Integrated wake from particles\n" |
| 180 | + "## Particle Wakefield Kicks\n", |
| 181 | + "\n", |
| 182 | + "Compute the integrated wakefield kick for each particle in a bunch:" |
139 | 183 | ] |
140 | 184 | }, |
141 | 185 | { |
|
145 | 189 | "metadata": {}, |
146 | 190 | "outputs": [], |
147 | 191 | "source": [ |
| 192 | + "# Load a particle beam\n", |
148 | 193 | "P = ParticleGroup(\"../data/bmad_particles2.h5\")\n", |
149 | | - "P.drift_to_t()" |
150 | | - ] |
151 | | - }, |
152 | | - { |
153 | | - "cell_type": "code", |
154 | | - "execution_count": null, |
155 | | - "id": "e2478b38-e658-40fb-95bb-a1574e784d50", |
156 | | - "metadata": {}, |
157 | | - "outputs": [], |
158 | | - "source": [ |
159 | | - "z = P.z\n", |
160 | | - "weight = P.weight" |
| 194 | + "P.drift_to_t() # Align particles at constant time\n", |
| 195 | + "P" |
161 | 196 | ] |
162 | 197 | }, |
163 | 198 | { |
164 | 199 | "cell_type": "markdown", |
165 | 200 | "id": "5e8c504c-d5c2-4cb4-b747-eb64dc3e1bcc", |
166 | 201 | "metadata": {}, |
167 | 202 | "source": [ |
168 | | - "`wake.pseudomode` gives the per particle kicks" |
| 203 | + "The `particle_kicks` method computes the wakefield-induced energy change for each particle:" |
169 | 204 | ] |
170 | 205 | }, |
171 | 206 | { |
|
175 | 210 | "metadata": {}, |
176 | 211 | "outputs": [], |
177 | 212 | "source": [ |
178 | | - "zwake = wake.pseudomode.particle_kicks(z, weight)\n", |
179 | | - "len(zwake)" |
180 | | - ] |
181 | | - }, |
182 | | - { |
183 | | - "cell_type": "code", |
184 | | - "execution_count": null, |
185 | | - "id": "34bf0eb3-7543-4da1-90ee-a426e26e5740", |
186 | | - "metadata": {}, |
187 | | - "outputs": [], |
188 | | - "source": [ |
189 | | - "plt.scatter(z, zwake)" |
| 213 | + "# Compute wakefield kicks (eV/m) for each particle\n", |
| 214 | + "kicks = wake_ref.particle_kicks(P)\n", |
| 215 | + "len(kicks)" |
190 | 216 | ] |
191 | 217 | }, |
192 | 218 | { |
|
196 | 222 | "metadata": {}, |
197 | 223 | "outputs": [], |
198 | 224 | "source": [ |
199 | | - "zscale = 1e6\n", |
200 | 225 | "fig, ax = plt.subplots()\n", |
201 | | - "ax.scatter(z * zscale, zwake, label=\"Python\", color=\"black\")\n", |
202 | | - "ax.legend()\n", |
| 226 | + "ax.scatter(P.z * 1e6, kicks, s=1, color=\"black\")\n", |
203 | 227 | "ax.set_xlabel(r\"$z$ (µm)\")\n", |
204 | | - "ax.set_ylabel(r\"$W_z$ (eV/m)\")" |
| 228 | + "ax.set_ylabel(r\"Wakefield kick (eV/m)\")\n", |
| 229 | + "ax.set_title(\"Per-particle wakefield kicks\")" |
205 | 230 | ] |
206 | 231 | }, |
207 | 232 | { |
208 | 233 | "cell_type": "markdown", |
209 | 234 | "id": "5d6d054a-3635-4072-82f2-203fd706c171", |
210 | 235 | "metadata": {}, |
211 | 236 | "source": [ |
212 | | - "# ParticleGroup.wakefield_plot\n", |
| 237 | + "## Convenience: `ParticleGroup.wakefield_plot`\n", |
213 | 238 | "\n", |
214 | | - "Wakefields can be autmoatically integrated and plotted from a ParticlGroup. The function automatically determintes the horizontal axis." |
| 239 | + "The wakefield can be computed and plotted directly from a `ParticleGroup`. The plot automatically chooses the appropriate horizontal axis based on whether particles are aligned at constant $t$ or constant $z$:" |
215 | 240 | ] |
216 | 241 | }, |
217 | 242 | { |
|
221 | 246 | "metadata": {}, |
222 | 247 | "outputs": [], |
223 | 248 | "source": [ |
224 | | - "P.wakefield_plot(wake, figsize=(12, 4))" |
| 249 | + "# Particles at constant t → use z as horizontal axis\n", |
| 250 | + "P.drift_to_t()\n", |
| 251 | + "P.wakefield_plot(wake_ref, figsize=(12, 4))" |
225 | 252 | ] |
226 | 253 | }, |
227 | 254 | { |
|
231 | 258 | "metadata": {}, |
232 | 259 | "outputs": [], |
233 | 260 | "source": [ |
| 261 | + "# Particles at constant z → use t as horizontal axis\n", |
234 | 262 | "P.drift_to_z()\n", |
235 | | - "P.wakefield_plot(wake, figsize=(12, 4))" |
| 263 | + "P.wakefield_plot(wake_ref, figsize=(12, 4))" |
236 | 264 | ] |
237 | 265 | } |
238 | 266 | ], |
239 | 267 | "metadata": { |
240 | 268 | "kernelspec": { |
241 | | - "display_name": "Python 3 (ipykernel)", |
| 269 | + "display_name": "pmd_beamphysics-dev", |
242 | 270 | "language": "python", |
243 | 271 | "name": "python3" |
244 | 272 | }, |
|
252 | 280 | "name": "python", |
253 | 281 | "nbconvert_exporter": "python", |
254 | 282 | "pygments_lexer": "ipython3", |
255 | | - "version": "3.13.3" |
| 283 | + "version": "3.13.9" |
256 | 284 | } |
257 | 285 | }, |
258 | 286 | "nbformat": 4, |
|
0 commit comments