diff --git a/api/particles/index.html b/api/particles/index.html index 2df9f1f..3a3987f 100644 --- a/api/particles/index.html +++ b/api/particles/index.html @@ -4344,8 +4344,8 @@

Particles

return deepcopy(self) @functools.wraps(resample_particles) - def resample(self, n=0): - data = resample_particles(self, n) + def resample(self, n=0, equal_weights=False): + data = resample_particles(self, n, equal_weights=equal_weights) return ParticleGroup(data=data) # Internal sorting diff --git a/assets/openPMD-beamphysics-examples.zip b/assets/openPMD-beamphysics-examples.zip index 05c1e6a..39e02cf 100644 Binary files a/assets/openPMD-beamphysics-examples.zip and b/assets/openPMD-beamphysics-examples.zip differ diff --git a/examples/bunching/bunching.ipynb b/examples/bunching/bunching.ipynb index 3a8ef8c..860c2e3 100644 --- a/examples/bunching/bunching.ipynb +++ b/examples/bunching/bunching.ipynb @@ -33,10 +33,10 @@ "id": "89d98045-89b5-4d05-a1b3-33f956ba329c", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:21.972774Z", - "iopub.status.busy": "2023-11-15T16:37:21.972421Z", - "iopub.status.idle": "2023-11-15T16:37:22.573329Z", - "shell.execute_reply": "2023-11-15T16:37:22.572736Z" + "iopub.execute_input": "2023-11-15T21:43:12.583496Z", + "iopub.status.busy": "2023-11-15T21:43:12.583317Z", + "iopub.status.idle": "2023-11-15T21:43:13.196397Z", + "shell.execute_reply": "2023-11-15T21:43:13.195786Z" } }, "outputs": [], @@ -51,10 +51,10 @@ "id": "2808d05b-41dc-44eb-ac98-ae00ec7ba97b", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:22.576333Z", - "iopub.status.busy": "2023-11-15T16:37:22.575855Z", - "iopub.status.idle": "2023-11-15T16:37:22.593411Z", - "shell.execute_reply": "2023-11-15T16:37:22.592942Z" + "iopub.execute_input": "2023-11-15T21:43:13.199048Z", + "iopub.status.busy": "2023-11-15T21:43:13.198793Z", + "iopub.status.idle": "2023-11-15T21:43:13.217129Z", + "shell.execute_reply": "2023-11-15T21:43:13.216550Z" } }, "outputs": [], @@ -73,10 +73,10 @@ "id": "f92a71d1-2804-4fba-a38b-3089351a910d", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:22.595536Z", - "iopub.status.busy": "2023-11-15T16:37:22.595360Z", - "iopub.status.idle": "2023-11-15T16:37:23.746043Z", - "shell.execute_reply": "2023-11-15T16:37:23.745435Z" + "iopub.execute_input": "2023-11-15T21:43:13.219950Z", + "iopub.status.busy": "2023-11-15T21:43:13.219351Z", + "iopub.status.idle": "2023-11-15T21:43:14.390925Z", + "shell.execute_reply": "2023-11-15T21:43:14.390250Z" } }, "outputs": [ @@ -121,10 +121,10 @@ "id": "95d51518-4dad-478d-a67f-4416e5e5a989", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:23.748326Z", - "iopub.status.busy": "2023-11-15T16:37:23.748146Z", - "iopub.status.idle": "2023-11-15T16:37:23.761871Z", - "shell.execute_reply": "2023-11-15T16:37:23.761376Z" + "iopub.execute_input": "2023-11-15T21:43:14.393510Z", + "iopub.status.busy": "2023-11-15T21:43:14.393132Z", + "iopub.status.idle": "2023-11-15T21:43:14.407145Z", + "shell.execute_reply": "2023-11-15T21:43:14.406592Z" } }, "outputs": [ @@ -150,10 +150,10 @@ "id": "9a0ba850-3e57-4b70-b0d1-31376166da55", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:23.763816Z", - "iopub.status.busy": "2023-11-15T16:37:23.763647Z", - "iopub.status.idle": "2023-11-15T16:37:23.775837Z", - "shell.execute_reply": "2023-11-15T16:37:23.775407Z" + "iopub.execute_input": "2023-11-15T21:43:14.409427Z", + "iopub.status.busy": "2023-11-15T21:43:14.409081Z", + "iopub.status.idle": "2023-11-15T21:43:14.421507Z", + "shell.execute_reply": "2023-11-15T21:43:14.420963Z" } }, "outputs": [ @@ -178,10 +178,10 @@ "id": "4756a087-6e40-4000-a4ea-158fc7a54d74", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:23.778129Z", - "iopub.status.busy": "2023-11-15T16:37:23.777671Z", - "iopub.status.idle": "2023-11-15T16:37:23.790094Z", - "shell.execute_reply": "2023-11-15T16:37:23.789521Z" + "iopub.execute_input": "2023-11-15T21:43:14.424109Z", + "iopub.status.busy": "2023-11-15T21:43:14.423630Z", + "iopub.status.idle": "2023-11-15T21:43:14.436403Z", + "shell.execute_reply": "2023-11-15T21:43:14.435919Z" } }, "outputs": [ @@ -206,10 +206,10 @@ "id": "1627bba1-710e-47b6-b923-9c64a6d32903", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:23.792271Z", - "iopub.status.busy": "2023-11-15T16:37:23.791849Z", - "iopub.status.idle": "2023-11-15T16:37:23.804165Z", - "shell.execute_reply": "2023-11-15T16:37:23.803603Z" + "iopub.execute_input": "2023-11-15T21:43:14.438638Z", + "iopub.status.busy": "2023-11-15T21:43:14.438277Z", + "iopub.status.idle": "2023-11-15T21:43:14.450694Z", + "shell.execute_reply": "2023-11-15T21:43:14.450120Z" } }, "outputs": [ @@ -242,10 +242,10 @@ "id": "d24b9496-e828-45f1-a055-4e27e7aeba31", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:23.806304Z", - "iopub.status.busy": "2023-11-15T16:37:23.805979Z", - "iopub.status.idle": "2023-11-15T16:37:23.808661Z", - "shell.execute_reply": "2023-11-15T16:37:23.808188Z" + "iopub.execute_input": "2023-11-15T21:43:14.453178Z", + "iopub.status.busy": "2023-11-15T21:43:14.452682Z", + "iopub.status.idle": "2023-11-15T21:43:14.455517Z", + "shell.execute_reply": "2023-11-15T21:43:14.455001Z" } }, "outputs": [], @@ -261,10 +261,10 @@ "id": "c6b5c501-bfdd-492d-891e-ba10e6b546ef", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:23.810663Z", - "iopub.status.busy": "2023-11-15T16:37:23.810490Z", - "iopub.status.idle": "2023-11-15T16:37:23.813258Z", - "shell.execute_reply": "2023-11-15T16:37:23.812787Z" + "iopub.execute_input": "2023-11-15T21:43:14.457777Z", + "iopub.status.busy": "2023-11-15T21:43:14.457447Z", + "iopub.status.idle": "2023-11-15T21:43:14.460396Z", + "shell.execute_reply": "2023-11-15T21:43:14.459833Z" } }, "outputs": [], @@ -278,10 +278,10 @@ "id": "50cda5af-4a97-4aa0-bf9c-c3984140d39c", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:23.815201Z", - "iopub.status.busy": "2023-11-15T16:37:23.815033Z", - "iopub.status.idle": "2023-11-15T16:37:25.878544Z", - "shell.execute_reply": "2023-11-15T16:37:25.877985Z" + "iopub.execute_input": "2023-11-15T21:43:14.462630Z", + "iopub.status.busy": "2023-11-15T21:43:14.462326Z", + "iopub.status.idle": "2023-11-15T21:43:16.535868Z", + "shell.execute_reply": "2023-11-15T21:43:16.535238Z" } }, "outputs": [ @@ -323,10 +323,10 @@ "id": "42b088fa-aaef-43df-910f-07cddac21bb0", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:25.880908Z", - "iopub.status.busy": "2023-11-15T16:37:25.880548Z", - "iopub.status.idle": "2023-11-15T16:37:26.207130Z", - "shell.execute_reply": "2023-11-15T16:37:26.206563Z" + "iopub.execute_input": "2023-11-15T21:43:16.538316Z", + "iopub.status.busy": "2023-11-15T21:43:16.537924Z", + "iopub.status.idle": "2023-11-15T21:43:16.865999Z", + "shell.execute_reply": "2023-11-15T21:43:16.865369Z" } }, "outputs": [ @@ -356,10 +356,10 @@ "id": "d9c0236c-cab9-463f-a2f3-010ef120b11c", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:26.209645Z", - "iopub.status.busy": "2023-11-15T16:37:26.209155Z", - "iopub.status.idle": "2023-11-15T16:37:26.541539Z", - "shell.execute_reply": "2023-11-15T16:37:26.540922Z" + "iopub.execute_input": "2023-11-15T21:43:16.868608Z", + "iopub.status.busy": "2023-11-15T21:43:16.868236Z", + "iopub.status.idle": "2023-11-15T21:43:17.203084Z", + "shell.execute_reply": "2023-11-15T21:43:17.202462Z" } }, "outputs": [ @@ -389,10 +389,10 @@ "id": "bbc674b0-67d8-4a5c-abe0-583bdf0270f2", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:26.544067Z", - "iopub.status.busy": "2023-11-15T16:37:26.543723Z", - "iopub.status.idle": "2023-11-15T16:37:26.553032Z", - "shell.execute_reply": "2023-11-15T16:37:26.552470Z" + "iopub.execute_input": "2023-11-15T21:43:17.205541Z", + "iopub.status.busy": "2023-11-15T21:43:17.205176Z", + "iopub.status.idle": "2023-11-15T21:43:17.214422Z", + "shell.execute_reply": "2023-11-15T21:43:17.213865Z" } }, "outputs": [ @@ -427,10 +427,10 @@ "id": "01b2f6d7-734c-4bea-add3-48ffa215dacc", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:26.555352Z", - "iopub.status.busy": "2023-11-15T16:37:26.554905Z", - "iopub.status.idle": "2023-11-15T16:37:26.558649Z", - "shell.execute_reply": "2023-11-15T16:37:26.558089Z" + "iopub.execute_input": "2023-11-15T21:43:17.216676Z", + "iopub.status.busy": "2023-11-15T21:43:17.216338Z", + "iopub.status.idle": "2023-11-15T21:43:17.220159Z", + "shell.execute_reply": "2023-11-15T21:43:17.219676Z" } }, "outputs": [ @@ -455,10 +455,10 @@ "id": "f5ecde78-9f42-40be-907b-81109f511015", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:26.560942Z", - "iopub.status.busy": "2023-11-15T16:37:26.560400Z", - "iopub.status.idle": "2023-11-15T16:37:26.564401Z", - "shell.execute_reply": "2023-11-15T16:37:26.563843Z" + "iopub.execute_input": "2023-11-15T21:43:17.222196Z", + "iopub.status.busy": "2023-11-15T21:43:17.222012Z", + "iopub.status.idle": "2023-11-15T21:43:17.225654Z", + "shell.execute_reply": "2023-11-15T21:43:17.225129Z" } }, "outputs": [ @@ -493,10 +493,10 @@ "id": "ac7ee4c6-1b02-40a1-96a3-3be6629da002", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:26.566864Z", - "iopub.status.busy": "2023-11-15T16:37:26.566448Z", - "iopub.status.idle": "2023-11-15T16:37:26.569223Z", - "shell.execute_reply": "2023-11-15T16:37:26.568732Z" + "iopub.execute_input": "2023-11-15T21:43:17.227701Z", + "iopub.status.busy": "2023-11-15T21:43:17.227525Z", + "iopub.status.idle": "2023-11-15T21:43:17.230090Z", + "shell.execute_reply": "2023-11-15T21:43:17.229575Z" } }, "outputs": [], @@ -510,10 +510,10 @@ "id": "af2982d8-a19d-481f-b9ec-940d5d02e290", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:26.571200Z", - "iopub.status.busy": "2023-11-15T16:37:26.571026Z", - "iopub.status.idle": "2023-11-15T16:37:26.604229Z", - "shell.execute_reply": "2023-11-15T16:37:26.603775Z" + "iopub.execute_input": "2023-11-15T21:43:17.232086Z", + "iopub.status.busy": "2023-11-15T21:43:17.231911Z", + "iopub.status.idle": "2023-11-15T21:43:17.265174Z", + "shell.execute_reply": "2023-11-15T21:43:17.264703Z" } }, "outputs": [], diff --git a/examples/fields/field_conversion/field_conversion.ipynb b/examples/fields/field_conversion/field_conversion.ipynb index 6b82f7b..d6d7576 100644 --- a/examples/fields/field_conversion/field_conversion.ipynb +++ b/examples/fields/field_conversion/field_conversion.ipynb @@ -12,10 +12,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:57.311997Z", - "iopub.status.busy": "2023-11-15T16:36:57.311521Z", - "iopub.status.idle": "2023-11-15T16:36:57.655572Z", - "shell.execute_reply": "2023-11-15T16:36:57.654982Z" + "iopub.execute_input": "2023-11-15T21:42:47.741935Z", + "iopub.status.busy": "2023-11-15T21:42:47.741447Z", + "iopub.status.idle": "2023-11-15T21:42:48.080059Z", + "shell.execute_reply": "2023-11-15T21:42:48.079525Z" }, "tags": [] }, @@ -37,10 +37,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:57.658618Z", - "iopub.status.busy": "2023-11-15T16:36:57.658367Z", - "iopub.status.idle": "2023-11-15T16:36:57.953685Z", - "shell.execute_reply": "2023-11-15T16:36:57.953110Z" + "iopub.execute_input": "2023-11-15T21:42:48.082772Z", + "iopub.status.busy": "2023-11-15T21:42:48.082356Z", + "iopub.status.idle": "2023-11-15T21:42:48.381536Z", + "shell.execute_reply": "2023-11-15T21:42:48.380866Z" }, "tags": [] }, @@ -63,10 +63,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:57.956539Z", - "iopub.status.busy": "2023-11-15T16:36:57.956163Z", - "iopub.status.idle": "2023-11-15T16:36:58.219993Z", - "shell.execute_reply": "2023-11-15T16:36:58.219394Z" + "iopub.execute_input": "2023-11-15T21:42:48.384521Z", + "iopub.status.busy": "2023-11-15T21:42:48.384289Z", + "iopub.status.idle": "2023-11-15T21:42:48.654389Z", + "shell.execute_reply": "2023-11-15T21:42:48.653796Z" }, "tags": [] }, @@ -104,17 +104,17 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:58.249399Z", - "iopub.status.busy": "2023-11-15T16:36:58.248962Z", - "iopub.status.idle": "2023-11-15T16:36:58.273101Z", - "shell.execute_reply": "2023-11-15T16:36:58.272622Z" + "iopub.execute_input": "2023-11-15T21:42:48.683993Z", + "iopub.status.busy": "2023-11-15T21:42:48.683586Z", + "iopub.status.idle": "2023-11-15T21:42:48.708790Z", + "shell.execute_reply": "2023-11-15T21:42:48.708206Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -144,17 +144,17 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:58.275459Z", - "iopub.status.busy": "2023-11-15T16:36:58.275097Z", - "iopub.status.idle": "2023-11-15T16:36:58.288479Z", - "shell.execute_reply": "2023-11-15T16:36:58.288024Z" + "iopub.execute_input": "2023-11-15T21:42:48.711320Z", + "iopub.status.busy": "2023-11-15T21:42:48.710823Z", + "iopub.status.idle": "2023-11-15T21:42:48.724103Z", + "shell.execute_reply": "2023-11-15T21:42:48.723524Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 5, @@ -179,10 +179,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:58.290720Z", - "iopub.status.busy": "2023-11-15T16:36:58.290382Z", - "iopub.status.idle": "2023-11-15T16:36:58.303894Z", - "shell.execute_reply": "2023-11-15T16:36:58.303343Z" + "iopub.execute_input": "2023-11-15T21:42:48.726404Z", + "iopub.status.busy": "2023-11-15T21:42:48.725976Z", + "iopub.status.idle": "2023-11-15T21:42:48.739102Z", + "shell.execute_reply": "2023-11-15T21:42:48.738610Z" } }, "outputs": [ @@ -213,10 +213,10 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:58.306134Z", - "iopub.status.busy": "2023-11-15T16:36:58.305968Z", - "iopub.status.idle": "2023-11-15T16:36:58.319372Z", - "shell.execute_reply": "2023-11-15T16:36:58.318831Z" + "iopub.execute_input": "2023-11-15T21:42:48.741272Z", + "iopub.status.busy": "2023-11-15T21:42:48.741094Z", + "iopub.status.idle": "2023-11-15T21:42:48.754958Z", + "shell.execute_reply": "2023-11-15T21:42:48.754464Z" } }, "outputs": [ @@ -243,17 +243,17 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:58.321587Z", - "iopub.status.busy": "2023-11-15T16:36:58.321268Z", - "iopub.status.idle": "2023-11-15T16:36:58.558985Z", - "shell.execute_reply": "2023-11-15T16:36:58.558395Z" + "iopub.execute_input": "2023-11-15T21:42:48.757148Z", + "iopub.status.busy": "2023-11-15T21:42:48.756971Z", + "iopub.status.idle": "2023-11-15T21:42:49.052796Z", + "shell.execute_reply": "2023-11-15T21:42:49.052147Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 8, @@ -294,17 +294,17 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:58.561358Z", - "iopub.status.busy": "2023-11-15T16:36:58.561009Z", - "iopub.status.idle": "2023-11-15T16:36:58.799692Z", - "shell.execute_reply": "2023-11-15T16:36:58.799066Z" + "iopub.execute_input": "2023-11-15T21:42:49.055341Z", + "iopub.status.busy": "2023-11-15T21:42:49.054882Z", + "iopub.status.idle": "2023-11-15T21:42:49.289241Z", + "shell.execute_reply": "2023-11-15T21:42:49.288640Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 9, @@ -350,10 +350,10 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:58.802038Z", - "iopub.status.busy": "2023-11-15T16:36:58.801850Z", - "iopub.status.idle": "2023-11-15T16:36:58.981922Z", - "shell.execute_reply": "2023-11-15T16:36:58.981318Z" + "iopub.execute_input": "2023-11-15T21:42:49.291773Z", + "iopub.status.busy": "2023-11-15T21:42:49.291444Z", + "iopub.status.idle": "2023-11-15T21:42:49.436154Z", + "shell.execute_reply": "2023-11-15T21:42:49.435487Z" }, "tags": [] }, @@ -390,10 +390,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:58.984505Z", - "iopub.status.busy": "2023-11-15T16:36:58.984146Z", - "iopub.status.idle": "2023-11-15T16:36:58.997947Z", - "shell.execute_reply": "2023-11-15T16:36:58.997382Z" + "iopub.execute_input": "2023-11-15T21:42:49.438789Z", + "iopub.status.busy": "2023-11-15T21:42:49.438413Z", + "iopub.status.idle": "2023-11-15T21:42:49.452899Z", + "shell.execute_reply": "2023-11-15T21:42:49.452411Z" }, "tags": [] }, @@ -429,10 +429,10 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:59.000193Z", - "iopub.status.busy": "2023-11-15T16:36:58.999853Z", - "iopub.status.idle": "2023-11-15T16:36:59.012119Z", - "shell.execute_reply": "2023-11-15T16:36:59.011686Z" + "iopub.execute_input": "2023-11-15T21:42:49.455348Z", + "iopub.status.busy": "2023-11-15T21:42:49.454957Z", + "iopub.status.idle": "2023-11-15T21:42:49.469453Z", + "shell.execute_reply": "2023-11-15T21:42:49.469036Z" }, "tags": [] }, @@ -463,10 +463,10 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:59.014083Z", - "iopub.status.busy": "2023-11-15T16:36:59.013922Z", - "iopub.status.idle": "2023-11-15T16:36:59.248587Z", - "shell.execute_reply": "2023-11-15T16:36:59.248107Z" + "iopub.execute_input": "2023-11-15T21:42:49.471711Z", + "iopub.status.busy": "2023-11-15T21:42:49.471523Z", + "iopub.status.idle": "2023-11-15T21:42:49.709363Z", + "shell.execute_reply": "2023-11-15T21:42:49.708768Z" } }, "outputs": [ @@ -495,10 +495,10 @@ "execution_count": 14, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:59.251112Z", - "iopub.status.busy": "2023-11-15T16:36:59.250937Z", - "iopub.status.idle": "2023-11-15T16:36:59.483009Z", - "shell.execute_reply": "2023-11-15T16:36:59.482396Z" + "iopub.execute_input": "2023-11-15T21:42:49.711934Z", + "iopub.status.busy": "2023-11-15T21:42:49.711494Z", + "iopub.status.idle": "2023-11-15T21:42:49.985726Z", + "shell.execute_reply": "2023-11-15T21:42:49.985123Z" } }, "outputs": [ diff --git a/examples/fields/field_conversion/index.html b/examples/fields/field_conversion/index.html index 3f34e14..b612086 100644 --- a/examples/fields/field_conversion/index.html +++ b/examples/fields/field_conversion/index.html @@ -1525,7 +1525,7 @@

3D rectangular field from ANSYS @@ -1577,7 +1577,7 @@

3D rectangular field from ANSYS @@ -1741,7 +1741,7 @@

3D rectangular field from ANSYS @@ -3193,7 +3193,7 @@

Write Impact-T @@ -3663,7 +3663,7 @@

Read ANSYS @@ -3802,7 +3802,7 @@

Read ANSYS diff --git a/examples/fields/field_expansion/field_expansion.ipynb b/examples/fields/field_expansion/field_expansion.ipynb index 7d79710..24735c0 100644 --- a/examples/fields/field_expansion/field_expansion.ipynb +++ b/examples/fields/field_expansion/field_expansion.ipynb @@ -14,10 +14,10 @@ "id": "526938d7-f85a-4fa2-962e-245842ffacdd", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:43.367076Z", - "iopub.status.busy": "2023-11-15T16:36:43.366605Z", - "iopub.status.idle": "2023-11-15T16:36:43.705549Z", - "shell.execute_reply": "2023-11-15T16:36:43.705021Z" + "iopub.execute_input": "2023-11-15T21:42:33.113162Z", + "iopub.status.busy": "2023-11-15T21:42:33.112982Z", + "iopub.status.idle": "2023-11-15T21:42:33.463185Z", + "shell.execute_reply": "2023-11-15T21:42:33.462560Z" } }, "outputs": [], @@ -39,10 +39,10 @@ "id": "6d31292f-04a5-4b69-847f-cbe928e08b7a", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:43.708496Z", - "iopub.status.busy": "2023-11-15T16:36:43.708002Z", - "iopub.status.idle": "2023-11-15T16:36:44.000079Z", - "shell.execute_reply": "2023-11-15T16:36:43.999474Z" + "iopub.execute_input": "2023-11-15T21:42:33.465923Z", + "iopub.status.busy": "2023-11-15T21:42:33.465682Z", + "iopub.status.idle": "2023-11-15T21:42:33.782350Z", + "shell.execute_reply": "2023-11-15T21:42:33.781710Z" } }, "outputs": [], @@ -56,10 +56,10 @@ "id": "5e6661cf-6adc-4cd3-b05a-4f1663f151c0", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:44.003000Z", - "iopub.status.busy": "2023-11-15T16:36:44.002612Z", - "iopub.status.idle": "2023-11-15T16:36:44.329771Z", - "shell.execute_reply": "2023-11-15T16:36:44.329139Z" + "iopub.execute_input": "2023-11-15T21:42:33.785935Z", + "iopub.status.busy": "2023-11-15T21:42:33.785435Z", + "iopub.status.idle": "2023-11-15T21:42:34.125770Z", + "shell.execute_reply": "2023-11-15T21:42:34.124346Z" } }, "outputs": [ @@ -90,10 +90,10 @@ "id": "44dc8f20-bdec-4ea2-8007-3af24a8e481d", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:44.332162Z", - "iopub.status.busy": "2023-11-15T16:36:44.331801Z", - "iopub.status.idle": "2023-11-15T16:36:44.600158Z", - "shell.execute_reply": "2023-11-15T16:36:44.599515Z" + "iopub.execute_input": "2023-11-15T21:42:34.128340Z", + "iopub.status.busy": "2023-11-15T21:42:34.127961Z", + "iopub.status.idle": "2023-11-15T21:42:34.398785Z", + "shell.execute_reply": "2023-11-15T21:42:34.398189Z" } }, "outputs": [ @@ -133,10 +133,10 @@ "id": "cd13b912-0c16-47c0-bc42-41dc3ec70ada", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:44.602696Z", - "iopub.status.busy": "2023-11-15T16:36:44.602326Z", - "iopub.status.idle": "2023-11-15T16:36:44.614627Z", - "shell.execute_reply": "2023-11-15T16:36:44.614177Z" + "iopub.execute_input": "2023-11-15T21:42:34.401384Z", + "iopub.status.busy": "2023-11-15T21:42:34.401000Z", + "iopub.status.idle": "2023-11-15T21:42:34.414556Z", + "shell.execute_reply": "2023-11-15T21:42:34.413988Z" } }, "outputs": [], @@ -150,10 +150,10 @@ "id": "28e99d21-acf4-411d-9cd7-01c77d4c73fa", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:44.616792Z", - "iopub.status.busy": "2023-11-15T16:36:44.616444Z", - "iopub.status.idle": "2023-11-15T16:36:44.630285Z", - "shell.execute_reply": "2023-11-15T16:36:44.629734Z" + "iopub.execute_input": "2023-11-15T21:42:34.417170Z", + "iopub.status.busy": "2023-11-15T21:42:34.416790Z", + "iopub.status.idle": "2023-11-15T21:42:34.432239Z", + "shell.execute_reply": "2023-11-15T21:42:34.431740Z" } }, "outputs": [], @@ -172,10 +172,10 @@ "id": "833a35d5-b804-4f63-b4db-be04ac459c76", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:44.632697Z", - "iopub.status.busy": "2023-11-15T16:36:44.632307Z", - "iopub.status.idle": "2023-11-15T16:36:44.836141Z", - "shell.execute_reply": "2023-11-15T16:36:44.835499Z" + "iopub.execute_input": "2023-11-15T21:42:34.434594Z", + "iopub.status.busy": "2023-11-15T21:42:34.434380Z", + "iopub.status.idle": "2023-11-15T21:42:34.642347Z", + "shell.execute_reply": "2023-11-15T21:42:34.641701Z" } }, "outputs": [ @@ -218,17 +218,17 @@ "id": "bacdc66f-068a-40e0-abb2-acd12a1343e7", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:44.838600Z", - "iopub.status.busy": "2023-11-15T16:36:44.838229Z", - "iopub.status.idle": "2023-11-15T16:36:45.066157Z", - "shell.execute_reply": "2023-11-15T16:36:45.065543Z" + "iopub.execute_input": "2023-11-15T21:42:34.644601Z", + "iopub.status.busy": "2023-11-15T21:42:34.644408Z", + "iopub.status.idle": "2023-11-15T21:42:34.876409Z", + "shell.execute_reply": "2023-11-15T21:42:34.875768Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 8, @@ -265,17 +265,17 @@ "id": "7bb3b16d-9ceb-4052-bfa9-ade9ee9466fe", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:45.068496Z", - "iopub.status.busy": "2023-11-15T16:36:45.068317Z", - "iopub.status.idle": "2023-11-15T16:36:45.275476Z", - "shell.execute_reply": "2023-11-15T16:36:45.274842Z" + "iopub.execute_input": "2023-11-15T21:42:34.878800Z", + "iopub.status.busy": "2023-11-15T21:42:34.878432Z", + "iopub.status.idle": "2023-11-15T21:42:35.114204Z", + "shell.execute_reply": "2023-11-15T21:42:35.113611Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 9, @@ -320,10 +320,10 @@ "id": "4e4eb2f9-0418-4cc3-97ec-74907c356dce", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:45.278155Z", - "iopub.status.busy": "2023-11-15T16:36:45.277943Z", - "iopub.status.idle": "2023-11-15T16:36:45.484926Z", - "shell.execute_reply": "2023-11-15T16:36:45.484341Z" + "iopub.execute_input": "2023-11-15T21:42:35.116739Z", + "iopub.status.busy": "2023-11-15T21:42:35.116375Z", + "iopub.status.idle": "2023-11-15T21:42:35.325115Z", + "shell.execute_reply": "2023-11-15T21:42:35.324507Z" } }, "outputs": [ @@ -362,17 +362,17 @@ "id": "6c5451c8-c33a-4a7b-b58b-29c2360bc8c3", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:45.487325Z", - "iopub.status.busy": "2023-11-15T16:36:45.487137Z", - "iopub.status.idle": "2023-11-15T16:36:45.502232Z", - "shell.execute_reply": "2023-11-15T16:36:45.501715Z" + "iopub.execute_input": "2023-11-15T21:42:35.327750Z", + "iopub.status.busy": "2023-11-15T21:42:35.327294Z", + "iopub.status.idle": "2023-11-15T21:42:35.342438Z", + "shell.execute_reply": "2023-11-15T21:42:35.341881Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 11, @@ -391,10 +391,10 @@ "id": "910f4a38-9083-4cee-a90a-d74a58959b27", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:45.504235Z", - "iopub.status.busy": "2023-11-15T16:36:45.504069Z", - "iopub.status.idle": "2023-11-15T16:36:45.773915Z", - "shell.execute_reply": "2023-11-15T16:36:45.773265Z" + "iopub.execute_input": "2023-11-15T21:42:35.344726Z", + "iopub.status.busy": "2023-11-15T21:42:35.344391Z", + "iopub.status.idle": "2023-11-15T21:42:35.669526Z", + "shell.execute_reply": "2023-11-15T21:42:35.668907Z" } }, "outputs": [ @@ -424,10 +424,10 @@ "id": "97b3964e-cdb4-4c6d-895a-449d92966464", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:45.776310Z", - "iopub.status.busy": "2023-11-15T16:36:45.776123Z", - "iopub.status.idle": "2023-11-15T16:36:46.060829Z", - "shell.execute_reply": "2023-11-15T16:36:46.060188Z" + "iopub.execute_input": "2023-11-15T21:42:35.672236Z", + "iopub.status.busy": "2023-11-15T21:42:35.671846Z", + "iopub.status.idle": "2023-11-15T21:42:36.007777Z", + "shell.execute_reply": "2023-11-15T21:42:36.007097Z" } }, "outputs": [ @@ -484,10 +484,10 @@ "id": "d2d45aa6-7bd1-4314-a1d9-41b8bddfaae2", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:46.063329Z", - "iopub.status.busy": "2023-11-15T16:36:46.063138Z", - "iopub.status.idle": "2023-11-15T16:36:46.415771Z", - "shell.execute_reply": "2023-11-15T16:36:46.415216Z" + "iopub.execute_input": "2023-11-15T21:42:36.010108Z", + "iopub.status.busy": "2023-11-15T21:42:36.009902Z", + "iopub.status.idle": "2023-11-15T21:42:36.380528Z", + "shell.execute_reply": "2023-11-15T21:42:36.379940Z" } }, "outputs": [ @@ -518,10 +518,10 @@ "id": "6351eda5-5129-44f1-a916-f87002f56d96", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:46.418484Z", - "iopub.status.busy": "2023-11-15T16:36:46.418119Z", - "iopub.status.idle": "2023-11-15T16:36:46.587220Z", - "shell.execute_reply": "2023-11-15T16:36:46.586626Z" + "iopub.execute_input": "2023-11-15T21:42:36.383235Z", + "iopub.status.busy": "2023-11-15T21:42:36.382853Z", + "iopub.status.idle": "2023-11-15T21:42:36.595183Z", + "shell.execute_reply": "2023-11-15T21:42:36.594561Z" } }, "outputs": [ @@ -564,10 +564,10 @@ "id": "3448a8f5-2d07-4906-b86c-f10b7a02922f", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:46.589505Z", - "iopub.status.busy": "2023-11-15T16:36:46.589331Z", - "iopub.status.idle": "2023-11-15T16:36:46.901130Z", - "shell.execute_reply": "2023-11-15T16:36:46.900568Z" + "iopub.execute_input": "2023-11-15T21:42:36.597668Z", + "iopub.status.busy": "2023-11-15T21:42:36.597472Z", + "iopub.status.idle": "2023-11-15T21:42:36.958694Z", + "shell.execute_reply": "2023-11-15T21:42:36.958094Z" } }, "outputs": [ @@ -599,10 +599,10 @@ "id": "62c2a4df-1222-4d7e-8e9c-0e9d97714217", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:46.903981Z", - "iopub.status.busy": "2023-11-15T16:36:46.903613Z", - "iopub.status.idle": "2023-11-15T16:36:47.202657Z", - "shell.execute_reply": "2023-11-15T16:36:47.202026Z" + "iopub.execute_input": "2023-11-15T21:42:36.961269Z", + "iopub.status.busy": "2023-11-15T21:42:36.961093Z", + "iopub.status.idle": "2023-11-15T21:42:37.306870Z", + "shell.execute_reply": "2023-11-15T21:42:37.306215Z" } }, "outputs": [ @@ -632,10 +632,10 @@ "id": "3a2f9fb9-3e37-4ea4-a0c6-92a94125562f", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:47.205102Z", - "iopub.status.busy": "2023-11-15T16:36:47.204714Z", - "iopub.status.idle": "2023-11-15T16:36:47.513729Z", - "shell.execute_reply": "2023-11-15T16:36:47.513141Z" + "iopub.execute_input": "2023-11-15T21:42:37.309332Z", + "iopub.status.busy": "2023-11-15T21:42:37.308965Z", + "iopub.status.idle": "2023-11-15T21:42:37.671086Z", + "shell.execute_reply": "2023-11-15T21:42:37.670439Z" } }, "outputs": [ @@ -673,10 +673,10 @@ "id": "00c58b7e-886f-40dd-bfa0-6eab49aa993b", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:47.516425Z", - "iopub.status.busy": "2023-11-15T16:36:47.516067Z", - "iopub.status.idle": "2023-11-15T16:36:47.839624Z", - "shell.execute_reply": "2023-11-15T16:36:47.838989Z" + "iopub.execute_input": "2023-11-15T21:42:37.673705Z", + "iopub.status.busy": "2023-11-15T21:42:37.673195Z", + "iopub.status.idle": "2023-11-15T21:42:38.050489Z", + "shell.execute_reply": "2023-11-15T21:42:38.049894Z" } }, "outputs": [ @@ -708,10 +708,10 @@ "id": "4829b28d-6e03-404f-acf4-dc12c0c5c929", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:47.841921Z", - "iopub.status.busy": "2023-11-15T16:36:47.841728Z", - "iopub.status.idle": "2023-11-15T16:36:48.188491Z", - "shell.execute_reply": "2023-11-15T16:36:48.187940Z" + "iopub.execute_input": "2023-11-15T21:42:38.053133Z", + "iopub.status.busy": "2023-11-15T21:42:38.052681Z", + "iopub.status.idle": "2023-11-15T21:42:38.400514Z", + "shell.execute_reply": "2023-11-15T21:42:38.399942Z" } }, "outputs": [ @@ -741,10 +741,10 @@ "id": "28607df0-5099-4f7b-9359-f465e52cb206", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:48.191032Z", - "iopub.status.busy": "2023-11-15T16:36:48.190842Z", - "iopub.status.idle": "2023-11-15T16:36:48.548695Z", - "shell.execute_reply": "2023-11-15T16:36:48.548129Z" + "iopub.execute_input": "2023-11-15T21:42:38.403132Z", + "iopub.status.busy": "2023-11-15T21:42:38.402776Z", + "iopub.status.idle": "2023-11-15T21:42:38.766327Z", + "shell.execute_reply": "2023-11-15T21:42:38.765731Z" } }, "outputs": [ @@ -774,10 +774,10 @@ "id": "1ed7bfbd-3b79-426d-adeb-edaa420473c7", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:48.551356Z", - "iopub.status.busy": "2023-11-15T16:36:48.550997Z", - "iopub.status.idle": "2023-11-15T16:36:48.909159Z", - "shell.execute_reply": "2023-11-15T16:36:48.908609Z" + "iopub.execute_input": "2023-11-15T21:42:38.769031Z", + "iopub.status.busy": "2023-11-15T21:42:38.768537Z", + "iopub.status.idle": "2023-11-15T21:42:39.131694Z", + "shell.execute_reply": "2023-11-15T21:42:39.131129Z" } }, "outputs": [ @@ -815,10 +815,10 @@ "id": "5571f83f-5307-45f6-bc87-bb208dac658c", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:48.911651Z", - "iopub.status.busy": "2023-11-15T16:36:48.911471Z", - "iopub.status.idle": "2023-11-15T16:36:49.223611Z", - "shell.execute_reply": "2023-11-15T16:36:49.223022Z" + "iopub.execute_input": "2023-11-15T21:42:39.134310Z", + "iopub.status.busy": "2023-11-15T21:42:39.133936Z", + "iopub.status.idle": "2023-11-15T21:42:39.454459Z", + "shell.execute_reply": "2023-11-15T21:42:39.453829Z" } }, "outputs": [ @@ -849,10 +849,10 @@ "id": "e4cfef90-189c-4c49-8755-e421c6ca7bf6", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:49.226329Z", - "iopub.status.busy": "2023-11-15T16:36:49.226122Z", - "iopub.status.idle": "2023-11-15T16:36:49.242452Z", - "shell.execute_reply": "2023-11-15T16:36:49.241981Z" + "iopub.execute_input": "2023-11-15T21:42:39.456781Z", + "iopub.status.busy": "2023-11-15T21:42:39.456590Z", + "iopub.status.idle": "2023-11-15T21:42:39.472585Z", + "shell.execute_reply": "2023-11-15T21:42:39.472084Z" } }, "outputs": [], @@ -897,10 +897,10 @@ "id": "539bcb9f-7441-41f0-970f-267028f8ba49", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:49.244742Z", - "iopub.status.busy": "2023-11-15T16:36:49.244385Z", - "iopub.status.idle": "2023-11-15T16:36:49.856215Z", - "shell.execute_reply": "2023-11-15T16:36:49.855610Z" + "iopub.execute_input": "2023-11-15T21:42:39.474894Z", + "iopub.status.busy": "2023-11-15T21:42:39.474706Z", + "iopub.status.idle": "2023-11-15T21:42:40.120546Z", + "shell.execute_reply": "2023-11-15T21:42:40.119976Z" } }, "outputs": [ @@ -908,9 +908,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/tmp/ipykernel_5526/1820762901.py:25: RuntimeWarning: divide by zero encountered in divide\n", + "/tmp/ipykernel_5629/1820762901.py:25: RuntimeWarning: divide by zero encountered in divide\n", " ax2.plot(abs((f5-f0)/f0), color='purple', label='relative fourier error')\n", - "/tmp/ipykernel_5526/1820762901.py:26: RuntimeWarning: divide by zero encountered in divide\n", + "/tmp/ipykernel_5629/1820762901.py:26: RuntimeWarning: divide by zero encountered in divide\n", " ax2.plot(abs((f6-f0)/f0), color='grey', label='relative spline error')\n" ] }, @@ -940,10 +940,10 @@ "id": "d64f302f-8542-443d-9994-049790f4285f", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:49.858646Z", - "iopub.status.busy": "2023-11-15T16:36:49.858206Z", - "iopub.status.idle": "2023-11-15T16:36:50.377738Z", - "shell.execute_reply": "2023-11-15T16:36:50.377090Z" + "iopub.execute_input": "2023-11-15T21:42:40.123207Z", + "iopub.status.busy": "2023-11-15T21:42:40.123018Z", + "iopub.status.idle": "2023-11-15T21:42:40.653760Z", + "shell.execute_reply": "2023-11-15T21:42:40.653131Z" } }, "outputs": [ @@ -973,10 +973,10 @@ "id": "a04450e4-8141-4a7b-a3c4-719737932af1", "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:50.380097Z", - "iopub.status.busy": "2023-11-15T16:36:50.379912Z", - "iopub.status.idle": "2023-11-15T16:36:51.008010Z", - "shell.execute_reply": "2023-11-15T16:36:51.007458Z" + "iopub.execute_input": "2023-11-15T21:42:40.656238Z", + "iopub.status.busy": "2023-11-15T21:42:40.655872Z", + "iopub.status.idle": "2023-11-15T21:42:41.241032Z", + "shell.execute_reply": "2023-11-15T21:42:41.240425Z" } }, "outputs": [ diff --git a/examples/fields/field_expansion/index.html b/examples/fields/field_expansion/index.html index be929d7..1f8e39d 100644 --- a/examples/fields/field_expansion/index.html +++ b/examples/fields/field_expansion/index.html @@ -1657,7 +1657,7 @@

Derivative array @@ -2535,9 +2535,9 @@

Compare Fourier and Spline diff --git a/examples/labels/labels.ipynb b/examples/labels/labels.ipynb index 97aee57..ea306cc 100644 --- a/examples/labels/labels.ipynb +++ b/examples/labels/labels.ipynb @@ -14,10 +14,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:37.306060Z", - "iopub.status.busy": "2023-11-15T16:36:37.305888Z", - "iopub.status.idle": "2023-11-15T16:36:37.319753Z", - "shell.execute_reply": "2023-11-15T16:36:37.319321Z" + "iopub.execute_input": "2023-11-15T21:42:26.026723Z", + "iopub.status.busy": "2023-11-15T21:42:26.026524Z", + "iopub.status.idle": "2023-11-15T21:42:26.040749Z", + "shell.execute_reply": "2023-11-15T21:42:26.040307Z" }, "tags": [] }, @@ -33,10 +33,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:37.321835Z", - "iopub.status.busy": "2023-11-15T16:36:37.321665Z", - "iopub.status.idle": "2023-11-15T16:36:37.659883Z", - "shell.execute_reply": "2023-11-15T16:36:37.659285Z" + "iopub.execute_input": "2023-11-15T21:42:26.043258Z", + "iopub.status.busy": "2023-11-15T21:42:26.042792Z", + "iopub.status.idle": "2023-11-15T21:42:26.389016Z", + "shell.execute_reply": "2023-11-15T21:42:26.388445Z" }, "tags": [] }, @@ -60,10 +60,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:37.689590Z", - "iopub.status.busy": "2023-11-15T16:36:37.689019Z", - "iopub.status.idle": "2023-11-15T16:36:37.984653Z", - "shell.execute_reply": "2023-11-15T16:36:37.984050Z" + "iopub.execute_input": "2023-11-15T21:42:26.418875Z", + "iopub.status.busy": "2023-11-15T21:42:26.418315Z", + "iopub.status.idle": "2023-11-15T21:42:26.718549Z", + "shell.execute_reply": "2023-11-15T21:42:26.717896Z" }, "tags": [] }, @@ -85,10 +85,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:37.987284Z", - "iopub.status.busy": "2023-11-15T16:36:37.987096Z", - "iopub.status.idle": "2023-11-15T16:36:38.029197Z", - "shell.execute_reply": "2023-11-15T16:36:38.028754Z" + "iopub.execute_input": "2023-11-15T21:42:26.721480Z", + "iopub.status.busy": "2023-11-15T21:42:26.721247Z", + "iopub.status.idle": "2023-11-15T21:42:26.763608Z", + "shell.execute_reply": "2023-11-15T21:42:26.763094Z" }, "tags": [] }, @@ -109,10 +109,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:38.031298Z", - "iopub.status.busy": "2023-11-15T16:36:38.031132Z", - "iopub.status.idle": "2023-11-15T16:36:38.044796Z", - "shell.execute_reply": "2023-11-15T16:36:38.044247Z" + "iopub.execute_input": "2023-11-15T21:42:26.766250Z", + "iopub.status.busy": "2023-11-15T21:42:26.766067Z", + "iopub.status.idle": "2023-11-15T21:42:26.780220Z", + "shell.execute_reply": "2023-11-15T21:42:26.779730Z" }, "tags": [] }, @@ -144,10 +144,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:38.046956Z", - "iopub.status.busy": "2023-11-15T16:36:38.046776Z", - "iopub.status.idle": "2023-11-15T16:36:38.060190Z", - "shell.execute_reply": "2023-11-15T16:36:38.059627Z" + "iopub.execute_input": "2023-11-15T21:42:26.782303Z", + "iopub.status.busy": "2023-11-15T21:42:26.782131Z", + "iopub.status.idle": "2023-11-15T21:42:26.794458Z", + "shell.execute_reply": "2023-11-15T21:42:26.793916Z" }, "tags": [] }, @@ -179,10 +179,10 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:38.062653Z", - "iopub.status.busy": "2023-11-15T16:36:38.062202Z", - "iopub.status.idle": "2023-11-15T16:36:38.074495Z", - "shell.execute_reply": "2023-11-15T16:36:38.073922Z" + "iopub.execute_input": "2023-11-15T21:42:26.796915Z", + "iopub.status.busy": "2023-11-15T21:42:26.796389Z", + "iopub.status.idle": "2023-11-15T21:42:26.808552Z", + "shell.execute_reply": "2023-11-15T21:42:26.808114Z" }, "tags": [] }, @@ -207,10 +207,10 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:38.076703Z", - "iopub.status.busy": "2023-11-15T16:36:38.076300Z", - "iopub.status.idle": "2023-11-15T16:36:38.087616Z", - "shell.execute_reply": "2023-11-15T16:36:38.087032Z" + "iopub.execute_input": "2023-11-15T21:42:26.810840Z", + "iopub.status.busy": "2023-11-15T21:42:26.810393Z", + "iopub.status.idle": "2023-11-15T21:42:26.821760Z", + "shell.execute_reply": "2023-11-15T21:42:26.821204Z" }, "tags": [] }, @@ -225,10 +225,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:38.089795Z", - "iopub.status.busy": "2023-11-15T16:36:38.089602Z", - "iopub.status.idle": "2023-11-15T16:36:39.488201Z", - "shell.execute_reply": "2023-11-15T16:36:39.487579Z" + "iopub.execute_input": "2023-11-15T21:42:26.823936Z", + "iopub.status.busy": "2023-11-15T21:42:26.823759Z", + "iopub.status.idle": "2023-11-15T21:42:28.541533Z", + "shell.execute_reply": "2023-11-15T21:42:28.540919Z" }, "tags": [] }, @@ -1536,10 +1536,10 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:39.490847Z", - "iopub.status.busy": "2023-11-15T16:36:39.490449Z", - "iopub.status.idle": "2023-11-15T16:36:39.504438Z", - "shell.execute_reply": "2023-11-15T16:36:39.503863Z" + "iopub.execute_input": "2023-11-15T21:42:28.544461Z", + "iopub.status.busy": "2023-11-15T21:42:28.544108Z", + "iopub.status.idle": "2023-11-15T21:42:28.561417Z", + "shell.execute_reply": "2023-11-15T21:42:28.560860Z" } }, "outputs": [], @@ -1552,10 +1552,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:39.506714Z", - "iopub.status.busy": "2023-11-15T16:36:39.506522Z", - "iopub.status.idle": "2023-11-15T16:36:39.519173Z", - "shell.execute_reply": "2023-11-15T16:36:39.518740Z" + "iopub.execute_input": "2023-11-15T21:42:28.564059Z", + "iopub.status.busy": "2023-11-15T21:42:28.563860Z", + "iopub.status.idle": "2023-11-15T21:42:28.580980Z", + "shell.execute_reply": "2023-11-15T21:42:28.580456Z" } }, "outputs": [ @@ -1586,10 +1586,10 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:39.521638Z", - "iopub.status.busy": "2023-11-15T16:36:39.521130Z", - "iopub.status.idle": "2023-11-15T16:36:39.533400Z", - "shell.execute_reply": "2023-11-15T16:36:39.532913Z" + "iopub.execute_input": "2023-11-15T21:42:28.583563Z", + "iopub.status.busy": "2023-11-15T21:42:28.583363Z", + "iopub.status.idle": "2023-11-15T21:42:28.600699Z", + "shell.execute_reply": "2023-11-15T21:42:28.600176Z" }, "tags": [] }, @@ -1614,10 +1614,10 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:39.535714Z", - "iopub.status.busy": "2023-11-15T16:36:39.535343Z", - "iopub.status.idle": "2023-11-15T16:36:41.138056Z", - "shell.execute_reply": "2023-11-15T16:36:41.137557Z" + "iopub.execute_input": "2023-11-15T21:42:28.603672Z", + "iopub.status.busy": "2023-11-15T21:42:28.603198Z", + "iopub.status.idle": "2023-11-15T21:42:30.742152Z", + "shell.execute_reply": "2023-11-15T21:42:30.741602Z" }, "tags": [] }, diff --git a/examples/normalized_coordinates/index.html b/examples/normalized_coordinates/index.html index 023b27e..d4fe131 100644 --- a/examples/normalized_coordinates/index.html +++ b/examples/normalized_coordinates/index.html @@ -1502,7 +1502,7 @@

Simple Normalized Coordinates @@ -1566,7 +1566,7 @@

Basic Usage @@ -2192,7 +2192,7 @@

Slice statistics @@ -2433,7 +2433,7 @@

Resampling @@ -2483,7 +2483,7 @@

Resampling @@ -2522,7 +2522,7 @@

Resampling @@ -3851,7 +3851,7 @@

Manual plotting @@ -1670,7 +1670,7 @@

Genesis4 HDF5 format diff --git a/examples/read_examples/read_examples.ipynb b/examples/read_examples/read_examples.ipynb index c2fe0ba..23ad819 100644 --- a/examples/read_examples/read_examples.ipynb +++ b/examples/read_examples/read_examples.ipynb @@ -12,10 +12,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:03.794089Z", - "iopub.status.busy": "2023-11-15T16:36:03.793908Z", - "iopub.status.idle": "2023-11-15T16:36:03.807314Z", - "shell.execute_reply": "2023-11-15T16:36:03.806811Z" + "iopub.execute_input": "2023-11-15T21:41:51.449592Z", + "iopub.status.busy": "2023-11-15T21:41:51.449406Z", + "iopub.status.idle": "2023-11-15T21:41:51.463827Z", + "shell.execute_reply": "2023-11-15T21:41:51.463298Z" }, "tags": [] }, @@ -31,10 +31,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:03.809740Z", - "iopub.status.busy": "2023-11-15T16:36:03.809313Z", - "iopub.status.idle": "2023-11-15T16:36:04.667300Z", - "shell.execute_reply": "2023-11-15T16:36:04.666685Z" + "iopub.execute_input": "2023-11-15T21:41:51.466550Z", + "iopub.status.busy": "2023-11-15T21:41:51.466187Z", + "iopub.status.idle": "2023-11-15T21:41:52.474523Z", + "shell.execute_reply": "2023-11-15T21:41:52.473925Z" }, "tags": [] }, @@ -56,10 +56,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:04.670216Z", - "iopub.status.busy": "2023-11-15T16:36:04.669960Z", - "iopub.status.idle": "2023-11-15T16:36:04.683096Z", - "shell.execute_reply": "2023-11-15T16:36:04.682663Z" + "iopub.execute_input": "2023-11-15T21:41:52.477687Z", + "iopub.status.busy": "2023-11-15T21:41:52.477107Z", + "iopub.status.idle": "2023-11-15T21:41:52.490557Z", + "shell.execute_reply": "2023-11-15T21:41:52.490088Z" }, "tags": [] }, @@ -80,10 +80,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:04.685368Z", - "iopub.status.busy": "2023-11-15T16:36:04.685048Z", - "iopub.status.idle": "2023-11-15T16:36:04.706139Z", - "shell.execute_reply": "2023-11-15T16:36:04.705561Z" + "iopub.execute_input": "2023-11-15T21:41:52.492972Z", + "iopub.status.busy": "2023-11-15T21:41:52.492547Z", + "iopub.status.idle": "2023-11-15T21:41:52.515344Z", + "shell.execute_reply": "2023-11-15T21:41:52.514759Z" }, "tags": [] }, @@ -132,10 +132,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:04.734976Z", - "iopub.status.busy": "2023-11-15T16:36:04.734637Z", - "iopub.status.idle": "2023-11-15T16:36:05.300092Z", - "shell.execute_reply": "2023-11-15T16:36:05.299470Z" + "iopub.execute_input": "2023-11-15T21:41:52.545811Z", + "iopub.status.busy": "2023-11-15T21:41:52.545364Z", + "iopub.status.idle": "2023-11-15T21:41:53.127247Z", + "shell.execute_reply": "2023-11-15T21:41:53.126567Z" }, "tags": [] }, @@ -168,10 +168,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:05.302617Z", - "iopub.status.busy": "2023-11-15T16:36:05.302251Z", - "iopub.status.idle": "2023-11-15T16:36:05.315115Z", - "shell.execute_reply": "2023-11-15T16:36:05.314553Z" + "iopub.execute_input": "2023-11-15T21:41:53.129818Z", + "iopub.status.busy": "2023-11-15T21:41:53.129441Z", + "iopub.status.idle": "2023-11-15T21:41:53.142517Z", + "shell.execute_reply": "2023-11-15T21:41:53.141908Z" } }, "outputs": [], @@ -184,17 +184,17 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:05.317597Z", - "iopub.status.busy": "2023-11-15T16:36:05.317247Z", - "iopub.status.idle": "2023-11-15T16:36:05.455517Z", - "shell.execute_reply": "2023-11-15T16:36:05.454905Z" + "iopub.execute_input": "2023-11-15T21:41:53.145123Z", + "iopub.status.busy": "2023-11-15T21:41:53.144688Z", + "iopub.status.idle": "2023-11-15T21:41:53.286433Z", + "shell.execute_reply": "2023-11-15T21:41:53.285775Z" } }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -212,16 +212,16 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:05.458030Z", - "iopub.status.busy": "2023-11-15T16:36:05.457713Z", - "iopub.status.idle": "2023-11-15T16:36:05.872998Z", - "shell.execute_reply": "2023-11-15T16:36:05.872356Z" + "iopub.execute_input": "2023-11-15T21:41:53.288775Z", + "iopub.status.busy": "2023-11-15T21:41:53.288600Z", + "iopub.status.idle": "2023-11-15T21:41:53.748294Z", + "shell.execute_reply": "2023-11-15T21:41:53.747673Z" } }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAG2CAYAAACeUpnVAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAB9a0lEQVR4nO3dd3xT1d8H8M+9md2ljC5KqewtyCxLZSvDCS4QZDgQqYgIKoI/VAR9QFFARZaAuABFRZYCyt5LNjJKB2WUpjNN7j3PH2lDQwdtadK0/bxfr0Bz7rk35zRN8s2ZkhBCgIiIiKgCk0u7AERERESljQERERERVXgMiIiIiKjCY0BEREREFR4DIiIiIqrwGBARERFRhceAiIiIiCo8BkRERERU4TEgIiIiogpPW9oFKAtUVUVsbCx8fHwgSVJpF4eIiApBCIHk5GSEhIRAlvn9n25DlHFbtmwRvXv3FsHBwQKAWLVqlcNxVVXFpEmTRHBwsDAajaJz587i6NGjRXqM6OhoAYA33njjjbcyeIuOji7BTx0qr8p8C1FqaiqaNWuGIUOG4NFHH811fPr06ZgxYwYWLVqEunXr4r333kO3bt1w8uRJ+Pj4FOoxsvNFR0fD19e3RMtPRETOYTKZEBYWVuj3eqrYJCHKz+aukiRh1apVeOihhwAAQgiEhIQgKioKb7zxBgDAbDYjMDAQ06ZNw/PPP1+o65pMJvj5+SEpKYkBERFRGcH3biqKct2peu7cOcTHx6N79+72NIPBgM6dO2P79u35nmc2m2EymRxuREREVH6V+S6zgsTHxwMAAgMDHdIDAwNx4cKFfM+bOnUq3n33XaeWjchVivK3PGnSJCeWhIjIfZXrgCjbrTPDhBAFzhabMGECxowZY7+f3Q9N5M6cGcTnd20GUERUXpTrgCgoKAiAraUoODjYnp6QkJCr1Sgng8EAg8Hg9PIRFRdbMImISla5DogiIiIQFBSEDRs2oHnz5gCAzMxMbNmyBdOmTSvl0hG5HwZaRFRRlfmAKCUlBWfOnLHfP3fuHA4ePIiAgADUqFEDUVFR+OCDD1CnTh3UqVMHH3zwATw9PfHUU0+VYqmJiIjInZT5gGjv3r2477777Pezx/48++yzWLRoEcaNG4f09HS89NJLSExMRJs2bbB+/XquS0FERER2ZT4guvfee1HQUkqSJGHy5MmYPHmy6wpFREREZUq5XoeIiIiIqDDK1UrVzsLVTqm0lMVBzpyKT+6C791UFGwhIiIiogqPARERERFVeAyIiIiIqMIr87PMiMqLsjheiIiovGBAREQuwf3QiMidscuMiIiIKjy2EBFRiWLXHxGVRS4PiDIyMnD48GEkJCRAVVWHY3379nV1cYhcjgEDEZH7cWlAtHbtWgwaNAhXr17NdUySJCiK4sriEBEREQFw8Riil19+GY8//jji4uKgqqrDjcEQERERlRaXBkQJCQkYM2YMAgMDXfmwRERERAVyaUD02GOPYfPmza58SCIiIqLbcukYos8//xyPP/44/vnnHzRp0gQ6nc7h+CuvvOLK4hAREREBcHFA9O2332LdunXw8PDA5s2bIUmS/ZgkSQyIqFzhbDIiorLDpQHR22+/jf/9738YP348ZJlrQhIREZF7cGlUkpmZiQEDBjAYIiIiIrfi0sjk2Wefxffff+/KhyQiIiK6LZd2mSmKgunTp2PdunVo2rRprkHVM2bMcGVxiIiIiAC4OCA6cuQImjdvDgA4evSow7GcA6yJiIiIXEkSQojSLoS7M5lM8PPzQ1JSEnx9fUu7OOSGOKPMNSZNmlTaRaAyhO/dVBRuMbr5ypUrWL58eWkXg4iIiCool3aZ/e9//8sz/ezZs1i5ciWefPJJVxaHiIiICICLA6JVq1Y53FcUBdHR0TCZTJgyZYori0JERERk59KA6MCBA7nSrFYroqKicOzYMVcWhYiIiMjOpQFRngXQahEVFYWmTZuWdlGIiIiogir1gAgALly4gIiIiNIuBtFtcTYZEVH55NKAaNasWbnS4uPjsXDhQvTp08fhODd6JaJb5ReQcjo+Ed0plwZEM2fOzDPdaDRiw4YN2LBhAwDbIo0MiIiIiMhVXBIQvfnmm3jooYdw7tw5VzwcERERUZG4ZGHGuLg49O7dG8HBwRgxYgTWrFkDs9nsiocmIiIiui2XBEQLFy7E5cuX8cMPP8Df3x9jxoxBlSpV8Mgjj2DRokW4evWqK4pBRERElCeXbd0hSRI6duyI6dOn48SJE9i9ezfatm2LefPmITQ0FJ06dcLHH3+MmJgYVxWJiIiICICbbO6akJCAX3/9FatXr0bHjh0xduzY0i6SA24QWPFwen3Zx5lnxPduKgq3WIeoWrVqGDp0KIYOHVraRSEiIqIKyCVdZn/99RcaNmwIk8mU61hSUhIaNWqEf/75xxVFISIiIsrFJQHRJ598guHDh+fZZOnn54fnn38eM2bMcEVRiIiIiHJxSUB06NAh9OzZM9/j3bt3x759+1xRFCIiIqJcXBIQXb58GTqdLt/jWq0WV65ccUVRiIiIiHJxyaDq0NBQHDlyBLVr187z+OHDhxEcHOyKohDlwhll5RP3PSOionBJC9EDDzyAd955BxkZGbmOpaenY9KkSejdu7crikJERESUi0taiN5++22sXLkSdevWxcsvv4x69epBkiQcP34cs2fPhqIoeOutt1xRFCIiIqJcXBIQBQYGYvv27XjxxRcxYcIEZK8FKUkSevTogTlz5iAwMNAVRaEKjF1jRESUH5ctzBgeHo41a9YgMTERZ86cAQDUrl0blSpVclURiIiIiPLk8pWqV65ciZkzZ+L06dMAgDp16iAqKgrDhg1zdVGIqALiYGsiyotLA6KJEydi5syZGDVqFNq1awcA2LFjB1599VWcP38e7733niuLQ0RERATAxQHR3LlzMW/ePDz55JP2tL59+6Jp06YYNWoUAyIiIiIqFS6Zdp9NURS0bNkyV/o999wDq9XqyqIQERER2Ukie8qXC4waNQo6nS7XvmVjx45Feno6Zs+e7aqiFInJZIKfnx+SkpLy3I+N3Atnk1FJ4biiso3v3VQULh9UPX/+fKxfvx5t27YFAOzcuRPR0dEYNGgQxowZY8/HzV6JiIjIVVwaEB09ehQtWrQAAJw9exYAULVqVVStWhVHjx6155MkyZXFIiIiogrOpQHRpk2bXPlwRERERIXi0kHVRERERO7IpYOqyyoOzHNfHEBNpYGDrcsGvndTUbCFiIiIiCo8l88yIyoOtgQREZEzMSAiIiqivAJ0dqMRlW3sMiMiIqIKjy1E5FbYNUZERKWBARERUQnIL5hnVxpR2cAuMyIiIqrwuA5RIXAtC+dg9xhVZGw5cj6+d1NRsIWIiIiIKjy2EBUCv2XcGbYEERUOW41KFt+7qSg4qJpKDAMfIiIqqypUC9GcOXPw0UcfIS4uDo0aNcInn3yCjh073vY8fsvIjcEPUelji1LB+N5NRVFhWoi+//57REVFYc6cOWjfvj2+/PJL9OrVC8eOHUONGjVKu3hui4EPERFVBBWmhahNmzZo0aIF5s6da09r0KABHnroIUydOtUhr9lshtlstt9PSkpCjRo1EB0dXS6+ZdxaXyKquCZMmFDaRXAak8mEsLAw3LhxA35+fqVdHHJzFSIgyszMhKenJ3788Uc8/PDD9vTRo0fj4MGD2LJli0P+yZMns2WEiKiciI6ORvXq1Uu7GOTmKkSX2dWrV6EoCgIDAx3SAwMDER8fnyv/hAkTMGbMGPt9VVVx/fp1VK5cGZIkFasM2d9UyksrUzbWq2xhvcoW1uvOCCGQnJyMkJAQpz0GlR8VIiDKdmswI4TIM8AxGAwwGAwOaf7+/iVSBl9f33L1xpaN9SpbWK+yhfUqPnaVUWFViIUZq1SpAo1Gk6s1KCEhIVerEREREVU8FSIg0uv1uOeee7BhwwaH9A0bNiAyMrKUSkVERETuosJ0mY0ZMwYDBw5Ey5Yt0a5dO3z11Ve4ePEiXnjhBZc8vsFgwKRJk3J1xZV1rFfZwnqVLawXketUiFlm2ebMmYPp06cjLi4OjRs3xsyZM9GpU6fSLhYRERGVsgoVEBERERHlpUKMISIiIiIqCAMiIiIiqvAYEBEREVGFx4CIiIiIKjwGRERERFThMSAiIiKiCo8BEREREVV4DIiIiIiowqswW3fcCVVVERsbCx8fH0iSVNrFISKiQhBCIDk5GSEhIZDlkvn+z8+DsqUofwMMiAohNjYWYWFhpV0MIiIqhujoaFSvXr1ErsXPg7KpMH8DDIgKwcfHB4DtF+rr61vKpSEiosIwmUwICwuzv4eXBH4elC1F+RtgQFQI2c2ivr6+fAEQEZUxJdm1xc+DsqkwfwMcVE1EREQVHgMiIiIiqvAYEBEREVGFx4CIiIiIKjwGRERERFThMSBykdSkVKSnZuRKvx6fCFVVS6FERERElI3T7p3szMFz+HnWGvy57B9odFr0Gno/er/QHeePXMRPM37Fid1nEFI7CI9G9Ua3QZ3g4e1R2kUmIiKqcCQhhCjtQrg7k8kEPz8/JCUlFWndiZWf/o65ry6CRitDsdpagWStDDX7Z40MVVEhSYAA4O3vhWXn58LTh0EREdGdKu57t6uvSc5TlOeLXWZOFHM6ziEYAmAPhgBAVWw/CwFAACmJqUhNSnN1MYmIiCo8BkROVtTmt4SLV51SDiIiIsofAyInqt08AkIVkDWF/zVHdXwbE/t9iGM7TjqxZERERJQTAyIn6jW0C5aem4P+Y/sWPigSwO41B/B2nw+dWzgiIiKyY0DkZNXCqmDo1KdRv3XtQp+jKiqsmVYnloqIiIhyYkDkZIqiYNvPu3Hh2KUinWdOz8Sar/+EOd3spJIRERFRNgZETnTgryN4JmIkJj/yEdKS04t0rqqomDniCwwIGYE/5v/ppBISERERwIDIqbau3IXrsdcBAEIt3nJPqUlpWLvgr5IsFhEREd2CAZGTSUWYYZYfqyX3eKLLF64gMyPzjq9NRERE3LrDqTy8jVCsim0l6jtYD/zU3v/wv8f/D/1e7okbCSas/OQ3HNtxCj6VvND3pZ7o/WJ3VAkJKLmCExERVTDcuqMQirtUuzndjI1L/sZPM3/DpZOxd1QGh+0/srb8yP5ZkiV8fXQmqtcJvqPHICIqT7h1B3HrDjdh8DDgwRHdsODYJ6jZOOyOrqXkseVH9s+KRcGNhKQ7uj4REVFFxoDIBSRJgpevp1Mf48K/RZvWT0RERDcxIHKRpp0bAhKKtI1HUXzywpcY2eoN/LNip1OuT0REVJ65VUCUnJyMqKgohIeHw8PDA5GRkdizZ0+B58yePRsNGjSAh4cH6tWrh2+++SZXnhUrVqBhw4YwGAxo2LAhVq1a5awq5Ou595/C4lOf4eFRvZz2GKcPnMP/Hv8/pKdmOO0xiIiIyiO3CoiGDRuGDRs2YMmSJThy5Ai6d++Orl27IiYmJs/8c+fOxYQJEzB58mT8+++/ePfddzFy5Ej8+uuv9jw7duzAgAEDMHDgQBw6dAgDBw5E//79sWvXLldVyy6kVhBemDEYnj4eTrl+9lpHOccYERER0e25zSyz9PR0+Pj44JdffsGDDz5oT7/77rvRu3dvvPfee7nOiYyMRPv27fHRRx/Z06KiorB3715s3boVADBgwACYTCb88ccf9jw9e/ZEpUqVsHz58jzLYjabYTbf3DLDZDIhLCysxGYVPFTpWaQmpd3xdfLz9NuPot/LvVCpmp/THoOIyN2VxIwwZ38ekHOVyVlmVqsViqLAaDQ6pHt4eNiDm1uZzeY88+/evRsWiwWArYWoe/fuDnl69OiB7du351uWqVOnws/Pz34LC7uzGWK3GjnrOQRFVCvRa+b07Qcr8VTY81jw1rdOewwioorA2Z8H5D7cJiDy8fFBu3btMGXKFMTGxkJRFCxduhS7du1CXFxcnuf06NEDX3/9Nfbt2wchBPbu3YsFCxbAYrHg6tWrAID4+HgEBgY6nBcYGIj4+Ph8yzJhwgQkJSXZb9HR0SVXUQDdBnbG4tOfYdziUSV63WxCFbBaFKxbuMkp1yciqiic/XlA7sNtAiIAWLJkCYQQCA0NhcFgwKxZs/DUU09Bo9HkmX/ixIno1asX2rZtC51Oh379+mHw4MEA4HCOJEkO5wkhcqXlZDAY4Ovr63ArabIso/n9jUr8ujkpeYwlijkTB9P1ZKc+LhFReeGKzwNyD24VENWqVQtbtmxBSkoKoqOj7V1fEREReeb38PDAggULkJaWhvPnz+PixYuoWbMmfHx8UKVKFQBAUFBQrtaghISEXK1GpcHgaYCskaHROj4N2cHanU7RT7piwugOE7Hlhx34+6cdeLXTRAyu+woGhIzAjOFzce7IhTu6PhERUXnhVgFRNi8vLwQHByMxMRHr1q1Dv379Csyv0+lQvXp1aDQafPfdd+jduzdk2Va1du3aYcOGDQ75169fj8jISKeVv7B8Knlj3pEZeGB4N+iNuqw0Lzz55sN4bcFLaNKxwR1cXQIg4cTuM3jviRmY0n8Gju04BQCwZlqxfvFmjGg2Fvv/PHLnFSEiIirj3Gpz13Xr1kEIgXr16uHMmTN4/fXXUa9ePQwZMgSArS83JibGvtbQqVOnsHv3brRp0waJiYmYMWMGjh49isWLF9uvOXr0aHTq1AnTpk1Dv3798Msvv2Djxo35DtR2tRr1Q/HK7GF47v0ncWrvWTTuUB96ox4A0HPwfXjnoWnY+ds++5R6RxKQs+sv54TBrPRbt/nIlr0VSGL8jRKrCxERUVnlVi1ESUlJGDlyJOrXr49BgwahQ4cOWL9+PXQ6W+tJXFwcLl68aM+vKAr+7//+D82aNUO3bt2QkZGB7du3o2bNmvY8kZGR+O6777Bw4UI0bdoUixYtwvfff482bdq4unoF8vb3QouuTe3BUM70At0aBEm3BEm3cWrfWSiKUpSiEhERlTtusw6ROyvN3Y3XLtyET1/8CopVyaeVCMjVUpSnrHPzeLqrhlXGY2P64KFRvexdjUREZR13u6cyuQ4R5a3nkPvwfcxXGPrB09Do8p5tV7gWISnrltuV6GuY++oiXDye94rgRERE5R0DojLAt7IPBozrh7C6IU59HG75QUREFRUDojJE1sgFrp90p76dugrRJ2Oddn0iIiJ3xYCoDHlhxrNo0LaO7U7OuEiosI8RykmIPNLzC6gk/L1iJ55r+ComP/YxOLSMiIgqEreadk8Fa35/EzS/vwnOHDiHF1uOczxoD34kOARBAlkzz2x3paxB00JVs87JPiBBKLbztq3a48RaEBERuR+2EJVBtZtHFDAbLK+WHVuaQ3ebJAGSnO80fVV1HE906VQs4s5dLmaJiYjKl6lTp5Z2EaiEsYWojPKp5AXT9RSHqfiSLEGoAhqtbF94EYCtJUgICAm2IAhSVkuRZEvPOg5JsgVNkoQhDV/Dw6N6wL+KD377cgMObzkGSEDrXs3xyOgH0aJrU6eOZyIiInIlBkRl1LwjM/D7Vxvx8+d/IOmKCV5+nuj7Ug80aFcXW77fjr+Wb829bpEAIEuQck7BzwqCbt3w9vL5K5gTtQhQ1Zt7qglg77pD2L3mAKK+GIEHR3RzRVWJiIicjgFRGVUp0B/PTHwMA97oh1N7/0Pt5jVh8DAAANr1bgn/qr74+fM/HFuKkN1tlrtl59bWHlurke3nW7f/0GhlJF1NLtkKERERlSKOISrjdHodGkXWswdD2YzexrwWpc57iFERKYqKE7vPID01484vRkRE5AYYEJVTTTs3gpefJwDH1h8h1MJPqc9vjJAAdvy6FwNCn8e8N5YyMCIiojKPAVE51aJLE3wX8xXGLXoZnn4eNw+oKqBYIVTFPtgaQr15y25CkiRIWi0kvQGSNmfPqmS/pSdn4IePf8XhzcdcVzEiIiInYEBUjukNOnQb1Bl339so90E1O/jJ3VokaTSQZNuq2JIkQdJoAVmT7zR9Nd9NZ4mIiMoGBkQVgKyRIcn5dH9lJYus6fdCUaFmZkK1Wh261iSdDpJeB2hu/slIsgxJq8H3H63G4b+Pw2pVsH31Hrx23yQ8FjgU8ycsQ0L0VWdWjYiIqERIgns03JbJZIKfnx+SkpLg6+tb2sUpsvP/RmPJuz/gn5W7IMHWouPt74UmnRsi5ky8bZf7vP4KZBmSTnvLGCQB6ZZFG2WtDMWiQCMJWDOtkDUyVMU2XV8IgaffehTPvjvAuZUkIrqFM967s685fvx4GI1GTJo0qUSuS85RlL8BTruvAGo2CsPEH15DQvRVbFi8BZVDA3DfE5EweBgghMDvX23Epy99nes8SZZyTcfPazFG1WrbBsRqsdruZ03Tz/5/9x8HGBAREZFbY0BUgVQLq4Kn337UIU2SJNRuHuHUx81IM+da+JGIiMidcAwRwbeyN2SNfHNF6iwl1Zl68dglvHjPG9jwzRZYMq0lc1EiIqISxICIEFIrCItPfYpHox6E3qi7ecBqgWo2QyiK49pFspxrppmk00Hy9ISk08GRbYr+f0cuYvqQOVj0zvdOqwcRkau9++67ePfdd0u7GFQCGBARACCoZjWMmP4Mnv/omRxrEgFQVYjMTEgix0BqSbIFRbIM6HSAXg9Jq4Ws0UA2GgG9AYDkME3ftumsBqk3UkulfkRERAVhQEQOdIZbW3hsVIsVqsVibymyT9O3KoDiuPq1bNBD9vYCtI5D1IQQOH3wPK7H3wAAKIptmv7Hz83BX8u3wpJpcU6liIiIboODqslBw8h6CK0TjJjTcZBkW8sOANtCjqoKYbVCaDSQpJuxtFAUwAJIXp62afpZ6bKHEao5EyIjw5529vBFPF1rFOo0D0fChSu4FnMdskbGukWbMCfKF/3H9sXjY/tyADYREbkUW4jIQXiD6lh44lN8uO5tVKtRJZ9ceQQrWo1t4cbslayzAxpFccgtVAFVUXF8+0lci7kO4Ob0/KQrJsx7YylS2K1GRGUMxxGVfQyIKBdJknBPt2aI7NsKWp0mj+POfXyuFUpERK7GgIjypdFqoChq7gPCuUHR7FcW4syBc857ACIiolswIKJ8PfZaH/Qb2RMGT4O9l8zT1xP3DWiHJh0bOGa2WqGkpkHcEkBJRgOgzz1QWzIYAE3u1idAwpYfd+DFluPx3hOflExFiIiIboN7mRVCWd/L7E6lmtLw17J/oDPq7Vt+AMDRbSfwaufJuVZwlLy9oDEasu7YIimhCoj0dFuaRmMfNK1aLBBp6Q55s/lV9cVP8fOcUykiKvdcsZdZXri/mfvgXmZUorx8PdHnxR650kNrB+W5nLWwWKBKEiSD/uaAasm2eGOu4EmrBbw8ITLMtplsOVgtChSrAo02r5YkIiKiksMuMyo2o7cRnr4eN6fIC2Fb0DEjA6rJBOXaNaipqRBWK2DN2rIjewaaLAOeHpArV4ImsCo0NUIhV64E6HS2RR71eqSnWfBMnSh8//FvSE7kzDMiInIeBkRUbB5eRiw7NxsjPnoGflV9ANzSWqQKW1eZouY6BD8f2/iirGBKkiRIOh1kSYIk3/yzvB5/Awvf+QEzX/rauZUhIqIKjQER3RFvfy889mpvvPHNy/nmyWuRRSnnWkW3IYRAenJGsctIRER0OxxDRCVCIxctthbIc3nHfEWfjseFk7EIrxcCVVWxe80B/PXtP6jbshZ6Pnc/vP29ivT4RETOknORRg6wLjsYEFGJqHV3TTTt3BCHtxyDRitDVWx7nel1MvQ+RqQlZ0CSpJuLLmaYIYwGW1CU3VKk19lut+5pJkm4dj0NL9z7PqqH+SM59gquxSZC1sjY/P12LHx7OXoN7YIRHw+CPp+92IiIiArCgIhKhF8VX/zfpndx7sgFrJq1BueORqPXc/fj/qc7QqfXYseag/j4pfm2ri9FAcxmQJIgvL1sG8HCNuNMCqwKkZkJcSMZsFohGXSATmcPms4fOGs7Hze3/MjMsOCX2WvRc+j9qH13ROn8AoiIqExjQEQlKqJJOMbMezFXeoe+92DZB6vw35GLNxOFANLSIXy8HcYZSXo9JD8fIDOzaA/OFbWIiKiYGBCRy+j0WsgaGaqi2rrOhACsVojzFwGjAbKvL+CZNY0/u/vMYgUsmQAkwKCD5q5wQFGgJt6ASEoGhIAkyxCQ8NnoRXj6zYfRsntTyEUc00RE5AwcT1R28FODXGbsV8PR5YlI20KL2WsTZS/GaDZDTbxxc+HG7Floei3g7QX4egN6PSRZhqTTQapSGZLBAEmjBSQZkiTh5J6zeLvvdLzYcgI3iCUioiJhQEQuU6N+KMZ+NQLfnv4UsnzLHDMBQKd1WIPIJsf0/JzdapJkaz3KIXtM0bmj0fafiYiICoMBEbmcfzVfyBrn/umlcd0iIiIqAo4holIRfFc1RJ+IhSRLEKqte0tYrBBCOE7FB/JftEijsc84cyBJeKbpODwwqBP6Pd8FQTWqOKEGRERFk3M8EcAxRe6GLURUKr7YNx3jv3kZNRuF3Uw0Z0K5cAlqYhKEqtrGEykKkJEBpGXYBmADEBKgehmgNq8DtVYoYNTbzpdlSEYDJG9PmNMt+OXrTZj8zOxSqR8REZUtbCGiUqHTa3H/kx1Qr1VtDK43+uaBrBlkksUCjY93jpYiAWRkQqnuA6HT3EyvVgmqtwc0Z2MBSXKYvq8qKjJSza6rFBERlVlu1UKUnJyMqKgohIeHw8PDA5GRkdizZ0+B5yxbtgzNmjWDp6cngoODMWTIEFy7ds1+fNGiRbaNQ2+5ZWRwjElZIoSAUBRIsdcgx10HzLbVrAUAodfCWisYahU/iOzB2hoN4OeNREXCgV1nIYSwbfnxxwH8r/8MzBu/DPHnE0qvQkRE5FbcqoVo2LBhOHr0KJYsWYKQkBAsXboUXbt2xbFjxxAaGpor/9atWzFo0CDMnDkTffr0QUxMDF544QUMGzYMq1atsufz9fXFyZMnHc41Go1Orw/dXrUaVdDl6Y7Y9N02AIBQBQQEDBrAp6oPEq+mQBIqRIYZUAXkrDhWumaCEl4NopI3oNEBBh0UH08ogZWgu5EGSZIBIZAJGRNGLEKAhwwlNgGJcYn2Ad0/zfgN7frcg1e/HAG/Kr6l9SsgogqKY4rci9sEROnp6VixYgV++eUXdOrUCQAwefJk/Pzzz5g7dy7ee++9XOfs3LkTNWvWxCuvvAIAiIiIwPPPP4/p06c75JMkCUFBQYUui9lshtl8s6vFZDIVp0pUCDq9FuO/eRnDPnwKv325EUf+Pob7nmiPLs90hNHTgGN7z+HDYV/hSnTuFj01wCdXmqyotmAIsG0NkrUe0dUTF4HUdNt5Oabkb1+9F10HdkKHh1o7oXZEVNbx86DicJsuM6vVCkVRcrXceHh4YOvWrXmeExkZiUuXLmHNmjUQQuDy5cv46aef8OCDDzrkS0lJQXh4OKpXr47evXvjwIEDBZZl6tSp8PPzs9/CwsIKzE93rkpIAAa/2x//t2kyej/fDR5eRkiShEat7kK95jUdJp2VOK7hSET54OdBxeE2AZGPjw/atWuHKVOmIDY2FoqiYOnSpdi1axfi4uLyPCcyMhLLli3DgAEDoNfrERQUBH9/f3z22Wf2PPXr18eiRYuwevVqLF++HEajEe3bt8fp06fzLcuECROQlJRkv0VHR5d4fanwdAatw2Bpu+ztP3LKL3IqIKL66u3vsX7ZNmRmWO6glERUHvHzoOJwm4AIAJYsWQIhBEJDQ2EwGDBr1iw89dRT0Gg0eeY/duwYXnnlFbzzzjvYt28f1q5di3PnzuGFF16w52nbti2eeeYZNGvWDB07dsQPP/yAunXrOgRNtzIYDPD19XW4UekZMaU/HnulJzx9PexpkqpCc/Q8pOumm0GREBAaCapB6xAACRmw1q0OS82qEDkXhJRlSEYjEuJNmDFqEQY2GYeUpDRXVYuIygB+HlQcknDDTZ9SU1NhMpkQHByMAQMGICUlBb///nuufAMHDkRGRgZ+/PFHe9rWrVvRsWNHxMbGIjg4OM/rDx8+HJcuXcIff/xRqPKYTCb4+fkhKSmJL4ZSZE7PxMD6r+JGQhKQYxyQ8POGqB8OSVEhZf81CwGrQYaqk6F4aoHs2WdWBR5bT0FSBaDR5Gp5WrD3fYTcVc1FNSIiZ3LGe3f2NcePH++yyTkcbF18RfkbcKsWomxeXl4IDg5GYmIi1q1bh379+uWZLy0tLdeu5tmtSfnFeUIIHDx4MN9gidyXwUMPo1HrEAwBADIyIcVeATIyHZIlIWyBTw5Cp4G5dhVYgnwcu9E0GsDTiMRrKc4qPhERuTG3mWUGAOvWrYMQAvXq1cOZM2fw+uuvo169ehgyZAgAW19uTEwMvvnmGwBAnz59MHz4cMydOxc9evRAXFwcoqKi0Lp1a4SEhACwTWts27Yt6tSpA5PJhFmzZuHgwYOYPZsrGJdFdzUJR/y5K9BoZVgtVkBVgVQLcC4VACAq+UIKqgqNpIEme0sQWYK5shFp1T1g8ZKBQFswrE3KgNfx6zCkCsCgByQJrw9bgA7dG+PxwR1Rp1HupR6IiKh8cquAKCkpCRMmTMClS5cQEBCARx99FO+//z50Oh0AIC4uDhcvXrTnHzx4MJKTk/H555/jtddeg7+/P+6//35MmzbNnufGjRsYMWIE4uPj4efnh+bNm+Pvv/9G69acZl0WTfrxVRzacgwrPl2Dnb/sznVcUlRohOww2FpSBdKr6WDxlh1ahRRPPQyKHsjR6q2qAls3/Is9f5/Eql1spiYiqijccgyRu+EYIvdjtVjRy/BkrnTJ1weakNxrTiU28UNmJb1jXouKqruv53l9rU6D3/b/r2QKS0SloryMISoIxxcVrMyPISJyiiLE/lZFxdq/j8FiVaCqKvZuOIxJ/Wdi+rAvcHLff04sJBERlQa36jIjKiytToun33oUKz79HRmpGZCyVqU2QEWVQB/EX06GLEtQs8YReV5MgaL3geKlywqMJAgNkBZshEd8BiAACVlrNMoSMqsY8b/P/8An03+B74k4JMXdgKyRIUnAn8u3o26LCIyb/wLC6nJwPhFRecCAiMqswVOewIA3+mHDN39j5+/70K73Peg6sBM8vD0Qc/4qpr7yDc4evQSRmg59nAUBJ+KQGeSNpMgwSBIgKRLSQ72QEegBwxUz9DcssPrqYfHV26fpm89cRlL8DQCOW36cOnAOe9YfYkBERFROMCCiMs3D2wN9X+qBvi/1cEgPrVkFzZpVx/mdJ2G1KABsLUCG+BTobpiheumBrEUahVaGuaoBio8h14rWQiNB1Wshm60O6bIsOwRIAKCqKm4kmBAQ5F+ylSQiysetG8Rm49iiomNAROWW3qiH1aoAEiBUASgK1MxM+Px8CEKvRWb9QJjrVoMsa6HNEJBVQEgCqkaCVQ9k+stIerA6hBQGz5PX4XPwCvSJFkgeRgitFt/O3QSLpMW9/Vpg9297sfLTNYg7l4AGbevg0agH0b5fK2i0ea+yTkRE7oUBEZVbA6J6wa+KN1bN2YDYkzGAxbZXmQRAyrRC/28spMAqEHrVvmK1JADFCCTXkG05s9LT6gfA47oKo5Jqv356aia++eg3LBy7EICABFvek3vO4r0nPkG9VrXx2fb3XFllIiIqJs4yo3LL6GXAQ893xcIDU1G7cfVcxyVZBgy6XNt3KNmz83MmyxL0Sbk3fxVWNWuT2Zuro2d3pcWcyXtTYiIicj9sIaJyT5Zl+Fb2vuPrCOn2eXKymC0wXU+Bb8CdPzYRUVFwbFHRsYWIKoR6LWsBADTarD95CYCiQk4zAwDk7M1fBaAx2/6/dd0icxVb05HI8aoRsgTIOa6ZM3+aBU9FjMInI+cj9r/LJVgbIiIqaQyIqEJ47v0n8fWR/0OvoV2gN+oQclcgXpk1BL+tGIP333wItapXBoSAJsUM32OJCF19Gf5HkgFVhZAELL4qYh+uhP+GByKpiSeEBKgGLazVKyGzYxNYGteE8DDYHkySbJvFajSwZFqxbvHf+Hz04tL9BRARUYHYZUYVRnjD6hg9ZxhGfjoYGq3GPnaoY5s68IOMcc986dDI43cyFak1ZJgae0BkTRYzB+oR16cydIofZBX2QdcisBIUjQbaoxdyjUlSFRWWTMdp+0RE5F4YEFGFo9Xl/WefM4wRQgAWCyptiYfvDuBGZACSG/kCsgQ5Q8K1Jjro0gS8YlXo0gSsBiC9kRcy2zSA17/X4X3oGjQZSlZrkYxzx2OwY80BtO7RDBoNG2aJqHTkN7YoLxVtvBEDIiIAdZuEoWf/1ti4ah+sGZnAjSTAqsB4wzacyOtsGm60qYLEDsEQMmD1kWD1BtIDNZAsAkILADoAQGZgKNIaVELgyvPQZNhmnKUmZ+DdJz9D1eoBeP3L4WjaoV5pVZWIiPLAr6pEAIyeeox+/zEs3fo2WrevBVhtq1tD2NYmAoDUuv6ALNnXG8r+T+iy1ivKcTNeTIVsvrmStcjaU+1abCJ2/L7fRbUiIqLCuqOAyGKxIDo6GidPnsT169dLqkxEpcYvwAst2teFJOeeY1/EWfd5niBpZFiythIhIiL3UeSAKCUlBV9++SXuvfde+Pn5oWbNmmjYsCGqVq2K8PBwDB8+HHv27HFGWYlcwsPbAKGKm1Pxs0hmBVAdp+JD3PJ/9l29DElFrqDIalHw2++HMOuTdYi+eK1Ey01ERMUnCXHLYisFmDlzJt5//33UrFkTffv2RevWrREaGgoPDw9cv34dR48exT///INVq1ahbdu2+Oyzz1CnTh1nlt8lTCYT/Pz8kJSUBF9f39IuDjmZoqjYtnofVs1eh+O7z9oShYBVDyTfXQWmVtWgeukAIaBLBbQpgNUDsPjC1mUmC0h+Znj/dx2+G03QX7CtcC2MOihV/SECfCBrNVBVgWkfPYGWre4qvcoSlWPOeO/Ovub48eNhNBpL5JplTVkabF2Uv4EiDarevn07Nm3ahCZNmuR5vHXr1njuuefwxRdfYP78+diyZUu5CIioYtFoZHR6uBU6PdwK/3viE+z4bR9UqwqNBfDfFge/HfG49vTdkK0yNNm7eSQBZtmCjMYWwNsKSQbSqnshrZMXApZYYbwoQXga7dP01ayWpitXkkuplkRElFORAqIff/yxUPkMBgNeeumlYhWIyJ34Bnjn6g6TVAFdCoCcG9kLAcNVK4x7zUhtLMNaLWuzWAjIrQRwF4DDAjDb0hW9hIzKGpy4dg09srrnhBDY/+dRnNp7Fp0fb4eQWoEuqSMREXHaPVGBGrSpg/Xf/A1ZlqCqApIkQUBAdy0Vlmo+0EiA5moGjJfToclQIAD4b1SQ0RiwPGqFR2AmpLZZF+ueAvMuH6Rc8oXZ2xZNzT98COuiz6Fdihbnfj6E2DPxkCQJCyf9gNY978bTbz6MBm3YykpE5GzFDoimTp2KwMBAPPfccw7pCxYswJUrV/DGG2/cceGISluPZzujdc+78fvXf2L13PXw9PXAI6/0QtenO+K/yzewZMVOHF58cxJB9hhqQ4gF+kCL46BqPZCkeMPqo0HOA5fPXMa2pUdvTuPPGta3d90hXDoVh0XHZzq3kkRERVDQ4o5laXzRrYo97f7LL79E/fr1c6U3atQIX3zxxR0VisidVAr0wzNvPYIfLn2BRcdmou8L3eHp44HGtYPx6tOd8z5JBqDmkS4k3Dr1LHuNolu75lRVwMotP4iIXKLYAVF8fDyCg4NzpVetWhVxcXF3VCiisuLWfcvsBApYuOjWQUn5X//61RSs+X4XMtIzi1E6IiIqrGIHRGFhYdi2bVuu9G3btiEkJOSOCkVUVlQN9sMzL3eBt6+H44EdAHZaAVVAqIAQtpshLDV7hw/YAiMBaxUD0rtWhvCUHEMlnQ6qrw8+e2cVnm7/Pv765YArqkREVCEVewzRsGHDEBUVBYvFgvvvvx8A8Oeff2LcuHF47bXXSqyARO5MlmU8/VIXPD60ExZ8+Ct+nr8FIjUNuGAGjgCoLCF1TCAsgQaYEnxgNesAP0CyCPj6pEHja4EhJB1STyPMI4OgXZAO3SYLJG8vwGiwt0ClpZqxbcNR3N+veelWmIioAGV5fFGxA6Jx48bh+vXreOmll5CZaWvONxqNeOONNzBhwoQSKyBRWaA36NCsZU2smv6z44FrAulbtUit4wmlUlbTkAQIHeARmgrJW4GkzWoXMsqQWnvCM0lBRrQeOfvSVJ2MDJVbfhAROUuRA6I333wTDz30EFq3bo1p06Zh4sSJOH78ODw8PFCnTh0YDAZnlJPI7Xn7eQIANFoZVosCCAFhVRCw6iwCAKTX80dS+2CIyr7wuApYjlYFtCo09dLg0SgJwVVvIKibCdpeAhmX9EhY7YcrBwNgruQFi58efyVew/gZv+CJB+5Bs3qh+Y9fIiKiIivyGKK4uDj07t0bwcHBGDFiBP7++280bdoUjRs3ZjBEFVqTDvXx8fq30O7BFoCqQmRaAFVF9rwyz9NJ8L0g4HVJQGPOOskqQxutxT3VLyLUIwnarNYiY2gmpJYapEQEwOJnsK9wvXXfWbz47vdYtfFQqdSRiKi8KnIL0cKFCyGEwNatW/Hrr79izJgxiImJQbdu3dC3b1/07t0bVapUcUZZidxek/b10KR9PcwbvxQ/zfgdqpJj7r0qIDwNuVp2tAYFsib3zDNLqh6SLCDUm/kVVUCjkXHtRqozq0FEVOIKGl+UH1eOOyrWLDNJktCxY0dMnz4dJ06cwO7du9G2bVvMmzcPoaGh6NSpEz7++GPExMSUdHmJygQvPy/btLJbFXor5fzzq6qKU8nxMCtZm8YKgYObj2H+Oz/g2K4zKMJ+zURElKVEtu5o0KABGjRogHHjxuHKlStYvXo1Vq9eDQAYO3ZsSTwEUZnSoE0dGL2NSDOl27f7kCBBn2BChq/RvhUIhIDZZEDqVSO8qmRAKICUtUeab2gyJK0KYcn+3mK7jtAAW8RhPLjpGFofrobLS//DpVPxkGQJP8z4HbWaheOpcX3RoV/LUqs/EVFZI4kifp08ePAg7r77bicVxz2ZTCb4+fkhKSkJvr6+pV0cKiMy0sz4a9lWrPh0DSxmCx5+pRe6P9sZNzIy8dPGQ1i+eg+0qZkwxKZCl2SGT7MMBA28Ds+ITChChlXIsJo1uPpvZUTvDoECCaJhBsRdZttaRukqKg2Msw1QyvEqliQJGq2M3xMXlFbVidyCM967s685fvx4GI3GErkm5e9Ou8yK8jdQ5BaiFi1aoHnz5hg2bBieeuop+Pn5FbugROWZ0dOAB4Z3wQPDuzike/l64pUnO2HtO7/lSJWQcsgDl30qIXj0dXuqxqAisMUVXI+QcSPT0/EBsocn3fKVRghha30iIiqjSmPNoiKPIdq2bRtatGiB8ePHIzg4GM888ww2bdrkjLIRlWtSjm3NhBAQ5kykbkvDmYc0uPKFDMsVQFEkRMdUwaWjwUg65Q/zdQOECvhrU9G3+iG89udOPPbRcVRvYrJdyKCHVCUACKyGZbP/ROLV5FKrHxFRWVLkgKhdu3aYN28e4uPjMXfuXFy6dAldu3ZFrVq18P777+PSpUvOKCdRuSJJEl79sD+CwypDqCrElWsQV6/DGmOFNUHCtaUyjoythA3rm+Pwv3chM10HJV2H1Ghf9Nb/i3drr0aXwJPwC8pE4+5X8dQXx1G5lQ/kwKqQPI2AVoNv5/6FgfdPw7I5f5Z2dYmI3F6x9zLz8PDAs88+i82bN+PUqVN48skn8eWXXyIiIgIPPPBASZaRqFzq9mgrfL1xHF6e1A+wZO1qn93TpQLp4T5QskdY51i1um31M5AlQJZsmWWtwLVLPki87OuQV1UFFKuKf9YedX5liIjKuGIHRDnVqlUL48ePx1tvvQVfX1+sW7euJC5LVO7JsoyGLWrme1wqgaFAmYrV4b4QAqcPnEMSu9OIiOzueNr9li1bsGDBAqxYsQIajQb9+/fH0KFDS6JsRBWCl68nZFkCJOnmQo4SoElXIDQSNJIEJXsyqABMZg/4GtNsK2BnNRx5+GQCkoAsC6jKze85AsDZNBMeX7wcA5s2gbw/AT9/vg7Rp+Kg1Wlw/5Pt8dCL3VGraQ2X1pmIqCDZizi6/cKM0dHRmDJlCmrVqoX77rsPZ8+exWeffYbY2FjMmzcPbdu2LelyEpVbgeFVMO/gNPQe3gUGDz0AoFpYFYzr1xWf9eqJNjXC7Hn1KQKfzH8Ev6xpjxsmb3t6tfAkvDRnDVp0PwtZawuqLD4aXGntjbj7/HA47jKmP/cFPntlES6djgcAWC0K/vx2K15q9zZO7vvPhTUmInI/RW4h6tatGzZt2oSqVati0KBBeO6551CvXj1nlI2owqheJxgjZw7C4MmPIfpkLOq2vAuybPu+0qtpAwyf9j0OnrgETSagwIDN25pj1776mPbO1wBsLUVBEUnoO3oPtJ0s+PHvdjBX1tmbkIQQ0KZa7T9nU6y24Cn5eoorq0tE5HaKHBB5eHhgxYoV6N27NzQaze1PIKJC8/LzRP3WtXOle0s6aDJzJAgBmATWfHkPwupfRaMOF6HRClhUGZeMfhBNLJATZagZtpe4j0caGj91BUiw4sLP3shMynrtGgyAlwf+PXQJze9vDI2mRIYVEhGVOUVeqTqnf/75B19++SXOnj2Ln376CaGhoViyZAkiIiLQoUOHIl8vOTkZEydOxKpVq5CQkIDmzZvj008/RatWrfI9Z9myZZg+fTpOnz4NPz8/9OzZEx9//DEqV65sz7NixQpMnDgRZ8+etS8P8PDDDxe6XFypmkrbTxsOYtayzbBYFWhuZMIjPh2adMW++atnQAa8ht7A2cDKSFMNtsFDEuBjyUTrSudROzAeELb5Z6oVODw7FBc2h0Jo9MjOXLmaLx5/riP6PdMu1wa0RGURV6queG4dc1SUv4Fifx1csWIFevToAQ8PDxw4cABmsxmALaj54IMPinXNYcOGYcOGDViyZAmOHDmC7t27o2vXrvluErt161YMGjQIQ4cOxb///osff/wRe/bswbBhw+x5duzYgQEDBmDgwIE4dOgQBg4ciP79+2PXrl3FKiNRaXis2934bfYLePnJTvCJtQVDACBUW+Byw8eAI1VDbMEQYJ+l3yz4ImoHxtum6cuAJAMaPXBpbxiERoecma8lmPDFh78j8Sq7z4io4il2QPTee+/hiy++wLx586DT6ezpkZGR2L9/f5Gvl56ejhUrVmD69Ono1KkTateujcmTJyMiIgJz587N85ydO3eiZs2aeOWVV+ytUs8//zz27t1rz/PJJ5+gW7dumDBhAurXr48JEyagS5cu+OSTT4pcRqLS5OtlxNMPtoK/j0fug/m06EhSVlPRLYSQ8ky3HeO2H0RU8RQ7IDp58iQ6deqUK93X1xc3btwo8vWsVisURcnVBOnh4YGtW7fmeU5kZCQuXbqENWvWQAiBy5cv46effsKDDz5oz7Njxw50797d4bwePXpg+/bt+ZbFbDbDZDI53IjchUYjQ5JvCWby3ddMyp0IQJJFvoscLZj7Fy6cu4KMNDPWfP0nhjV5DQNrj8KKT35HalLanVeAqAzh50HFUex1iIKDg3HmzBnUrFnTIX3r1q246667inw9Hx8ftGvXDlOmTEGDBg0QGBiI5cuXY9euXahTp06e50RGRmLZsmUYMGAAMjIyYLVa0bdvX3z22Wf2PPHx8QgMDHQ4LzAwEPHx8fmWZerUqfY1EIjczZj3H8W3c//C0X0X7Gm681Z4/2RBWlcjVH+NfQzRwfPh0PjKqBceA1kWkCQBGQLdX9uPwz/chUsnq8CeWZag6jX4c/1RrP9hJzTnLkExWyBJEgQEvhy7BAveWo5xi0ai8+PtSqv6RC7Fz4PS56q1iIrdQvT8889j9OjR2LVrFyRJQmxsLJYtW4axY8fipZdeKtY1lyxZAiEEQkNDYTAYMGvWLDz11FP5zmY7duwYXnnlFbzzzjvYt28f1q5di3PnzuGFF15wyHfrAFEhRIGDRidMmICkpCT7LTo6ulj1IXKGFu1q4+NvRuD/Fg+DSDJBJFwFouPh9eM1VHkxBsYfM6CeN0LZ6YekPYH468+WWPhtT3hnZCJCdxWtPM7jvvbHMfrT3zHkf39C6LVQPXVQvfSAXgtVFUBKGhSzBUBWF5qw/W+1KDjyz/FS/g0QuQ4/DyqOYrcQjRs3DklJSbjvvvuQkZGBTp06wWAwYOzYsXj55ZeLdc1atWphy5YtSE1NhclkQnBwMAYMGICIiIg880+dOhXt27fH66+/DgBo2rQpvLy80LFjR7z33nsIDg5GUFBQrtaghISEXK1GORkMBhgMhmLVgchVatYJBG44Nt9LKqA9pkBU9nAYV5SRrkfKf94ICkmBJvRmV1nIXddxd+urOHWmMlLTbn7xsPhoYL3LG8ZzKQ49a5JWi9TkDOdVisjN8POg4rijrTvef/99vPXWWzh27BhUVUXDhg3h7e19+xNvw8vLC15eXkhMTMS6deswffr0PPOlpaVBq3WsQnZrUvbA0Hbt2mHDhg149dVX7XnWr1+PyMjIOy4nUWnSGXXw8DYiI80Mod6MWjTpVkCSoJElKFYVxusKvC5b8dPe+wAAtRvEoEfffWjX9DLqBQq0f3cjzGYN1v9VC4v/aYJTVfyREhYKyNWhu2aG/59x8D+QBI3WAEmjwebfDiO5/yw89HwX3HN/Q07RJ6Jy4Y73MvP09ETLli1LoixYt24dhBCoV68ezpw5g9dffx316tXDkCFDANiaLmNiYvDNN98AAPr06YPhw4dj7ty56NGjB+Li4hAVFYXWrVsjJCQEADB69Gh06tQJ06ZNQ79+/fDLL79g48aN+Q7UJior9AYdFh/9GGsWbsbPc9fjRoIJVcMq45EXeiC8S32sPHAcm1YfgWesxeG8/04GIzI8A5U1OmSPzTYYFATdY8L+tCqQVNg70y0BBshentAYLA5z0g78fQL7Nh3DhHnD0Omhknn9ExHlpShjuO5kvFGRA6L09HT8+eef6N27NwBbkJK9BhFga6GZMmVKsRasSkpKwoQJE3Dp0iUEBATg0Ucfxfvvv2+f1h8XF4eLFy/a8w8ePBjJycn4/PPP8dprr8Hf3x/3338/pk2bZs8TGRmJ7777Dm+//TYmTpyIWrVq4fvvv0ebNm2KXD4id+NXxQdPvt4Hj0f1woUTsajZsLp9tel76odj+kUr/rx8FEr2prEAVFWGt1cmbp2olqLYXmci58hCCdBkqLbetxxdZ9mb0KaY0p1RLSIilytyQPTNN9/gt99+swdEn3/+ORo1agQPD9vaKCdOnEBISIhDF1Vh9e/fH/3798/3+KJFi3KljRo1CqNGjSrwuo899hgee+yxIpeHqKzQ6rSo1ST3jvUGnbZE1hXKezUj4PCec7jvsdbw8OQYCyIq24o8y2zZsmV47rnnHNK+/fZbbNq0CZs2bcJHH32EH374ocQKSETF16Zdbfj42Fprcw71WffnXVAUQLEteA2rKqGh/zWEeSXlOFsAEEhr6AHZw/azQ1Sk0WDzn8fxZJfpmDdjLTIzrU6uDRGR8xS5hejUqVOoW7eu/b7RaLTvyg0ArVu3xsiRI0umdER0R9q2q43vVryCvzcfxycfrEZGSgbk68n46s0wrJhZDd2fjkX7/lex9VooFl9oiGh4AR5WVNKkQyMEmoVeRON7o6EdoSJmow+OfBoKRdEAXp6AQQdIEjLSMrFi8TZ06NoIDZqGlXaViagCcMbaREUOiJKSkhxmdl25csXhuKqqDmOKiKh06XQadOnWGL/NXIMTx26+Xq9fMWDJwtqYFHLfzcwSAA1QJyQOncJOQ86ec68FajxowpmNtZES45nn43DLDyIqy4ocEFWvXh1Hjx5FvXr18jx++PBhVK9e/Y4LRkQlS6vTQJYl28KLAITVCikhHbVfPIz0el5I7FoVafV9YLwiI3Z/OJan3oU6d0ejfstzqBZgQm1dEh78ZBWSbxjxx6pG+OfPWkjReyA92ABzgB4z1m7HCAPQtEYgNv+wAys+WYOrMdfx4LAu6PNCVwSGVy3l3wARUf6KPIbogQcewDvvvIOMjNyLs6Wnp+Pdd9912EuMiNzDS1OfQIc+LSBrJCgpqVCTUyGlZUK2CngdT0Hw/FgEbpDhd0wLS6IR1kwdTuypicz9ldHH8zwa6K/D2zsTQaEmDHl5B/z6WnGjiS/MlfWALOHQxXiMnLQE/YJGYMaIr3DxRAxSk9Lw0ye/Y1DdKHz/0erS/hUQEeWryC1Eb775Jn744QfUq1cPL7/8MurWrQtJknDixAl8/vnnsFqtePPNN51RViK6AxENQzHhq2G4GncDg+q8Aoch0Cqg+hgBveNbghAyGoTHAoB9mn724OxjMSEOCYoqoL+RDpGRteVHVktU9hT9f7efKvlKERGVkCIHRIGBgdi+fTtefPFFjB8/3j5uQJIkdOvWDXPmzClwWwwiKl1Vgv2h1WlgdfGssOQbqbfdR5CIyFWbud6qWCtVR0REYO3atbh+/TrOnDkDAKhduzYCAgJKtHBE5BzVwqrg4okYSLJkb8mRMyyAEJA1sn2cEQRw5YYvZFlAUSRoNLZ0RUio7JOCq8neyDkXX/HU2dZvlG37quX07+6zeLHDu3jkpW647/E20OnveKF8IqISU+zd7gEgICAArVu3RuvWrRkMEZUhs3e9j9fmjUCN+qEAgCqhAXhx/MNYNvlpPNOrJfRZewJqkzOx4tOGeD+qCw7uCIEQgEXI2JlaHU0eO4bqkTHQ+9hmlaoGFcmRHrjwfl3c6FoZQpsVKGk0kIwGSHo9LpyMxYxRi7Bi9vpSqTcRUX6K9BXt4sWLqFEj92q4+YmJiUFoaGiRC0VEzmXw0KPHs/ei+6DOiPsvAYE1q9q3/KhdKwj+1y1Y+sWfQLqtW+34wSAcPxiEiJmxEIECGUIHSEDl+tdhqJmC/ScjoBqzF2404OqTIYCHHpX+SoIsbrYgCVVAq9MgI5VLcxCReylSC1GrVq0wfPhw7N69O988SUlJmDdvHho3boyVK1fecQGJyHkkSUJIrUB7MJRNp5EhZSgOaSLTgpi5GiR8pYH1ctaAaVVC4hVfIEULKVUDZHWTeenMaNr1MppOuAr/JhkAhG3daw89LJX9cPjkZVy7kuyCGhJRWVJa44eAIrYQHT9+HB988AF69uwJnU6Hli1bIiQkBEajEYmJiTh27Bj+/fdftGzZEh999BF69erlrHITkRPd06EuNqzYi0vnrkCyWqEkJgEWCzK2AJCAlOUCqQOq4FrdQFgVLWQIIF0DQ6qKTi2Ook5gfPbOHwjrnYKLG6vg2PJaUFQjIAT+PXsVTz/0KTrd3xAjX+sJP/+8F3skInKVIrUQBQQE4OOPP0ZsbCzmzp2LunXr4urVqzh9+jQA4Omnn8a+ffuwbds2BkNEZVithiH4au1rmLp4OAK8NIDFNpUeArZWIAFcjgiBVcn+TmXrFgurcg31guIhS4AsA7JtKBISjgVCUbM2gJUkCCGgqgKbN/6LA3vPubJqRER5KtY0D6PRiEceeQSPPPJISZeHiNyEJEm4u11tNGl9FzZfTICq3LI1R1Fmz4v8T7h1yw8hBBSrAq2Os9CIyHX4jkNEBdLoNFkBzS1UAVkjIefselW1BT1C3FzAEQAkjQAkAYjcQdGstduQVlmLe+uGY+tPu7Dik99x8UQM7hsQiYdfeQB177mrZCtERC5XmmODCosBEREVaMikx+Dj74U1CzYjI802O6xySCXc7xmOE0EStl64aI+A4v+tjAPxdVCvfTQ8q2ZACECFhMoPJyDJw4D0nT4QVgkSAMUgISVEh8siDW9++QuqLjsCKd1iXxtp03fbsHHpP+g++F68Pv/F0v0lEFG5x4CIiApUObgSnv/wKQx862H8vXI3fAO80eaB5vaZadsOncWY6T/AI0GBPllFLIIR+0sQQobEwKd5Cq5YvaF4aKB/OAW6nqmIWRIGi6SFuZLG3oykSzZDSnfc8kOx2tqeju/klh9E5HwMiIioUDx9PNDz2c650qv7+sLvrMUxUUi4etAbSSYZmlaApMtKNgqY6mogUmVoMm52n6l6CaZWAfA8boI2JceWInodMqyA1aJAq9M4o1pERABKMCA6cuQImjRpUlKXI6IywtfPEx6eemSkZ9r2KkvOgJSUAuWsBQoAyReQ+kq43s4XCWl+sAYCgAJNugptkgRVD1hrG4G2tQBFhc+ua6i6ORF6qx6SXodEMzAo8l30G9wJDz7THt5+HqVcYyIqrLIwdijbHW3dkVPz5s3x+uuvO6StW7eupC5PRG6qUoAXvl0dhedHd4e/ToackAjJfLPFSJiAi0oVxJoqwarcbOVRDALmygJWb9wcga2RIXl7wiB7QdLr7HkTryRj8cdrMH/qaldVi4gqmBILiJo0aQKj0YihQ4fa0yZMmFBSlyciN+blbcQjT7TFi1E98jyu6iTHaWcAIAGS7R8Hspr3pDZJAjLSM0umwEREtyixgEiSJEyZMgWNGjXC448/DovFkmt9ESIq32S5gLeUIrwf5LVikaoKnDgei//OJhS9YEREt1FiY4i8vb0BAGPGjMGiRYvQp08fpKenl9TliagMaNS2Fpq2r4vD207Z1ijKWszRf48JGSF6WP11gHpzkSLJKiDkrFYiCQAEUmvqkVZdB89LFgjJtnwRAAitjFhTBkY89zWaNA3DmNcfQFiNyqVST6KKqCyNByqOOw6IYmJiAAB///23PW3w4MHw8/Nz6D4jovKvSnAlTPt5DC6ciMX057/G2cMXoZoz4bXThLt2XUZKUz9cebwmZEWG8bIMbYYEVSOQHqzCXE0AWhWWEAkXng+AIdaC4NWpMFwXUL2NEB56eyB19Eg0tv5zEk8+HVnKNSai8qLYXWbbtm1DREQEatSogRo1aiAwMBBvvPEGTCYTAODhhx/G9evXS6ygRFR2hNcPQcvO9SFnmgGrbRq9JACfQ0nwPwJ4X9BAmzXtXlYkGG5IkGULoBP2/jJziA7JzXyRGeoH4WlwGIMkaWSoeWz5kZbMVmkiKp5itxA9//zzaNSoEVasWAGDwYB9+/Zh1qxZWLlyJXbs2IEqVaqUZDmJqIzRGbRQrEruA1YVErIGTqsChsvpqBRtQugKC9Jq6HA90hOmurbWoGt3a3CtOeBzRsD/hApNugSznxZWbxnztx8AqhrRq3Vd7F9zECtn/YGzhy7g7nsb4ZHRvdCqR7OCxzQREeUgiWKOfPbw8MDhw4dRp04de5oQAv3794dOp8O3335bYoUsbSaTCX5+fkhKSoKvr29pF4eoTEhPycCvX2zAyll/4Hr8DQBAQJA/Or3QBXGBHli39xT8N8dAk6HAHiFJQGoNPS48XQnQ5BharQLe52R4xsn2fAAgKSr8Vh+FnGPLD1kjQ1VU1G9VC5/+8z8X15rciTPeu7OvOX78eBiNxhK5ZllRFscQFeVvoNgtRA0aNEB8fLxDQCRJEv73v/+hdevWxb0sEZUTHt5G9B/bB49GPYBdaw5ACIG2D7aARmtbi+ilPu3xXJt3bZmzv5YJIDNA4xgMAYAMaFOzfs55KFOBfMuWH6pi2/Lj/L+XnFArooqhLAY/d6rY7cmDBw/GiBEjcPHiRYf0pKQk+Pn53XHBiKh80Gg1iOzbEu37tbIHQwBQydu5364VRUEqxxQRUSEVu4UoKioKAFC3bl088sgjuPvuu6EoCpYuXYqPPvqopMpHROWUTq9FteqVkHApEbIsQc1q4dFfVwAla3C1nNUcJACrF6A3OV5D1WmgGrWQM6w3u92yWKwCTzd/Gz2faoeHR9yPwOoBLqkXEZVNxQ6I4uPjceDAARw6dAgHDx7EokWLcPr0aUiShA8//BC///47mjZtiqZNm6Jnz54lWWYiKgc0Wg3m//Umtq07jOWz1uPC6cuAxQKPQybUPnEZN9oH4HqXqlD1MowJEnQmyT58SABQdUBGiIzEN5vA+99EBPwZB/1VM6DTQjIaAZ0O5vRM/Lrob/x3LAbTfxpdyjUmIndW7ICoWrVq6NGjB3r0uLlUf0ZGBo4cOYKDBw/i0KFDWL16NT744APcuHGjJMpKROWMVqdB597NUa2aD6Lun2JfzVqbCVRZewXGK3pk1KkMKbt3X7IFQ4n1AaFD1ngiGcktKkPxN6L6D5chyY7jj1RFIDPDAiK6qSKOEbqdElupGgCMRiNatWqFVq1aleRliagiuHVdIVWF4cwV6C5eh7leNVhC/QEA+kQLQv7KRKa/jBsNDLD4aQAVUDz1iH04BN5nU+FzMhmyRUDotVC9PRBtMmPvrrO4p/VdkG7dU42ICCUcEBERFUdEozB0ebI9Nv2wA0JRoVosgBDQXkkBJEAfkwRRrRKkwKr2zV894wH/45mI6+iF9EAdFC8dFE8dMoKNSGrsj8BdadBaJUAIpFhUvDl6GULDAvDqm33QtHl4aVeZiNwMVy0jolJn9DJg3NfPY+nJT9C6RxPH1qKsH2VPL8hZA68l2Fa+VowSMgL1kLI3Q5NsN4/rKjTW7MWKJPtG03Exidi8/qjrKkZEZQZbiIjIbVQO9kfbB1pg56/7ch2zdXUVobvrllln2dewZq1TlE0IgevxNxAQ5M/uNCpXOE6oaBgQEZFb0XvoAcC+8nQ2oaq5wiEpe2cQVdycog9AyLYWpFtZFRVrN/8LQ4gP+nZpglP/HMeKT9bgzMHzCKsXgkejHsD9T7aH0dNQwrUiInfHgIiI3Mp9T7SHUAVWfPI7/jt8AQBQKcgfPQa2g+LlhXW/HEBKcgYAQJOcgZCfonGjiS/S6vrauswEkF5ZB1M44HXZAk2GrUVIyBKsXhpYjTJ+WL0Xq15dDDnVbJ+VdulUHD558WssmvQDlv33OXR6vj0SVSR8xRORW9FoZHQb2Aldn+mIYztOwXQ9Ba16NINWZ3u7GvRyV/Rv+DrMpjQg0wIjgKD/EpFayw837g2HNgOQhITMSnpk+uvgcdkCrVlA1cu2gAmAqgpIaWYAN7f8yB5ndCPBBHOamQERUQXDVzwRuSVJktAosl6udINRB21mJsyZjmsLaa9nwGdXLDJrBkD1zbEtiMg9LkjIQHr9atAmpkMXn3yzK06jgaTV4GrcDXj7e5VgbYicg+OESg4DIiIqc+reE4EDm45Bo5VhzciEasmENiUFmuhr8Nx5HpaaAVDqVoc+UwNZyRpbnQ5kemmQEaCF4iEBVWoAkgRNYhq898XBcCMTUvbGs/e/j/YP3I3+r/RAnWacok9UEXDaPRGVOR/8PBbT17yBFvc1gpqRAWTNHMueh6bL1MCYJkPOGnSdPU0/o3JWMJQ1PR8AhE4LY4piD4YAWzfa9j8OYcqQL11bMSIqNW4VECUnJyMqKgrh4eHw8PBAZGQk9uzZk2/+wYMHQ5KkXLdGjRrZ8yxatCjPPBkZGa6oEhE5gSRJaNqhPqI+H5JfhrzT5dzH8pqNBgCqosKSaS1+IYmoTHGrgGjYsGHYsGEDlixZgiNHjqB79+7o2rUrYmJi8sz/6aefIi4uzn6Ljo5GQEAAHn/8cYd8vr6+Dvni4uJgNBrzvCYRlR35LxuUT5QjkGuLkIKWNkpOSseGX/YjM9MKS6YVfy77B692noT3n/oUx3aeKkaJichduc0YovT0dKxYsQK//PILOnXqBACYPHkyfv75Z8ydOxfvvfdernP8/Pzg5+dnv//zzz8jMTERQ4Y4fmuUJAlBQUHOrQARuVylQH88Mf5h/DL7D6SnZECWZaiKCj+oCAj1x7nYJEiSBKGqthWsY9ORHqSH4qmzB0aKhxbpNf1hvJgEqOJmfCTLUIwG/N/bK/D5+OUQ8VeQbkqHLEuQZAmbv9+OOi0iMPGHMQiOqFZqvwMq/zhw2jXcJiCyWq1QFCVXy42Hhwe2bt1aqGvMnz8fXbt2RXi44yDIlJQUhIeHQ1EU3H333ZgyZQqaN2+e73XMZjPMZrP9vslkKkJNiMhVJEnC0A+ewlNvPYI/l/6Dg38dQcdH26L9w62h1WkRG5OISaOX4MKZy5BvpEJntsDzKJBZ1QOm5sGQVUBrBiw1qsASEgDjhUTorqYARgOg19mboNLirgHJ6QBsU/aRNVX/9P5z+HfbSQZE5Rg/DyoOt+ky8/HxQbt27TBlyhTExsZCURQsXboUu3btQlxc3G3Pj4uLwx9//IFhw4Y5pNevXx+LFi3C6tWrsXz5chiNRrRv3x6nT5/O91pTp061tz75+fkhLCzsjutHRM7j4WVE7+e74e3vx6Bz/0j7mkUhoZVQP9QfuqsmSGbbNH0JgOFKOowJGdClqjfHEGllWKt4Q1TyhTDoHfvjNDKQY9B1TopVyTOdygd+HlQckhC3dqiXnrNnz+K5557D33//DY1GgxYtWqBu3brYv38/jh07VuC5U6dOxf/93/8hNjYWer0+33yqqqJFixbo1KkTZs2alWeevL4RhIWFISkpCb6+vsWrHBGVijlvfo/fFv0NIKt1JzMTanoGYLVC6DSwRFSDNbwqNNBAm6ZAEoCkKJDSrYBVgTDoAF1WMHT1BhB3BUjPhKzRAJIETx8jHo16AA8OvR+VAv3yLwi5nMlkgp+f3x29d+f3eTB+/HiXjUVll1nxFeVvwG26zACgVq1a2LJlC1JTU2EymRAcHIwBAwYgIiKiwPOEEFiwYAEGDhxYYDAEALIso1WrVgW2EBkMBhgM3MuIqDx47u2HEFYnCCu//Asxh/8DLDcXdJQsCrTnEqCtVBlCc3N8tdBoIDzl3DPQqvhDum6CZLm5QWxacgaWvr8K303/FQuPfoyq1QOcXylyGVd/HjD4KT1u02WWk5eXF4KDg5GYmIh169ahX79+BebfsmULzpw5g6FDh9722kIIHDx4EMHBwSVVXCJyY0ZPA/oM6Yz52ychKCx3sCJptIBGLmiyWY7MEqQ8puILVcBitsB0PfnOC0xEpcKtWojWrVsHIQTq1auHM2fO4PXXX0e9evXss8YmTJiAmJgYfPPNNw7nzZ8/H23atEHjxo1zXfPdd99F27ZtUadOHZhMJsyaNQsHDx7E7NmzXVInInIPsizD6FlwC3JhCOQ/U//iqTjUasqVrYnKIrdqIUpKSsLIkSNRv359DBo0CB06dMD69euh0+kA2AZOX7x4Mdc5K1asyLd16MaNGxgxYgQaNGiA7t27IyYmBn///Tdat27t9PoQkXu5+17boq2yRr75v8UCOasbTZazQh0hbFGPELnXLfLxtP1/a1QkAR8O/xqv9piKXesOOakGROQsbjWo2l2VxMA8InIP0SdjsXrOOqxbvBk1G4fh0dEPom2fe3Bg/wXM/+IvnDt3FVJqBjSmdMCiQHgbofp5QcjSzSAoPQPS5UTIV5MAOWsGmixDkiR7UPVbwheQZbf6zlnhOOO9O/uaJTGomuOFnK/MDqomInK2sHohGPnpEIz81HEB17aRdWBJTMUHLy5ySJeS06EadYBXjoG1nkaI8CBIqWbcSlX5HZOoLOLXFyKiLNIte4EIISDMmZAuxEE+cQHSdZOtC00VkDIViJCqEJX9IbLWPYIs2xZ19PLEj1/8BVNiKqwWKzZ9tw1RHSdibNf/Ydsve6Aoah6PTkSliS1ERERZmneoiweejsSGH3fDkp4BYUoBVNXeUyadiwNSMiAq+UGCBOh0gFYLeHoAySmQcrQOLf54Db6Z+gs0KcnISMmwdaVJEg5vOYYqoQGY+N2raNC2TulUlIhyYUBERJTFy9cDo6b2x+A3HsQHg7/A/o15DI729rYFQ9kkCVAUh2AIsE3FV5LTYEnNAJDdlWbLcy0uEQc2HWVAVMFwzJB7Y5cZEdEtfPy90KBlBDT5bNdRJHnM0ZdkCZnpmXd+bSIqMWwhIiLKg7efJxSLAlkjQ8055kdRbLPKco43ym9hIlnKbhRyoFpVfP/xr0i+kYZ+I3ugRr2QEi07ERUdW4iIiPLQ76XueHvZKNRvVetmoqpCPRcNNf4KYMlasVoIwGK1xT2qcAiAJF8fSAGVAL0uR6IEyDIURcWaBX9heLPXcXxX/lsJEZFrsIWIiCgPGo2Mjg+1QseHWmFcjyk4sPHIzUUaE65CTbgKTc0agFWBdOtybrKtq02SJEheXoCXF5SEK4DF4jCTTbXaWp5uXDG5pE7kehw3VHawhYiI6Da8fDxyr1gNQLJYHYIhIQREpgUiLR1CURzzenlC8va6patNAjQanD5wHqpqC46yp+kveuc7XDod55wKEVEubCEiIrqNu+9rjB2r90JVVQhVQJYlqKqArFqhylrIsgQlPQMwZ94MnNLTIfR6wNsLkixD9vS07YPm6wuRnGzLmxUcffvRb/jzux0IrxeI49tP4kZCEmRZwrL3V6BVz+YYNOlx1G/NGWlEzsSAiIjoNvqN7IlOj7fDmnkb8evc9agSGoBHX+2NDo+0xn/HYrHs49+xe/We3CdqtTe7yKQck/UtVseWIgDx5xMQe/yC/X72itd71x1E8rVkfLZzqhNqRkTZGBARERVCpWp+ePqtR/H0W486pNe7OxzPjOmVd0AkIVfgk698tpUUqoBi5crWRM7GgIiI6A5Jcj5Bj4At0ClsUJSPC8ei8evcdeg6qDM8vO5sQ1FyPg6kLps4qJqI6A5FNKqOgW8+BJ9KXo4H0tOgpqdDCGELjLJvHsbcQZJGA0mvzzN4ysywYNbLX2NAyHBs+XGHE2tCVHExICIiukNanRZPj++HZadmoufADhBWK9SMDKhp6RDXrkONi4NIzwAyM4HUVEiKenMftKx1iSStFrKnJ2RfX9uxWwnAnGrGvvV5bCdCRHeMARERUQnRG3Ro0KoWhMXiOCZIUSHSUm1BUVayJEkQOW7ZLUOSJEHSagFNHtuGSDLSUzJcUBOiiodjiIiISpBvZW8AgEYrw2rJWotIqBApqQAASaeDZDAAsgwoVnuAJGTZ1m0my5A9PCB5ekIoCtS0NIjMTEgaGQLAlp92wpL5MR5+pReadmrosNAjlS6OHSrb2EJERFSC2vVpiZmb30X7fq1s0+xV1WE7D2GxQFitgNXquM+ZEJC1WkiybA9yJI3GFiRpNMi5YdquNfvxetcp2PTdNldUiahCYEBERFTCGrWvh7e/exW9nrsPGm3urq88W3XyaenJK1WxqpBkCUlXk++wpESUjQEREZGTeHgbbTPMnECoAid2n4E5PROAbcuPLT/uwOzRC3B02wmnPS5RecUxRERETnL3/U2wduEmpCalQZJtK1WrqoBOK8GqApIsQ1WyFl1UVQhVhSTf8j01r8HVWTb9sAO71x1CnWbhOH/0AhIvJ0HWyPj5sz9wV9NwDJrcH+0fau28CpIdxw+VfWwhIiJykra978H3sV9hzLwXENG4Bmq3uAvjl7yCVVe+xuJ//w+9h91v2xDWqkDNMEO5kQTFlAxhtdrSVRWSVgvJ29s2EDubLNum6ksSUpPScODPw0i8nAQA9gDr3JEL+OSFr0qj2kRlEluIiIicyOBhQK+hXdBraBeH9MAaVfD0+H74+ZPfHNJtaxiZIRsNDlPxodfbBmirhdvGQ4ibwRER3R5biIiISomssb0F28dTZ61kLTLNUEwm2yrXqprVimTNOkl2HIAtSZD0BkhaXa6B2cmJKVg86Xtci0t0QW2IyjYGREREpcQ3wBuvff0CQusEZy3kmHXLDozMZqipqRBmM6AotoAna2VrSLabJNm6ziSNBtItK1wLVeDb91fiqfAX8MNHv5RKHSsCjh8qHxgQERGVop5D7sOCYzMxfNrTeR7PuS7Rbam5Z5apqgrVqmLv+oN3UEqi8o8BERFRKZMkCXXuucupj5GW7Ljlh2JVcHDTUSQmJDn1cYnKCg6qJiJyA/5VfW1T8yXJPhhaliX7ekKyRs5jkLSAw9KNBbQkndx9BiPbvIleQ++H6WoSfv58LRLjb0Cj1eD+pzrgkdEPonbziBKuFVHZIQmu3nVbJpMJfn5+SEpKgq+vb2kXh4jKqZgzcfjl87VY8/WfMKeZUadFBB6N6g2vAG+s/nIj9m08ahtbZJ9tJkHSyMgZFAlVhVAUQFVyXNl23BZUKba7Od75NVoZilXFwpOzUL1OsCuq6hLOeO/Ovub48eNhNBo5fsjNFeVvgC1ERERuIrR2MF76ZAie/d8AXItNRI36ofZjbR9ogeHNxuLCsUs5zhAQVtU2wyyLJMu2BR+t0s2B2FnU7DFGt3wNVqy2lqc0U1qJ14morOAYIiIiN+Pl6+kQDGXT6R2/wwohIISAarFAKIq9e00IYRuIfeuq18DNmWp52P/nEShWJc9jROUdAyIiojKiy9MdoTfqIEnSzW4zVQVUBcJqgcg029Yrylq8UZJl23R8Wcpxk+23W80fvwxPhb+Inz/7w9VVIyp1DIiIiMqIR6MexPeXvsSI6c8gzzaeAlp/bpXf4NHrcYmYPXoBrBZrcYtJVCYxICIiKkO8/DzxaNSD0Orz3/S1JHC+ze1xQHX5woCIiKgM0mg1kOVCLthYDLNemodzRy447fpE7oYBERFRGTR55eto3LGBY6IQtin32R1i2VuAiNwtPvkOus6y4ZstGNFsLD4eOqeES07knhgQERGVQfd0a4b/2/Qu3v99guMBoUJYrRCK1TYLTVWzAiPb3mY5Ze+Blte4o+yp+Ac3HXVaHYjcCQMiIqIyrHrdkLwPZLUO3ZqWPVXfUf5db+b0TCjKzan4ilXB3vWHcPFETDFLXD5MmDDh9pmoTOHCjEREZZi3vxcMngZkZmTaW4AkWbL/rNHKUCzZAY2wbwArJAmO237ItuO3BEs3LidhUK2X8eCIblAUBb9/uQHXYhMBAM27NMGjUQ+i9QMtCr8BLZGb4tYdhcCtO4jInZmuJWPN139i1aw1uB6XiFrNwvHomD6oXjcEv3+1EesXb8rVXWaTxzR9IZBrUr6UOwm4ub/a1LVvo2X3ZiVUm5LjzK07+HlQNnDrDiKiCsS3sg+eeOMhPP5aHyRcvIqgiGr2FpsGberAdD0ZO1fvzd1VVthWnXy+NmdvNpueklHcohO5DY4hIiIqJzRaDYLvCszVfaXTayHyXYrxzu1esx9pyelOuz6RK7hVQJScnIyoqCiEh4fDw8MDkZGR2LNnT775Bw8ebJslccutUaNGDvlWrFiBhg0bwmAwoGHDhli1apWzq0JE5Dbue6I9/Kv6AYBjsJTXwOtiWLvgL/QPHo4vx37DvdCozHKrgGjYsGHYsGEDlixZgiNHjqB79+7o2rUrYmLyns3w6aefIi4uzn6Ljo5GQEAAHn/8cXueHTt2YMCAARg4cCAOHTqEgQMHon///ti1a5erqkVEVKo6PNwGy6O/wJvfRkFnyDlSImu8kFBvJklS1gDrog2SNqeZ8dOMXxF/PqEkikzkcm4zqDo9PR0+Pj745Zdf8OCDD9rT7777bvTu3Rvvvffeba/x888/45FHHsG5c+cQHh4OABgwYABMJhP++OPmZoU9e/ZEpUqVsHz58kKVjYPoiKi8GFT7ZcT9dzmPI3kNsFbzyFewhSdnoXqdYPt9VVUhhIBG49ytRvLCQdVUlOfLbVqIrFYrFEWB0Wh0SPfw8MDWrVsLdY358+eja9eu9mAIsLUQde/e3SFfjx49sH379nyvYzabYTKZHG5EROWBzqCFrMnrrT+rpUjk+L8YPn3hS+zfeBhJ15Lx/fRf8FSNF/BwwGB8OfabMtl6xM+DisNtAiIfHx+0a9cOU6ZMQWxsLBRFwdKlS7Fr1y7ExcXd9vy4uDj88ccfGDZsmEN6fHw8AgMDHdICAwMRHx+f77WmTp0KPz8/+y0sLKx4lSIicjNvLX8VnftHQtLk1yUmbvm/aA7/fRxvdJ+Cx6sNxfw3l+FabCLSkzOw8tPfMajWy/j58z9ufxE3ws+DisNtAiIAWLJkCYQQCA0NhcFgwKxZs/DUU08Vqql10aJF8Pf3x0MPPZTr2K0zLoQQBS4iNmHCBCQlJdlv0dHRRa4LEZE7uqtpON5cNhpL/3POHmXZU/Ft24YIh3RJlnB6/39OeVxn4edBxeFW6xDVqlULW7ZsQWpqKkwmE4KDgzFgwABEREQUeJ4QAgsWLMDAgQOh1+sdjgUFBeVqDUpISMjVapSTwWCAwWAofkWIiNxc5eBKLn9MIQQS45Nu+6XUnfDzoOJwqxaibF5eXggODkZiYiLWrVuHfv36FZh/y5YtOHPmDIYOHZrrWLt27bBhwwaHtPXr1yMyMrJEy0xEVJZIsoRqNarYf875P4B8xhndGaEK7Fl7AMObjMEf8/+E1WIt8ccgKi63CojWrVuHtWvX4ty5c9iwYQPuu+8+1KtXD0OGDAFga7ocNGhQrvPmz5+PNm3aoHHjxrmOjR49GuvXr8e0adNw4sQJTJs2DRs3bkRUVJSzq0NE5LZkWcbCE5/i9YUjUbORbVxMeMPqGLvgJSw+NQv9X+8HnUHnlMe+eCIGM4Z/gT/m/+WU6xMVh1t1mSUlJWHChAm4dOkSAgIC8Oijj+L999+HTmd7UcbFxeHixYu5zlmxYgU+/fTTPK8ZGRmJ7777Dm+//TYmTpyIWrVq4fvvv0ebNm2cXh8iInemN+rR/dl70W1QZ1yPv4GAIH97V9bQD56CJSMTP3/+BxRr0affF0SoArJGhjnNXKLXJboTbrMOkTvjuhNEVBHNG7cEP838FarinI+J5l2a4PWFI1G1emWYridj7fy/cHjLMXTuH4nOAyKhv8MWKq5DREV5vhgQFQJfAERUER3fdRofPzcbF4/HQJYlqGrJflzIsgRVCITUCkTCxWv2bT+EKuAT4I1nJj6GR0Y/eJur5I8BEZXJhRmJiMi9NGhTB18fnYmP/5oM36ol/+GvqgIQQOyZy7BmWiHUm1P1k6+nYMm7P5b4YxLlhwERERHlS5IkNLu3EWo1q+nyx2YHBrkSAyIiIrotvVGXeyp+1iz9nNP1S1JqUhqmPvMpTu4545TrE+XEgIiIiG7rldnD8MjoB+HhfXO/yZqNwtB/XD+07NHMHhyVtC0/bMfLbSZg9ugFznkAoixuNe2eiIjcU5XQynj+40EYNPlxbF21G0E1q6Fxh/r2afp/ffsPpj4zq8QfN3vK/9lD50v82kQ5MSAiIqJC8/D2QLeBnXOlV6leuRRKQ1Ry2GVGRER3rGr1yvDwNpb4HmUare1jqjQGdVPFwoCIiIjuWPBdgVh+6Uu89MkQ+FT2LrHrdu4fic92foCRnz5XYtckygu7zIiIqER4+XrioVG9AAmYE7XQvqZQsa/n54kJS0eXUOmICsYWIiIiKlGSJN1xMAQAGakZ2Pz9Nlgt1hIoFVHBGBAREVGJav1AczTv0gQAcq9dVASqouL9Jz/BUzVewKbvtpVU8YjyxICIiIhKVHBEIKZveAdfH52BsHohxb5O9kLViZeT8OsX60qodER5Y0BEREROEd4wDI07NIBGpyntohDdFgMiIiJyGoOHHmrW4orZsrf6uHXLDznr/q3dbLIswehpcGIpiTjLjIiInOjZ/w1AleqVsfLT33H10jUAQI36objviQ6IP38ZG5f+A2umFZIsIfKh1qh7z13YtWY//t12EgDgXckL/V7qiX4v9yzNalAFIAluJ3xbJpMJfn5+SEpKgq+vb2kXh4iozFEUBXvXHYKHtxFNOjawL+BoupaMPWsPokmnBqgWVsWe/8zBc4g7exltHmwBvVFfrMd0xns3Pw/KlqI8X2whIiIip9NoNGjzQItc6b6VfdDl6Y650mvfHYHad0e4omhEADiGiIiIiIgBEREREREDIiIiIqrwGBARERFRhceAiIiIiCo8BkRERERU4TEgIiIiogqP6xAVQvbalSaTqZRLQkREhZX9nl2S6w/z86BsKcrfAAOiQkhOTgYAhIWFlXJJiIioqJKTk+Hn51ci17p2zbb9CD8PypbC/A1w645CUFUVsbGx8PHxsS83X1QmkwlhYWGIjo4uV8u9s15lC+tVtrBed0YIgeTkZISEhECWS2aEyI0bN1CpUiVcvHixxIKs8shd/naL8jfAFqJCkGUZ1atXL5Fr+fr6lqs3tmysV9nCepUtrFfxlXTQkv2h6ufnVy6fk5LmDn+7hf0b4KBqIiIiqvAYEBEREVGFx4DIRQwGAyZNmgSDwVDaRSlRrFfZwnqVLayX+ynLZXelsvh74qBqIiIiqvDYQkREREQVHgMiIiIiqvAYEBEREVGFx4CIiIiIKjwGRCVozpw5iIiIgNFoxD333IN//vmnwPxbtmzBPffcA6PRiLvuugtffPGFi0paOFOnTkWrVq3g4+ODatWq4aGHHsLJkycLPGfz5s2QJCnX7cSJEy4q9e1Nnjw5V/mCgoIKPMfdnysAqFmzZp6/+5EjR+aZ312fq7///ht9+vRBSEgIJEnCzz//7HBcCIHJkycjJCQEHh4euPfee/Hvv//e9rorVqxAw4YNYTAY0LBhQ6xatcpJNchbQfWyWCx444030KRJE3h5eSEkJASDBg1CbGxsgddctGhRns9hRkaGk2tz0+2er8GDB+cqX9u2bW973dJ+vvJT1Pf58uR2752FeW2azWaMGjUKVapUgZeXF/r27YtLly65uip5YkBUQr7//ntERUXhrbfewoEDB9CxY0f06tULFy9ezDP/uXPn8MADD6Bjx444cOAA3nzzTbzyyitYsWKFi0uevy1btmDkyJHYuXMnNmzYAKvViu7duyM1NfW25548eRJxcXH2W506dVxQ4sJr1KiRQ/mOHDmSb96y8FwBwJ49exzqtGHDBgDA448/XuB57vZcpaamolmzZvj888/zPD59+nTMmDEDn3/+Ofbs2YOgoCB069bNvudgXnbs2IEBAwZg4MCBOHToEAYOHIj+/ftj165dzqpGLgXVKy0tDfv378fEiROxf/9+rFy5EqdOnULfvn1ve11fX1+H5y8uLg5Go9EZVcjT7Z4vAOjZs6dD+dasWVPgNd3h+cpLUd/ny6OC3jsL89qMiorCqlWr8N1332Hr1q1ISUlB7969oShKaVTHkaAS0bp1a/HCCy84pNWvX1+MHz8+z/zjxo0T9evXd0h7/vnnRdu2bZ1WxjuVkJAgAIgtW7bkm2fTpk0CgEhMTHRdwYpo0qRJolmzZoXOXxafKyGEGD16tKhVq5ZQVTXP42XhuQIgVq1aZb+vqqoICgoSH374oT0tIyND+Pn5iS+++CLf6/Tv31/07NnTIa1Hjx7iiSeeKPEyF8at9crL7t27BQBx4cKFfPMsXLhQ+Pn5lWzh7kBe9Xr22WdFv379inQdd3u+shX1fb68Kei9szCvzRs3bgidTie+++47e56YmBghy7JYu3atU8teGGwhKgGZmZnYt28funfv7pDevXt3bN++Pc9zduzYkSt/jx49sHfvXlgsFqeV9U4kJSUBAAICAm6bt3nz5ggODkaXLl2wadMmZxetyE6fPo2QkBBERETgiSeewH///Zdv3rL4XGVmZmLp0qV47rnnbrshsbs/VzmdO3cO8fHxDs+HwWBA586d832tAfk/hwWdU9qSkpIgSRL8/f0LzJeSkoLw8HBUr14dvXv3xoEDB1xTwCLYvHkzqlWrhrp162L48OFISEgoML87Pl/FeZ8vj/J77yzMa3Pfvn2wWCwOeUJCQtC4cWO3+B0yICoBV69ehaIoCAwMdEgPDAxEfHx8nufEx8fnmd9qteLq1atOK2txCSEwZswYdOjQAY0bN843X3BwML766iusWLECK1euRL169dClSxf8/fffLixtwdq0aYNvvvkG69atw7x58xAfH4/IyEhcu3Ytz/xl7bkCgJ9//hk3btzA4MGD881TFp6rW2W/noryWss+r6jnlKaMjAyMHz8eTz31VIEbY9avXx+LFi3C6tWrsXz5chiNRrRv3x6nT592YWkL1qtXLyxbtgx//fUX/u///g979uzB/fffD7PZnO857vh8Fed9vrwp6L2zMK/N+Ph46PV6VKpUKd88pYm73ZegW7+JCyEK/HaeV/680t3Byy+/jMOHD2Pr1q0F5qtXrx7q1atnv9+uXTtER0fj448/RqdOnZxdzELp1auX/ecmTZqgXbt2qFWrFhYvXowxY8bkeU5Zeq4AYP78+ejVqxdCQkLyzVMWnqv8FPW1VtxzSoPFYsETTzwBVVUxZ86cAvO2bdvWYYBy+/bt0aJFC3z22WeYNWuWs4taKAMGDLD/3LhxY7Rs2RLh4eH4/fff8cgjj+R7nrs+X+5aLlco6L0z+++wOL8fd/kdsoWoBFSpUgUajSZXhJuQkJArWs4WFBSUZ36tVovKlSs7razFMWrUKKxevRqbNm1C9erVi3x+27Zt3eob6628vLzQpEmTfMtYlp4rALhw4QI2btyIYcOGFflcd3+usme0FOW1ln1eUc8pDRaLBf3798e5c+ewYcOGAluH8iLLMlq1auXWz2FwcDDCw8MLLKM7Pl/FeZ8v73K+dxbmtRkUFITMzEwkJibmm6c0MSAqAXq9Hvfcc499Vk+2DRs2IDIyMs9z2rVrlyv/+vXr0bJlS+h0OqeVtSiEEHj55ZexcuVK/PXXX4iIiCjWdQ4cOIDg4OASLl3JMZvNOH78eL5lLAvPVU4LFy5EtWrV8OCDDxb5XHd/riIiIhAUFOTwfGRmZmLLli35vtaA/J/Dgs5xtexg6PTp09i4cWOxgm0hBA4ePOjWz+G1a9cQHR1dYBnd8fkqzvt8eZfzvbMwr8177rkHOp3OIU9cXByOHj3qHr/D0hnLXf589913QqfTifnz54tjx46JqKgo4eXlJc6fPy+EEGL8+PFi4MCB9vz//fef8PT0FK+++qo4duyYmD9/vtDpdOKnn34qrSrk8uKLLwo/Pz+xefNmERcXZ7+lpaXZ89xar5kzZ4pVq1aJU6dOiaNHj4rx48cLAGLFihWlUYU8vfbaa2Lz5s3iv//+Ezt37hS9e/cWPj4+Zfq5yqYoiqhRo4Z44403ch0rK89VcnKyOHDggDhw4IAAIGbMmCEOHDhgn2314YcfCj8/P7Fy5Upx5MgR8eSTT4rg4GBhMpns1xg4cKDDzJ9t27YJjUYjPvzwQ3H8+HHx4YcfCq1WK3bu3OkW9bJYLKJv376ievXq4uDBgw6vN7PZnG+9Jk+eLNauXSvOnj0rDhw4IIYMGSK0Wq3YtWuXW9QrOTlZvPbaa2L79u3i3LlzYtOmTaJdu3YiNDTU7Z+vvNzufb68u917Z2Femy+88IKoXr262Lhxo9i/f7+4//77RbNmzYTVai2tatkxICpBs2fPFuHh4UKv14sWLVo4TE9/9tlnRefOnR3yb968WTRv3lzo9XpRs2ZNMXfuXBeXuGAA8rwtXLjQnufWek2bNk3UqlVLGI1GUalSJdGhQwfx+++/u77wBRgwYIAIDg4WOp1OhISEiEceeUT8+++/9uNl8bnKtm7dOgFAnDx5MtexsvJcZS8HcOvt2WefFULYpvdOmjRJBAUFCYPBIDp16iSOHDnicI3OnTvb82f78ccfRb169YROpxP169d3eeBXUL3OnTuX7+tt06ZN+dYrKipK1KhRQ+j1elG1alXRvXt3sX37drepV1pamujevbuoWrWq0Ol0okaNGuLZZ58VFy9edLiGOz5f+Snofb68u917Z2Fem+np6eLll18WAQEBwsPDQ/Tu3TvX30NpkYTIGh1KREREVEFxDBERERFVeAyIiIiIqMJjQEREREQVHgMiIiIiqvAYEBEREVGFx4CIiIiIKjwGRERERFThMSAiIiKiCo8BEREREVV4DIiIiIiowmNARFTOXbt2DdWqVcP58+ed/liPPfYYZsyY4fTHIaro4uPjMWrUKNx1110wGAwICwtDnz598Oeff+bKO3jwYIwfP97h3NGjR6N27dowGo0IDAxEhw4d8MUXXyAtLa1Qj9+nTx907do1z2M7duyAJEnYv39/geVwN9zLjKicGzt2LBITEzF//nynP9bhw4dx33334dy5c/D19XX64xFVROfPn0f79u3h7++Pd999F02bNoXFYsG6devw1Vdf4cSJE/a8qqoiMDAQq1evRrt27fDff/85nNukSRNYrVacOnUKCxYswPPPP4++ffvetgw///wzHnnkEZw7dw7h4eEOx4YPH469e/fiwIED+ZbDLZXu3rJE5ExpaWnC39/fpTugt2jRQsyZM8dlj0dU3nTu3FmMHDlSjBw5Uvj5+YmAgADx1ltvCVVVhRBC9OrVS4SGhoqUlJRc5yYmJjrc//vvv0W1atWEoihCCCF69Oghqlevnue5Qgj7Y6iqKqZNmyYiIiKE0WgUTZs2FT/++KM9n8ViEYGBgWLy5MkO56empgofHx/x2Wef5VsORVHEhx9+KGrVqiX0er0ICwsT7733XtF+SU7ALjOiMuaDDz6AJEm5bnl1Vf3xxx/QarW5vpHVrFkTn3zyiUPa3XffjcmTJ9vv33vvvRg1ahSioqJQqVIlBAYG4quvvkJqaiqGDBkCHx8f1KpVC3/88YfDdfr27Yvly5eXWH2JKqLFixdDq9Vi165dmDVrFmbOnImvv/4a169fx9q1azFy5Eh4eXnlOs/f39/h/urVq9GnTx/Isoxr165h/fr1+Z4LAJIkAQDefvttLFy4EHPnzsW///6LV199Fc888wy2bNkCANBqtRg0aBAWLVoEkaOj6ccff0RmZiaefvrpfMsxYcIETJs2DRMnTsSx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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAG1CAYAAAAYxut7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAAB97UlEQVR4nO3dd3hT1f8H8Pe9SZqkK20ZXZRSGWXvWZaDqQxFBReIgIjiqKgIKopfVAT9OVBBUZa4UAFFAQGVIXsjyN6FthRoaToz7j2/P9KGhrbQljZN2/frefJAzj335pymufn0TEkIIUBERERUhcnlXQAiIiKi8saAiIiIiKo8BkRERERU5TEgIiIioiqPARERERFVeQyIiIiIqMpjQERERERVHgMiIiIiqvK05V2AikBVVcTHx8PPzw+SJJV3cYiIqAiEEEhLS0NYWBhkmX//0/UxICqC+Ph4RERElHcxiIioBOLi4lCrVq3yLgZ5uAofEG3YsAHvvfcedu3ahYSEBCxduhR3332387gQAm+++SZmz56NlJQUdOjQAZ999hmaNGlS5Nfw8/MD4PhQ+fv7l3YViIioDJjNZkRERDjv4UTXU+EDooyMDLRo0QKPPfYY7r333nzHp0+fjg8++ADz589HgwYN8NZbb6Fnz544cuRIkT8kud1k/v7+DIiIiCoYDnWgoqjwAVHfvn3Rt2/fAo8JIfDRRx/h1VdfxaBBgwAACxYsQHBwML777js88cQT7iwqEREReagKHxBdz6lTp5CYmIhevXo50/R6Pbp3747NmzcXGhBZLBZYLBbnc7PZXOZlJbpZb775ZrHyv/HGG0W+RkF5iYgqk0odECUmJgIAgoODXdKDg4Nx5syZQs+bOnVqsb9ciCoa/o4TEV1VJeYhXtt/LIS4bp/yxIkTkZqa6nzExcWVdRGJiIioHFXqFqKQkBAAjpai0NBQZ3pSUlK+VqO89Ho99Hp9mZePiIiIPEOlDoiioqIQEhKCNWvWoFWrVgAAq9WK9evXY9q0aeVcOqKSY3cXEVHpqvABUXp6Oo4fP+58furUKezduxdBQUGoXbs2YmNj8c4776B+/fqoX78+3nnnHXh7e+Ohhx4qx1ITERGRJ6nwAdHOnTtx2223OZ+PGzcOAPDoo49i/vz5GD9+PLKysvDUU085F2ZcvXo1F+oiIiIiJ0kIIcq7EJ7ObDbDZDIhNTWVCzOSR/CULjNOxydPxns3FUeFbyEiqsw8JfAhIqrsGBARUYkVFLCx1YiIKqIqsQ4RERER0fUwICIiIqIqjwERERERVXkMiIiIiKjK47T7IuDUTXKHyj6jjIOtyd1476biYAsRERERVXlun3afnZ2Nf//9F0lJSVBV1eXYgAED3F0cIiIiIvcGRH/88QeGDRuGS5cu5TsmSRIURXFncYiIiIgAuHkMUb169dC7d2+8/vrrCA4OdtfL3jT2Q1NpquxjhYqLY4uorPDeTcXh1jFESUlJGDduXIUKhoiIiKjyc2tAdN9992HdunXufEkiIiKiG3LrGKJPP/0U999/P/755x80a9YMOp3O5fizzz7rzuIQERERAXBzQPTdd99h1apVMBqNWLduHSRJch6TJIkBEREREZULtwZEr732Gv73v/9hwoQJkGUugUSVGwdPExFVHG6NSqxWK4YMGcJgiIiIiDyKW1uIHn30USxatAivvPKKO1+WiDxYQS1pnIpPRO7m1oBIURRMnz4dq1atQvPmzfMNqv7ggw/cWRwiIiIiAG4OiPbv349WrVoBAA4cOOByLO8AayIiIiJ3cmtAtHbtWne+HBEREVGRuHXrjsJcvHgRf/75Jx588MHyLkqBuPw73QhnlLkHxxZRcfDeTcXh1hai//3vfwWmnzhxAkuWLPHYgIiIiIgqN7cGREuXLnV5rigK4uLiYDabMWXKFHcWhYiIiMjJrQHRnj178qXZ7XbExsbi4MGD7iwKERERkZNbA6ICC6DVIjY2Fs2bNy/vohAREVEV5RFLRp85cwZRUVHlXQwiIiKqotw6y2zGjBn50hITEzFv3jz0798fTZs2daZ70kavnKlAuTibzDNx9hkVhPduKg63dpl9+OGHBaYbDAasWbMGa9asAeBYpNGTAiIiIiKq3NwSEL3yyiu4++67cerUKXe8HBEREVGxuGUMUUJCAvr164fQ0FCMHj0aK1asgMViccdLExEREd2QWwKiefPm4cKFC/jxxx8REBCAcePGoXr16hg0aBDmz5+PS5cuuaMYRERERAUqt607Dh06hN9++w2//vordu7ciQ4dOmDAgAF48MEHER4eXh5FKhQH5lU9HDxdsXBQNRWE924qDo/YyywpKQm//fYbli1bhq5du+LFF18s7yK54Ieq6mFAVPExSCLeu6k4yn1hRgCoWbMmRo4ciZEjR5Z3UYiIiKgKcssYor///huNGzeG2WzOdyw1NRVNmjTBP//8446iEBEREeXjloDoo48+wuOPP15gk6XJZMITTzyBDz74wB1FISIiIsrHLQHRvn370KdPn0KP9+rVC7t27XJHUYiIiIjyccsYogsXLkCn0xVeCK0WFy9edEdRiPLhAGoiInJLQBQeHo79+/ejXr16BR7/999/ERoa6o6iEFEVUVigy9lnRFQQt3SZ3XnnnXj99deRnZ2d71hWVhbeeOMN9OvXzx1FISIiIsrHLS1Er732GpYsWYIGDRrg6aefRnR0NCRJwqFDh/DZZ59BURS8+uqr7igKERERUT5uCYiCg4OxefNmPPnkk5g4cSJy14KUJAm9e/fGzJkzERwc7I6iEBEREeXjtoUZIyMjsWLFCqSkpOD48eMAgHr16iEwMNBdRSAiIiIqkNtXql6yZAk+/PBDHDt2DABQv359xMbGYtSoUe4uClUxnE1GAAdbE1HB3BoQTZo0CR9++CGeeeYZdOrUCQCwZcsWPP/88zh9+jTeeustdxaHiIiICICbA6JZs2bhyy+/xIMPPuhMGzBgAJo3b45nnnmGARERERGVC7dMu8+lKAratm2bL71Nmzaw2+3uLAoRERGRk1tbiB555BHMmjUr375ls2fPxsMPP+zOolAlxrFCRERUXG4fVD1nzhysXr0aHTt2BABs3boVcXFxGDZsGMaNG+fMx81eicidCgqkOdCaqOpwa0B04MABtG7dGgBw4sQJAECNGjVQo0YNHDhwwJlPkiR3FouIiIiqOLcGRGvXrnXnyxEREREVidu7zIhKE8cLERFRaWBARERUCC7iSFR1uHXaPREREZEnYkBEREREVR67zKhC4FghIiIqSwyIiIiKiWsWEVU+DIjIo7AliIiIygMDIiKiUsAZaUQVGwdVExERUZUnCSFEeRfC05nNZphMJqSmpsLf37+8i1NpsHuMqjK2HJU93rupONhCRERERFUeW4iKgH9l3By2BBEVDVuNShfv3VQcDIiKgB+qomHgQ1Q2GCiVDO/dVBxVapbZzJkz8d577yEhIQFNmjTBRx99hK5du5Z3sSokBj9ERFSZVJkWokWLFmHo0KGYOXMmOnfujC+++AJfffUVDh48iNq1a1/33Kr8VwYDH6KKha1JV1XlezcVX5UJiDp06IDWrVtj1qxZzrRGjRrh7rvvxtSpU13yWiwWWCwW5/PU1FTUrl0bcXFxleJDdW19iajymzhxYnkXwe3MZjMiIiJw5coVmEym8i4OebgqERBZrVZ4e3vjp59+wj333ONMf+6557B3716sX7/eJf/kyZPZMkJEVEnExcWhVq1a5V0M8nBVYgzRpUuXoCgKgoODXdKDg4ORmJiYL//EiRMxbtw453NVVZGcnIxq1apBkqQSlSH3L5XK0sqUi/WqWFivioX1ujlCCKSlpSEsLKzMXoMqjyoREOW6NpgRQhQY4Oj1euj1epe0gICAUimDv79/pbqx5WK9KhbWq2JhvUqOXWVUVFViYcbq1atDo9Hkaw1KSkrK12pEREREVU+VCIi8vLzQpk0brFmzxiV9zZo1iImJKadSERERkaeoMl1m48aNw9ChQ9G2bVt06tQJs2fPxtmzZzFmzBi3vL5er8cbb7yRryuuomO9KhbWq2JhvYjcp0rMMss1c+ZMTJ8+HQkJCWjatCk+/PBDdOvWrbyLRUREROWsSgVERERERAWpEmOIiIiIiK6HARERERFVeQyIiIiIqMpjQERERERVHgMiIiIiqvIYEBEREVGVx4CIiIiIqjwGRERERFTlVZmtO26GqqqIj4+Hn58fJEkq7+IQEVERCCGQlpaGsLAwyHLp/P3P74OKpTi/AwyIiiA+Ph4RERHlXQwiIiqBuLg41KpVq1Suxe+DiqkovwMMiIrAz88PgOMH6u/vX86lISKiojCbzYiIiHDew0sDvw8qluL8DjAgKoLcZlF/f39+AIiIKpjS7Nri90HFVJTfAQ6qJiIioiqPARERERFVeQyIiIiIqMpjQERERERVHgMiIiIiqvIYELlJRmoGsjKyy7sYREREVABOuy9jx/eewi8zVuCvb/+BRqdF35G34+5n+iK8Xmh5F42IiIhysIWoDC35eDmebD0ef36zAXabAkumBctmrcLw6Gex4ect5V08IiIiysGAqAydP5YAjVaGYledaapdhSzLSDiZVI4lIyIiorwYEJUxUVCaquLcsQQIcfXo8T2nsPjD35Fw6oL7CkdEREQAOIaoTNVrFQWhCsgaGapytZVICOCPOX9h/4aDaHVHM5zYewqHth4DAHz+4gJ07NcGD064B407RZdX0YmIiKoUSeRtpqACmc1mmEwmpKamFnvv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Bj788EN89dVXSE5Oxh9//IGxY8fCx8cn33kBAQEuz5ctW4b+/ftDlmVcvnwZq1evLvRcAJAkCQDw2muvYd68eZg1axb+++8/PP/883jkkUewfv16AIBWq8WwYcMwf/58iDwdTT/99BOsVisefvjhQssxceJETJs2DZMmTcLBgwfx3XffITg4+GZ+XKWjvCMyIioes9ksEhISnI8nn3xSREZGiri4uHx5n3vuOdGnT5986ZGRkeLDDz90SWvRooV44403nM+7d+8u/Pz8xJQpU8TRo0fFlClThCzLom/fvmL27Nni6NGj4sknnxTVqlUTGRkZzvNWrFgh9Hq9yM7OLrU6E1Ul3bt3F40aNXK21gghxMsvvywaNWoktm3bJgCIJUuWFOlaDRo0EMuWLRNCCLF169YCz61WrZrw8fERPj4+Yvz48SI9PV0YDIZ8LcsjR44UDz74oPP5oUOHBADx999/O9O6devmkufacpjNZqHX68WXX35ZpPK7E1uIiCoYPz8/hISEICQkBF988QVWrFiB9evXo1atWvnynj59GmFhYSV+rRYtWuC1115D/fr1MXHiRBiNRlSvXh2PP/446tevj9dffx2XL1/Gv//+6zwnPDwcFosFiYmJJX5doqquY8eOztYaAOjUqROOHTvmbI3Je6wwhw4dwrlz5/INfr723O3bt2Pv3r1o0qQJLBYLDh48iOzsbPTs2RO+vr7Ox9dff40TJ044z2vYsCFiYmIwd+5cAMCJEyfwzz//YMSIEYWW49ChQ7BYLLjjjjuK9wNxA215F4CISubNN9/EvHnzsH79+nyDGnNlZWXBYDCU+DWaN2/u/L9Go0G1atXQrFkzZ1puM3dSUpIzzWg0AkCRZ6sQUdHVq1cPkiTh0KFDuPvuu6+bd9myZejZs6fzM5l7bt5B1wBwyy23ALj62VVVFQCwfPlyhIeHu+TV6/Uuz0eOHImnn34an332GebNm4fIyMh8wU7ecuS+hidiCxFRBVSUYAgAqlevjpSUlCJdU1GUfGk6nc7luSRJLmm5f2nm3kABIDk5GQBQo0aNIr0uEeW3devWfM/r16+PatWqoXfv3vjss8+QkZGR77wrV644///rr7+6zBirVq0aevbsiU8//bTAc3M1btwYer0eZ8+eRb169VweERERLnkHDx4MjUaD7777DgsWLMBjjz2WrwUqbznq168Po9FY4PIA5Y0BEVEFU9RgCABatWqFgwcPFngsb5eWzWZDXFxcqZTvwIEDqFWrFqpXr14q1yOqiuLi4jBu3DgcOXIE33//PT755BM899xzAICZM2dCURS0b98eixcvxrFjx3Do0CHMmDHDOYEiKSkJO3bsQL9+/VyuO3PmTNjtdrRt2xaLFi3CoUOHcOTIEXzzzTc4fPgwNBoN/Pz88OKLL+L555/HggULcOLECezZswefffYZFixY4HI9X19fDBkyBK+88gri4+MxfPhwl+PXlsNgMODll1/G+PHjnV1wW7dudcuyIDfCLjOiCuStt97Cp59+it9//x16vd4Z1AQGBuZrygaA3r17Y+LEiUhJSUFgYKDLsXnz5qFHjx6IjIzExx9/jNTUVJw4cQIXLly4qRkf//zzD3r16lXi84kIGDZsGLKystC+fXtoNBo888wzGD16NAAgKioKu3fvxttvv40XXngBCQkJqFGjBtq0aYNZs2YBAH777Td06NABNWvWdLlu3bp1sWfPHrzzzjuYOHEizp07B71ej8aNG+PFF1/EU089BQCYMmUKatasialTp+LkyZMICAhA69at8corr+Qr68iRIzFnzhz06tULtWvXdjlWUDkmTZoErVaL119/HfHx8QgNDcWYMWNK9edXIuU9qpuIikZVVeHv7y8A5Hts3bq10PM6duwoPv/8c5e0yMhIMXLkSNGoUSOh1+vFgw8+KKZMmSK8vb3FN998I4RwzHR57rnn8p137ew0AGLp0qVCCCGysrKEv7+/2LJly03Xl6iqKuizV1z9+/cX06ZNK50CVYJyFAVbiIgqCEmSkJqaWuzzJk2ahBdffBGPP/44ZPlqL3nTpk3x1VdfueR97bXXnP9ft25dvmsVtP2HyLMGyZw5c9ChQwd07Nix2OUkotLTpUsXPPjgg+VdDI8pR1EwICKq5O68804cO3YM58+fzzcgsrTpdDp88sknZfoaRHRj48ePL+8iAPCcchQF9zIjqoLq1KmD2NhYxMbGlndRiIg8AgMiIiIiqvI47Z6IiIiqPAZEREREVOUxICIiIqIqjwERERERVXkMiIiIiKjKY0BEREREVR4DIiIiIqryGBARERFRlceAiIiIiKo8BkRERERU5f0/KiIMSTvNusMAAAAASUVORK5CYII=", "text/plain": [ "
" ] diff --git a/examples/units/units.ipynb b/examples/units/units.ipynb index f76b8e1..5eff3f3 100644 --- a/examples/units/units.ipynb +++ b/examples/units/units.ipynb @@ -14,10 +14,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:18.936119Z", - "iopub.status.busy": "2023-11-15T16:37:18.935708Z", - "iopub.status.idle": "2023-11-15T16:37:18.949303Z", - "shell.execute_reply": "2023-11-15T16:37:18.948869Z" + "iopub.execute_input": "2023-11-15T21:43:09.525999Z", + "iopub.status.busy": "2023-11-15T21:43:09.525826Z", + "iopub.status.idle": "2023-11-15T21:43:09.539534Z", + "shell.execute_reply": "2023-11-15T21:43:09.538969Z" }, "tags": [] }, @@ -33,10 +33,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:18.951419Z", - "iopub.status.busy": "2023-11-15T16:37:18.951247Z", - "iopub.status.idle": "2023-11-15T16:37:19.556378Z", - "shell.execute_reply": "2023-11-15T16:37:19.555776Z" + "iopub.execute_input": "2023-11-15T21:43:09.541936Z", + "iopub.status.busy": "2023-11-15T21:43:09.541582Z", + "iopub.status.idle": "2023-11-15T21:43:10.151231Z", + "shell.execute_reply": "2023-11-15T21:43:10.150618Z" }, "tags": [] }, @@ -60,10 +60,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.559107Z", - "iopub.status.busy": "2023-11-15T16:37:19.558835Z", - "iopub.status.idle": "2023-11-15T16:37:19.600498Z", - "shell.execute_reply": "2023-11-15T16:37:19.599974Z" + "iopub.execute_input": "2023-11-15T21:43:10.154048Z", + "iopub.status.busy": "2023-11-15T21:43:10.153749Z", + "iopub.status.idle": "2023-11-15T21:43:10.195387Z", + "shell.execute_reply": "2023-11-15T21:43:10.194808Z" }, "tags": [] }, @@ -84,10 +84,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.602729Z", - "iopub.status.busy": "2023-11-15T16:37:19.602548Z", - "iopub.status.idle": "2023-11-15T16:37:19.617586Z", - "shell.execute_reply": "2023-11-15T16:37:19.617068Z" + "iopub.execute_input": "2023-11-15T21:43:10.197677Z", + "iopub.status.busy": "2023-11-15T21:43:10.197492Z", + "iopub.status.idle": "2023-11-15T21:43:10.212730Z", + "shell.execute_reply": "2023-11-15T21:43:10.212179Z" }, "tags": [] }, @@ -124,10 +124,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.646745Z", - "iopub.status.busy": "2023-11-15T16:37:19.646561Z", - "iopub.status.idle": "2023-11-15T16:37:19.659127Z", - "shell.execute_reply": "2023-11-15T16:37:19.658678Z" + "iopub.execute_input": "2023-11-15T21:43:10.241434Z", + "iopub.status.busy": "2023-11-15T21:43:10.241180Z", + "iopub.status.idle": "2023-11-15T21:43:10.254425Z", + "shell.execute_reply": "2023-11-15T21:43:10.253881Z" }, "tags": [] }, @@ -161,10 +161,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.661310Z", - "iopub.status.busy": "2023-11-15T16:37:19.660960Z", - "iopub.status.idle": "2023-11-15T16:37:19.674085Z", - "shell.execute_reply": "2023-11-15T16:37:19.673632Z" + "iopub.execute_input": "2023-11-15T21:43:10.256593Z", + "iopub.status.busy": "2023-11-15T21:43:10.256421Z", + "iopub.status.idle": "2023-11-15T21:43:10.269166Z", + "shell.execute_reply": "2023-11-15T21:43:10.268687Z" }, "tags": [] }, @@ -200,10 +200,10 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.676188Z", - "iopub.status.busy": "2023-11-15T16:37:19.675969Z", - "iopub.status.idle": "2023-11-15T16:37:19.688751Z", - "shell.execute_reply": "2023-11-15T16:37:19.688206Z" + "iopub.execute_input": "2023-11-15T21:43:10.271309Z", + "iopub.status.busy": "2023-11-15T21:43:10.271134Z", + "iopub.status.idle": "2023-11-15T21:43:10.283493Z", + "shell.execute_reply": "2023-11-15T21:43:10.282939Z" } }, "outputs": [ @@ -235,10 +235,10 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.691128Z", - "iopub.status.busy": "2023-11-15T16:37:19.690805Z", - "iopub.status.idle": "2023-11-15T16:37:19.704407Z", - "shell.execute_reply": "2023-11-15T16:37:19.703854Z" + "iopub.execute_input": "2023-11-15T21:43:10.285544Z", + "iopub.status.busy": "2023-11-15T21:43:10.285368Z", + "iopub.status.idle": "2023-11-15T21:43:10.297953Z", + "shell.execute_reply": "2023-11-15T21:43:10.297402Z" }, "tags": [] }, @@ -266,10 +266,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.706728Z", - "iopub.status.busy": "2023-11-15T16:37:19.706355Z", - "iopub.status.idle": "2023-11-15T16:37:19.719689Z", - "shell.execute_reply": "2023-11-15T16:37:19.719144Z" + "iopub.execute_input": "2023-11-15T21:43:10.300344Z", + "iopub.status.busy": "2023-11-15T21:43:10.299929Z", + "iopub.status.idle": "2023-11-15T21:43:10.312138Z", + "shell.execute_reply": "2023-11-15T21:43:10.311598Z" }, "tags": [] }, @@ -301,10 +301,10 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.722164Z", - "iopub.status.busy": "2023-11-15T16:37:19.721843Z", - "iopub.status.idle": "2023-11-15T16:37:19.735381Z", - "shell.execute_reply": "2023-11-15T16:37:19.734817Z" + "iopub.execute_input": "2023-11-15T21:43:10.314399Z", + "iopub.status.busy": "2023-11-15T21:43:10.313969Z", + "iopub.status.idle": "2023-11-15T21:43:10.326269Z", + "shell.execute_reply": "2023-11-15T21:43:10.325738Z" }, "tags": [] }, @@ -336,10 +336,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.737764Z", - "iopub.status.busy": "2023-11-15T16:37:19.737318Z", - "iopub.status.idle": "2023-11-15T16:37:19.749255Z", - "shell.execute_reply": "2023-11-15T16:37:19.748820Z" + "iopub.execute_input": "2023-11-15T21:43:10.328675Z", + "iopub.status.busy": "2023-11-15T21:43:10.328349Z", + "iopub.status.idle": "2023-11-15T21:43:10.339117Z", + "shell.execute_reply": "2023-11-15T21:43:10.338541Z" }, "tags": [] }, @@ -360,10 +360,10 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.751360Z", - "iopub.status.busy": "2023-11-15T16:37:19.751187Z", - "iopub.status.idle": "2023-11-15T16:37:19.764402Z", - "shell.execute_reply": "2023-11-15T16:37:19.763831Z" + "iopub.execute_input": "2023-11-15T21:43:10.341291Z", + "iopub.status.busy": "2023-11-15T21:43:10.341128Z", + "iopub.status.idle": "2023-11-15T21:43:10.353293Z", + "shell.execute_reply": "2023-11-15T21:43:10.352831Z" }, "tags": [] }, @@ -390,10 +390,10 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.766523Z", - "iopub.status.busy": "2023-11-15T16:37:19.766196Z", - "iopub.status.idle": "2023-11-15T16:37:19.778885Z", - "shell.execute_reply": "2023-11-15T16:37:19.778347Z" + "iopub.execute_input": "2023-11-15T21:43:10.355260Z", + "iopub.status.busy": "2023-11-15T21:43:10.355100Z", + "iopub.status.idle": "2023-11-15T21:43:10.367187Z", + "shell.execute_reply": "2023-11-15T21:43:10.366629Z" }, "tags": [] }, @@ -418,10 +418,10 @@ "execution_count": 14, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.781160Z", - "iopub.status.busy": "2023-11-15T16:37:19.780832Z", - "iopub.status.idle": "2023-11-15T16:37:19.792044Z", - "shell.execute_reply": "2023-11-15T16:37:19.791509Z" + "iopub.execute_input": "2023-11-15T21:43:10.369159Z", + "iopub.status.busy": "2023-11-15T21:43:10.368985Z", + "iopub.status.idle": "2023-11-15T21:43:10.379579Z", + "shell.execute_reply": "2023-11-15T21:43:10.379142Z" }, "tags": [] }, @@ -435,10 +435,10 @@ "execution_count": 15, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.794265Z", - "iopub.status.busy": "2023-11-15T16:37:19.793940Z", - "iopub.status.idle": "2023-11-15T16:37:19.806284Z", - "shell.execute_reply": "2023-11-15T16:37:19.805740Z" + "iopub.execute_input": "2023-11-15T21:43:10.381479Z", + "iopub.status.busy": "2023-11-15T21:43:10.381306Z", + "iopub.status.idle": "2023-11-15T21:43:10.393021Z", + "shell.execute_reply": "2023-11-15T21:43:10.392478Z" }, "tags": [] }, @@ -474,10 +474,10 @@ "execution_count": 16, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:37:19.808833Z", - "iopub.status.busy": "2023-11-15T16:37:19.808298Z", - "iopub.status.idle": "2023-11-15T16:37:19.819825Z", - "shell.execute_reply": "2023-11-15T16:37:19.819369Z" + "iopub.execute_input": "2023-11-15T21:43:10.395371Z", + "iopub.status.busy": "2023-11-15T21:43:10.395046Z", + "iopub.status.idle": "2023-11-15T21:43:10.406077Z", + "shell.execute_reply": "2023-11-15T21:43:10.405536Z" }, "tags": [] }, diff --git a/examples/write_examples/index.html b/examples/write_examples/index.html index ffc97f6..247ad58 100644 --- a/examples/write_examples/index.html +++ b/examples/write_examples/index.html @@ -3050,7 +3050,7 @@

Lucretia

diff --git a/examples/write_examples/write_examples.ipynb b/examples/write_examples/write_examples.ipynb index 57034ec..45b8566 100644 --- a/examples/write_examples/write_examples.ipynb +++ b/examples/write_examples/write_examples.ipynb @@ -12,10 +12,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.041946Z", - "iopub.status.busy": "2023-11-15T16:36:20.041765Z", - "iopub.status.idle": "2023-11-15T16:36:20.055517Z", - "shell.execute_reply": "2023-11-15T16:36:20.054985Z" + "iopub.execute_input": "2023-11-15T21:42:08.345577Z", + "iopub.status.busy": "2023-11-15T21:42:08.345400Z", + "iopub.status.idle": "2023-11-15T21:42:08.359170Z", + "shell.execute_reply": "2023-11-15T21:42:08.358606Z" }, "tags": [] }, @@ -31,10 +31,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.057880Z", - "iopub.status.busy": "2023-11-15T16:36:20.057498Z", - "iopub.status.idle": "2023-11-15T16:36:20.672559Z", - "shell.execute_reply": "2023-11-15T16:36:20.671940Z" + "iopub.execute_input": "2023-11-15T21:42:08.361499Z", + "iopub.status.busy": "2023-11-15T21:42:08.361083Z", + "iopub.status.idle": "2023-11-15T21:42:08.984340Z", + "shell.execute_reply": "2023-11-15T21:42:08.983694Z" }, "tags": [] }, @@ -50,10 +50,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.675282Z", - "iopub.status.busy": "2023-11-15T16:36:20.675014Z", - "iopub.status.idle": "2023-11-15T16:36:20.692081Z", - "shell.execute_reply": "2023-11-15T16:36:20.691648Z" + "iopub.execute_input": "2023-11-15T21:42:08.987322Z", + "iopub.status.busy": "2023-11-15T21:42:08.986885Z", + "iopub.status.idle": "2023-11-15T21:42:09.004187Z", + "shell.execute_reply": "2023-11-15T21:42:09.003590Z" }, "tags": [] }, @@ -87,10 +87,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.694467Z", - "iopub.status.busy": "2023-11-15T16:36:20.694102Z", - "iopub.status.idle": "2023-11-15T16:36:20.711556Z", - "shell.execute_reply": "2023-11-15T16:36:20.711108Z" + "iopub.execute_input": "2023-11-15T21:42:09.006978Z", + "iopub.status.busy": "2023-11-15T21:42:09.006786Z", + "iopub.status.idle": "2023-11-15T21:42:09.025222Z", + "shell.execute_reply": "2023-11-15T21:42:09.024702Z" }, "tags": [] }, @@ -111,10 +111,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.713817Z", - "iopub.status.busy": "2023-11-15T16:36:20.713441Z", - "iopub.status.idle": "2023-11-15T16:36:20.731122Z", - "shell.execute_reply": "2023-11-15T16:36:20.730594Z" + "iopub.execute_input": "2023-11-15T21:42:09.027857Z", + "iopub.status.busy": "2023-11-15T21:42:09.027677Z", + "iopub.status.idle": "2023-11-15T21:42:09.045756Z", + "shell.execute_reply": "2023-11-15T21:42:09.045266Z" }, "tags": [] }, @@ -137,10 +137,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.733647Z", - "iopub.status.busy": "2023-11-15T16:36:20.733309Z", - "iopub.status.idle": "2023-11-15T16:36:20.748489Z", - "shell.execute_reply": "2023-11-15T16:36:20.748024Z" + "iopub.execute_input": "2023-11-15T21:42:09.048336Z", + "iopub.status.busy": "2023-11-15T21:42:09.048143Z", + "iopub.status.idle": "2023-11-15T21:42:09.064595Z", + "shell.execute_reply": "2023-11-15T21:42:09.064095Z" }, "tags": [] }, @@ -161,10 +161,10 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.750730Z", - "iopub.status.busy": "2023-11-15T16:36:20.750379Z", - "iopub.status.idle": "2023-11-15T16:36:20.765515Z", - "shell.execute_reply": "2023-11-15T16:36:20.764947Z" + "iopub.execute_input": "2023-11-15T21:42:09.067054Z", + "iopub.status.busy": "2023-11-15T21:42:09.066872Z", + "iopub.status.idle": "2023-11-15T21:42:09.083135Z", + "shell.execute_reply": "2023-11-15T21:42:09.082543Z" }, "tags": [] }, @@ -196,10 +196,10 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.795330Z", - "iopub.status.busy": "2023-11-15T16:36:20.794991Z", - "iopub.status.idle": "2023-11-15T16:36:20.875104Z", - "shell.execute_reply": "2023-11-15T16:36:20.874470Z" + "iopub.execute_input": "2023-11-15T21:42:09.113366Z", + "iopub.status.busy": "2023-11-15T21:42:09.112815Z", + "iopub.status.idle": "2023-11-15T21:42:09.194512Z", + "shell.execute_reply": "2023-11-15T21:42:09.193838Z" } }, "outputs": [], @@ -212,10 +212,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:20.877613Z", - "iopub.status.busy": "2023-11-15T16:36:20.877167Z", - "iopub.status.idle": "2023-11-15T16:36:21.016253Z", - "shell.execute_reply": "2023-11-15T16:36:21.015599Z" + "iopub.execute_input": "2023-11-15T21:42:09.197471Z", + "iopub.status.busy": "2023-11-15T21:42:09.197244Z", + "iopub.status.idle": "2023-11-15T21:42:09.338418Z", + "shell.execute_reply": "2023-11-15T21:42:09.337647Z" } }, "outputs": [ @@ -252,10 +252,10 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.018948Z", - "iopub.status.busy": "2023-11-15T16:36:21.018582Z", - "iopub.status.idle": "2023-11-15T16:36:21.046890Z", - "shell.execute_reply": "2023-11-15T16:36:21.046464Z" + "iopub.execute_input": "2023-11-15T21:42:09.341959Z", + "iopub.status.busy": "2023-11-15T21:42:09.341487Z", + "iopub.status.idle": "2023-11-15T21:42:09.371864Z", + "shell.execute_reply": "2023-11-15T21:42:09.371351Z" } }, "outputs": [], @@ -279,10 +279,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.049139Z", - "iopub.status.busy": "2023-11-15T16:36:21.048821Z", - "iopub.status.idle": "2023-11-15T16:36:21.098730Z", - "shell.execute_reply": "2023-11-15T16:36:21.098169Z" + "iopub.execute_input": "2023-11-15T21:42:09.374607Z", + "iopub.status.busy": "2023-11-15T21:42:09.374396Z", + "iopub.status.idle": "2023-11-15T21:42:09.424872Z", + "shell.execute_reply": "2023-11-15T21:42:09.424206Z" } }, "outputs": [], @@ -295,10 +295,10 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.101001Z", - "iopub.status.busy": "2023-11-15T16:36:21.100652Z", - "iopub.status.idle": "2023-11-15T16:36:21.237127Z", - "shell.execute_reply": "2023-11-15T16:36:21.236542Z" + "iopub.execute_input": "2023-11-15T21:42:09.427948Z", + "iopub.status.busy": "2023-11-15T21:42:09.427545Z", + "iopub.status.idle": "2023-11-15T21:42:09.568275Z", + "shell.execute_reply": "2023-11-15T21:42:09.567647Z" } }, "outputs": [ @@ -335,10 +335,10 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.239393Z", - "iopub.status.busy": "2023-11-15T16:36:21.239210Z", - "iopub.status.idle": "2023-11-15T16:36:21.255237Z", - "shell.execute_reply": "2023-11-15T16:36:21.254693Z" + "iopub.execute_input": "2023-11-15T21:42:09.571104Z", + "iopub.status.busy": "2023-11-15T21:42:09.570910Z", + "iopub.status.idle": "2023-11-15T21:42:09.588344Z", + "shell.execute_reply": "2023-11-15T21:42:09.587800Z" } }, "outputs": [ @@ -385,10 +385,10 @@ "execution_count": 14, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.257414Z", - "iopub.status.busy": "2023-11-15T16:36:21.257082Z", - "iopub.status.idle": "2023-11-15T16:36:21.272227Z", - "shell.execute_reply": "2023-11-15T16:36:21.271809Z" + "iopub.execute_input": "2023-11-15T21:42:09.590880Z", + "iopub.status.busy": "2023-11-15T21:42:09.590416Z", + "iopub.status.idle": "2023-11-15T21:42:09.606699Z", + "shell.execute_reply": "2023-11-15T21:42:09.606177Z" } }, "outputs": [], @@ -408,10 +408,10 @@ "execution_count": 15, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.274483Z", - "iopub.status.busy": "2023-11-15T16:36:21.274154Z", - "iopub.status.idle": "2023-11-15T16:36:21.318978Z", - "shell.execute_reply": "2023-11-15T16:36:21.318381Z" + "iopub.execute_input": "2023-11-15T21:42:09.609400Z", + "iopub.status.busy": "2023-11-15T21:42:09.609056Z", + "iopub.status.idle": "2023-11-15T21:42:09.655220Z", + "shell.execute_reply": "2023-11-15T21:42:09.654611Z" } }, "outputs": [ @@ -432,10 +432,10 @@ "execution_count": 16, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.320977Z", - "iopub.status.busy": "2023-11-15T16:36:21.320802Z", - "iopub.status.idle": "2023-11-15T16:36:21.456623Z", - "shell.execute_reply": "2023-11-15T16:36:21.456114Z" + "iopub.execute_input": "2023-11-15T21:42:09.657443Z", + "iopub.status.busy": "2023-11-15T21:42:09.657255Z", + "iopub.status.idle": "2023-11-15T21:42:09.797205Z", + "shell.execute_reply": "2023-11-15T21:42:09.796580Z" } }, "outputs": [ @@ -482,10 +482,10 @@ "execution_count": 17, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.459042Z", - "iopub.status.busy": "2023-11-15T16:36:21.458667Z", - "iopub.status.idle": "2023-11-15T16:36:21.508294Z", - "shell.execute_reply": "2023-11-15T16:36:21.507706Z" + "iopub.execute_input": "2023-11-15T21:42:09.800136Z", + "iopub.status.busy": "2023-11-15T21:42:09.799821Z", + "iopub.status.idle": "2023-11-15T21:42:09.852858Z", + "shell.execute_reply": "2023-11-15T21:42:09.852288Z" } }, "outputs": [ @@ -506,10 +506,10 @@ "execution_count": 18, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.510599Z", - "iopub.status.busy": "2023-11-15T16:36:21.510252Z", - "iopub.status.idle": "2023-11-15T16:36:21.648072Z", - "shell.execute_reply": "2023-11-15T16:36:21.647343Z" + "iopub.execute_input": "2023-11-15T21:42:09.855227Z", + "iopub.status.busy": "2023-11-15T21:42:09.855025Z", + "iopub.status.idle": "2023-11-15T21:42:09.995101Z", + "shell.execute_reply": "2023-11-15T21:42:09.994331Z" } }, "outputs": [ @@ -553,10 +553,10 @@ "execution_count": 19, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.650822Z", - "iopub.status.busy": "2023-11-15T16:36:21.650628Z", - "iopub.status.idle": "2023-11-15T16:36:21.750495Z", - "shell.execute_reply": "2023-11-15T16:36:21.749892Z" + "iopub.execute_input": "2023-11-15T21:42:09.998092Z", + "iopub.status.busy": "2023-11-15T21:42:09.997888Z", + "iopub.status.idle": "2023-11-15T21:42:10.102209Z", + "shell.execute_reply": "2023-11-15T21:42:10.101640Z" }, "tags": [] }, @@ -587,10 +587,10 @@ "execution_count": 20, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.753058Z", - "iopub.status.busy": "2023-11-15T16:36:21.752612Z", - "iopub.status.idle": "2023-11-15T16:36:21.765648Z", - "shell.execute_reply": "2023-11-15T16:36:21.765177Z" + "iopub.execute_input": "2023-11-15T21:42:10.104634Z", + "iopub.status.busy": "2023-11-15T21:42:10.104433Z", + "iopub.status.idle": "2023-11-15T21:42:10.118280Z", + "shell.execute_reply": "2023-11-15T21:42:10.117768Z" }, "tags": [] }, @@ -726,10 +726,10 @@ "execution_count": 21, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.767836Z", - "iopub.status.busy": "2023-11-15T16:36:21.767653Z", - "iopub.status.idle": "2023-11-15T16:36:21.783604Z", - "shell.execute_reply": "2023-11-15T16:36:21.783133Z" + "iopub.execute_input": "2023-11-15T21:42:10.120499Z", + "iopub.status.busy": "2023-11-15T21:42:10.120314Z", + "iopub.status.idle": "2023-11-15T21:42:10.137952Z", + "shell.execute_reply": "2023-11-15T21:42:10.137374Z" }, "tags": [] }, @@ -775,10 +775,10 @@ "execution_count": 22, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.785722Z", - "iopub.status.busy": "2023-11-15T16:36:21.785525Z", - "iopub.status.idle": "2023-11-15T16:36:21.800191Z", - "shell.execute_reply": "2023-11-15T16:36:21.799656Z" + "iopub.execute_input": "2023-11-15T21:42:10.140567Z", + "iopub.status.busy": "2023-11-15T21:42:10.140138Z", + "iopub.status.idle": "2023-11-15T21:42:10.156307Z", + "shell.execute_reply": "2023-11-15T21:42:10.155743Z" }, "tags": [] }, @@ -807,10 +807,10 @@ "execution_count": 23, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.802354Z", - "iopub.status.busy": "2023-11-15T16:36:21.802184Z", - "iopub.status.idle": "2023-11-15T16:36:21.814733Z", - "shell.execute_reply": "2023-11-15T16:36:21.814236Z" + "iopub.execute_input": "2023-11-15T21:42:10.158866Z", + "iopub.status.busy": "2023-11-15T21:42:10.158392Z", + "iopub.status.idle": "2023-11-15T21:42:10.172518Z", + "shell.execute_reply": "2023-11-15T21:42:10.171957Z" }, "tags": [] }, @@ -846,10 +846,10 @@ "execution_count": 24, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.816773Z", - "iopub.status.busy": "2023-11-15T16:36:21.816599Z", - "iopub.status.idle": "2023-11-15T16:36:21.876338Z", - "shell.execute_reply": "2023-11-15T16:36:21.875788Z" + "iopub.execute_input": "2023-11-15T21:42:10.174893Z", + "iopub.status.busy": "2023-11-15T21:42:10.174715Z", + "iopub.status.idle": "2023-11-15T21:42:10.234810Z", + "shell.execute_reply": "2023-11-15T21:42:10.234155Z" } }, "outputs": [ @@ -871,10 +871,10 @@ "execution_count": 25, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.878526Z", - "iopub.status.busy": "2023-11-15T16:36:21.878341Z", - "iopub.status.idle": "2023-11-15T16:36:21.891169Z", - "shell.execute_reply": "2023-11-15T16:36:21.890705Z" + "iopub.execute_input": "2023-11-15T21:42:10.237355Z", + "iopub.status.busy": "2023-11-15T21:42:10.236910Z", + "iopub.status.idle": "2023-11-15T21:42:10.250478Z", + "shell.execute_reply": "2023-11-15T21:42:10.249988Z" } }, "outputs": [], @@ -888,10 +888,10 @@ "execution_count": 26, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.893224Z", - "iopub.status.busy": "2023-11-15T16:36:21.892912Z", - "iopub.status.idle": "2023-11-15T16:36:21.903915Z", - "shell.execute_reply": "2023-11-15T16:36:21.903362Z" + "iopub.execute_input": "2023-11-15T21:42:10.252867Z", + "iopub.status.busy": "2023-11-15T21:42:10.252684Z", + "iopub.status.idle": "2023-11-15T21:42:10.264714Z", + "shell.execute_reply": "2023-11-15T21:42:10.264280Z" } }, "outputs": [], @@ -913,10 +913,10 @@ "execution_count": 27, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.906119Z", - "iopub.status.busy": "2023-11-15T16:36:21.905795Z", - "iopub.status.idle": "2023-11-15T16:36:21.917018Z", - "shell.execute_reply": "2023-11-15T16:36:21.916500Z" + "iopub.execute_input": "2023-11-15T21:42:10.266826Z", + "iopub.status.busy": "2023-11-15T21:42:10.266663Z", + "iopub.status.idle": "2023-11-15T21:42:10.278154Z", + "shell.execute_reply": "2023-11-15T21:42:10.277720Z" }, "tags": [] }, @@ -937,10 +937,10 @@ "execution_count": 28, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.919695Z", - "iopub.status.busy": "2023-11-15T16:36:21.919181Z", - "iopub.status.idle": "2023-11-15T16:36:21.977098Z", - "shell.execute_reply": "2023-11-15T16:36:21.976551Z" + "iopub.execute_input": "2023-11-15T21:42:10.280487Z", + "iopub.status.busy": "2023-11-15T21:42:10.280152Z", + "iopub.status.idle": "2023-11-15T21:42:10.337352Z", + "shell.execute_reply": "2023-11-15T21:42:10.336705Z" }, "tags": [] }, @@ -968,10 +968,10 @@ "execution_count": 29, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:21.979630Z", - "iopub.status.busy": "2023-11-15T16:36:21.979088Z", - "iopub.status.idle": "2023-11-15T16:36:22.118086Z", - "shell.execute_reply": "2023-11-15T16:36:22.117328Z" + "iopub.execute_input": "2023-11-15T21:42:10.339914Z", + "iopub.status.busy": "2023-11-15T21:42:10.339560Z", + "iopub.status.idle": "2023-11-15T21:42:10.479794Z", + "shell.execute_reply": "2023-11-15T21:42:10.479077Z" }, "tags": [] }, @@ -1011,10 +1011,10 @@ "execution_count": 30, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.121325Z", - "iopub.status.busy": "2023-11-15T16:36:22.120799Z", - "iopub.status.idle": "2023-11-15T16:36:22.136216Z", - "shell.execute_reply": "2023-11-15T16:36:22.135747Z" + "iopub.execute_input": "2023-11-15T21:42:10.482968Z", + "iopub.status.busy": "2023-11-15T21:42:10.482552Z", + "iopub.status.idle": "2023-11-15T21:42:10.498297Z", + "shell.execute_reply": "2023-11-15T21:42:10.497811Z" }, "tags": [] }, @@ -1028,10 +1028,10 @@ "execution_count": 31, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.138623Z", - "iopub.status.busy": "2023-11-15T16:36:22.138190Z", - "iopub.status.idle": "2023-11-15T16:36:22.170174Z", - "shell.execute_reply": "2023-11-15T16:36:22.169562Z" + "iopub.execute_input": "2023-11-15T21:42:10.500739Z", + "iopub.status.busy": "2023-11-15T21:42:10.500563Z", + "iopub.status.idle": "2023-11-15T21:42:10.532173Z", + "shell.execute_reply": "2023-11-15T21:42:10.531668Z" }, "tags": [] }, @@ -1064,10 +1064,10 @@ "execution_count": 32, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.172487Z", - "iopub.status.busy": "2023-11-15T16:36:22.172200Z", - "iopub.status.idle": "2023-11-15T16:36:22.311615Z", - "shell.execute_reply": "2023-11-15T16:36:22.311011Z" + "iopub.execute_input": "2023-11-15T21:42:10.534501Z", + "iopub.status.busy": "2023-11-15T21:42:10.534102Z", + "iopub.status.idle": "2023-11-15T21:42:10.673138Z", + "shell.execute_reply": "2023-11-15T21:42:10.672510Z" }, "tags": [] }, @@ -1115,10 +1115,10 @@ "execution_count": 33, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.314359Z", - "iopub.status.busy": "2023-11-15T16:36:22.314168Z", - "iopub.status.idle": "2023-11-15T16:36:22.331470Z", - "shell.execute_reply": "2023-11-15T16:36:22.330973Z" + "iopub.execute_input": "2023-11-15T21:42:10.676245Z", + "iopub.status.busy": "2023-11-15T21:42:10.676009Z", + "iopub.status.idle": "2023-11-15T21:42:10.693329Z", + "shell.execute_reply": "2023-11-15T21:42:10.692749Z" }, "tags": [] }, @@ -1147,10 +1147,10 @@ "execution_count": 34, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.333995Z", - "iopub.status.busy": "2023-11-15T16:36:22.333541Z", - "iopub.status.idle": "2023-11-15T16:36:22.350339Z", - "shell.execute_reply": "2023-11-15T16:36:22.349785Z" + "iopub.execute_input": "2023-11-15T21:42:10.695759Z", + "iopub.status.busy": "2023-11-15T21:42:10.695331Z", + "iopub.status.idle": "2023-11-15T21:42:10.712896Z", + "shell.execute_reply": "2023-11-15T21:42:10.712362Z" }, "tags": [] }, @@ -1166,7 +1166,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 34, @@ -1192,10 +1192,10 @@ "execution_count": 35, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.352501Z", - "iopub.status.busy": "2023-11-15T16:36:22.352314Z", - "iopub.status.idle": "2023-11-15T16:36:22.365363Z", - "shell.execute_reply": "2023-11-15T16:36:22.364835Z" + "iopub.execute_input": "2023-11-15T21:42:10.715236Z", + "iopub.status.busy": "2023-11-15T21:42:10.714943Z", + "iopub.status.idle": "2023-11-15T21:42:10.729722Z", + "shell.execute_reply": "2023-11-15T21:42:10.729130Z" }, "tags": [] }, @@ -1229,10 +1229,10 @@ "execution_count": 36, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.367707Z", - "iopub.status.busy": "2023-11-15T16:36:22.367385Z", - "iopub.status.idle": "2023-11-15T16:36:22.412140Z", - "shell.execute_reply": "2023-11-15T16:36:22.411647Z" + "iopub.execute_input": "2023-11-15T21:42:10.732259Z", + "iopub.status.busy": "2023-11-15T21:42:10.731891Z", + "iopub.status.idle": "2023-11-15T21:42:10.778476Z", + "shell.execute_reply": "2023-11-15T21:42:10.777901Z" }, "tags": [] }, @@ -1248,10 +1248,10 @@ "execution_count": 37, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.414277Z", - "iopub.status.busy": "2023-11-15T16:36:22.414095Z", - "iopub.status.idle": "2023-11-15T16:36:22.552638Z", - "shell.execute_reply": "2023-11-15T16:36:22.552044Z" + "iopub.execute_input": "2023-11-15T21:42:10.781403Z", + "iopub.status.busy": "2023-11-15T21:42:10.780933Z", + "iopub.status.idle": "2023-11-15T21:42:10.920963Z", + "shell.execute_reply": "2023-11-15T21:42:10.920337Z" }, "tags": [] }, @@ -1289,10 +1289,10 @@ "execution_count": 38, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.555240Z", - "iopub.status.busy": "2023-11-15T16:36:22.555054Z", - "iopub.status.idle": "2023-11-15T16:36:22.602866Z", - "shell.execute_reply": "2023-11-15T16:36:22.602382Z" + "iopub.execute_input": "2023-11-15T21:42:10.924247Z", + "iopub.status.busy": "2023-11-15T21:42:10.923749Z", + "iopub.status.idle": "2023-11-15T21:42:10.972175Z", + "shell.execute_reply": "2023-11-15T21:42:10.971565Z" }, "tags": [] }, @@ -1307,10 +1307,10 @@ "execution_count": 39, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.605098Z", - "iopub.status.busy": "2023-11-15T16:36:22.604741Z", - "iopub.status.idle": "2023-11-15T16:36:22.742368Z", - "shell.execute_reply": "2023-11-15T16:36:22.741811Z" + "iopub.execute_input": "2023-11-15T21:42:10.974751Z", + "iopub.status.busy": "2023-11-15T21:42:10.974378Z", + "iopub.status.idle": "2023-11-15T21:42:11.113083Z", + "shell.execute_reply": "2023-11-15T21:42:11.112455Z" }, "tags": [] }, @@ -1349,10 +1349,10 @@ "execution_count": 40, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.744934Z", - "iopub.status.busy": "2023-11-15T16:36:22.744538Z", - "iopub.status.idle": "2023-11-15T16:36:22.806827Z", - "shell.execute_reply": "2023-11-15T16:36:22.806356Z" + "iopub.execute_input": "2023-11-15T21:42:11.116185Z", + "iopub.status.busy": "2023-11-15T21:42:11.115811Z", + "iopub.status.idle": "2023-11-15T21:42:11.178870Z", + "shell.execute_reply": "2023-11-15T21:42:11.178242Z" } }, "outputs": [], @@ -1372,10 +1372,10 @@ "execution_count": 41, "metadata": { "execution": { - "iopub.execute_input": "2023-11-15T16:36:22.809299Z", - "iopub.status.busy": "2023-11-15T16:36:22.808950Z", - "iopub.status.idle": "2023-11-15T16:36:22.823594Z", - "shell.execute_reply": "2023-11-15T16:36:22.823159Z" + "iopub.execute_input": "2023-11-15T21:42:11.181681Z", + "iopub.status.busy": "2023-11-15T21:42:11.181193Z", + "iopub.status.idle": "2023-11-15T21:42:11.197114Z", + "shell.execute_reply": "2023-11-15T21:42:11.196574Z" }, "tags": [] }, diff --git a/search/search_index.json b/search/search_index.json index fe01472..26e7fb1 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"openPMD-beamphysics","text":"

Tools for analyzing and viewing particle data in the openPMD standard, extension beamphysics defined in: beamphysics extension

"},{"location":"#python-classes","title":"Python classes","text":"

This package provides two feature-rich classes for handling openPMD-beamphysics standard data:

  • ParticleGroup for handling particle data

  • FieldMesh - for handling external field mesh data

For usage see the examples.

"},{"location":"#installation","title":"Installation","text":"

Installing openpmd-beamphysics from the conda-forge channel can be achieved by adding conda-forge to your channels with:

conda config --add channels conda-forge\n

Once the conda-forge channel has been enabled, openpmd-beamphysics can be installed with:

conda install openpmd-beamphysics\n

It is possible to list all of the versions of openpmd-beamphysics available on your platform with:

conda search openpmd-beamphysics --channel conda-forge\n
"},{"location":"api/fields/","title":"Fields","text":"

Class for openPMD External Field Mesh data.

Initialized on on openPMD beamphysics particle group:

  • h5: open h5 handle, or str that is a file
  • data: raw data

The required data is stored in ._data, and consists of dicts:

  • 'attrs'
  • 'components'

Component data is always 3D.

Initialization from openPMD-beamphysics HDF5 file:

  • FieldMesh('file.h5')

Initialization from a data dict:

  • FieldMesh(data=data)

Derived properties:

  • .r, .theta, .z
  • .Br, .Btheta, .Bz
  • .Er, .Etheta, .Ez
  • .E, .B

  • .phase

  • .scale
  • .factor

  • .harmonic

  • .frequency

  • .shape

  • .geometry
  • .mins, .maxs, .deltas
  • .meshgrid
  • .dr, .dtheta, .dz

Booleans:

  • .is_pure_electric
  • .is_pure_magnetic
  • .is_static

Units and labels

  • .units
  • .axis_labels

Plotting:

  • .plot
  • .plot_onaxis

Writers

  • .write
  • .write_astra_1d
  • .write_astra_3d
  • .to_cylindrical
  • .to_astra_1d
  • .to_impact_solrf
  • .write_gpt
  • .write_superfish

Constructors (class methods):

  • .from_ansys_ascii_3d
  • .from_astra_3d
  • .from_superfish
  • .from_onaxis
  • .expand_onaxis
Source code in pmd_beamphysics/fields/fieldmesh.py
class FieldMesh:\n    \"\"\"\n    Class for openPMD External Field Mesh data.\n\n    Initialized on on openPMD beamphysics particle group:\n\n    - **h5**: open h5 handle, or str that is a file\n    - **data**: raw data\n\n    The required data is stored in ._data, and consists of dicts:\n\n    - `'attrs'`\n    - `'components'`\n\n    Component data is always 3D.\n\n    Initialization from openPMD-beamphysics HDF5 file:\n\n    - `FieldMesh('file.h5')`\n\n    Initialization from a data dict:\n\n    - `FieldMesh(data=data)`\n\n    Derived properties:\n\n    - `.r`, `.theta`, `.z`\n    - `.Br`, `.Btheta`, `.Bz`\n    - `.Er`, `.Etheta`, `.Ez`\n    - `.E`, `.B`\n\n    - `.phase`\n    - `.scale`\n    - `.factor`\n\n    - `.harmonic`\n    - `.frequency`\n\n    - `.shape`\n    - `.geometry`\n    - `.mins`, `.maxs`, `.deltas`\n    - `.meshgrid`\n    - `.dr`, `.dtheta`, `.dz`\n\n    Booleans:\n\n    - `.is_pure_electric`\n    - `.is_pure_magnetic`\n    - `.is_static`\n\n    Units and labels\n\n    - `.units`\n    - `.axis_labels`\n\n    Plotting:\n\n    - `.plot`\n    - `.plot_onaxis`\n\n    Writers\n\n    - `.write`\n    - `.write_astra_1d`\n    - `.write_astra_3d`\n    - `.to_cylindrical`\n    - `.to_astra_1d`\n    - `.to_impact_solrf`\n    - `.write_gpt`\n    - `.write_superfish`\n\n    Constructors (class methods):\n\n    - `.from_ansys_ascii_3d`\n    - `.from_astra_3d`\n    - `.from_superfish`\n    - `.from_onaxis`\n    - `.expand_onaxis`\n\n\n\n\n    \"\"\"\n    def __init__(self, h5=None, data=None):\n\n        if h5:\n            # Allow filename\n            if isinstance(h5, str):\n                fname = os.path.expandvars(os.path.expanduser(h5))\n                assert os.path.exists(fname), f'File does not exist: {fname}'\n\n                with File(fname, 'r') as hh5:\n                    fp = field_paths(hh5)\n                    assert len(fp) == 1, f'Number of field paths in {h5}: {len(fp)}'\n                    data = load_field_data_h5(hh5[fp[0]])\n\n            else:\n                data = load_field_data_h5(h5)\n        else:\n            data = load_field_data_dict(data)\n\n        # Internal data\n        self._data = data\n\n        # Aliases (Do not set these! Set via slicing: .Bz[:] = 0\n        #for k in self.components:\n        #    alias = component_alias[k]\n        #    self.__dict__[alias] =  self.components[k]\n\n\n    # Direct access to internal data        \n    @property\n    def attrs(self):\n        return self._data['attrs']\n\n    @property\n    def components(self):\n        return self._data['components']  \n\n    @property\n    def data(self):\n        return self._data\n\n\n    # Conveniences \n    @property\n    def shape(self):\n        return tuple(self.attrs['gridSize'])\n\n    @property\n    def geometry(self):\n        return self.attrs['gridGeometry']\n\n    @property\n    def scale(self):\n        return self.attrs['fieldScale']    \n    @scale.setter\n    def scale(self, val):\n        self.attrs['fieldScale']  = val\n\n    @property\n    def phase(self):\n        \"\"\"\n        Returns the complex argument `phi = -2*pi*RFphase`\n        to multiply the oscillating field by. \n\n        Can be set. \n        \"\"\"\n        return -self.attrs['RFphase']*2*np.pi\n    @phase.setter\n    def phase(self, val):\n        \"\"\"\n        Complex argument in radians\n        \"\"\"\n        self.attrs['RFphase']  = -val/(2*np.pi)    \n\n    @property\n    def factor(self):\n        \"\"\"\n        factor to multiply fields by, possibly complex.\n\n        `factor = scale * exp(i*phase)`\n        \"\"\"\n        return np.real_if_close(self.scale * np.exp(1j*self.phase))           \n\n    @property\n    def axis_labels(self):\n        \"\"\"\n\n        \"\"\"\n        return axis_labels_from_geometry[self.geometry]\n\n    def axis_index(self, key):\n        \"\"\"\n        Returns axis index for a named axis label key.\n\n        Examples:\n\n        - `.axis_labels == ('x', 'y', 'z')`\n        - `.axis_index('z')` returns `2`\n        \"\"\"\n        for i, name in enumerate(self.axis_labels):\n            if name == key:\n                return i\n        raise ValueError(f'Axis not found: {key}')\n\n    @property\n    def coord_vecs(self):\n        \"\"\"\n        Uses gridSpacing, gridSize, and gridOriginOffset to return coordinate vectors.\n        \"\"\"\n        return [np.linspace(x0, x1, nx) for x0, x1, nx in zip(self.mins, self.maxs, self.shape)]\n\n    def coord_vec(self, key):\n        \"\"\"\n        Gets the coordinate vector from a named axis key. \n        \"\"\"\n        i = self.axis_index(key)\n        return np.linspace(self.mins[i], self.maxs[i], self.shape[i])\n\n    @property \n    def meshgrid(self):\n        \"\"\"\n        Usses coordinate vectors to produce a standard numpy meshgrids. \n        \"\"\"\n        vecs = self.coord_vecs\n        return np.meshgrid(*vecs, indexing='ij')\n\n\n\n    @property \n    def mins(self):\n        return np.array(self.attrs['gridOriginOffset'])\n    @property\n    def deltas(self):\n        return np.array(self.attrs['gridSpacing'])\n    @property\n    def maxs(self):\n        return self.deltas*(np.array(self.attrs['gridSize'])-1) + self.mins      \n\n    @property\n    def frequency(self):\n        if self.is_static:\n            return 0\n        else:\n            return self.attrs['harmonic']*self.attrs['fundamentalFrequency']\n\n\n    # Logicals\n    @property\n    def is_pure_electric(self):\n        \"\"\"\n        Returns True if there are no non-zero mageneticField components\n        \"\"\"\n        klist = [key for key in self.components if not self.component_is_zero(key)]\n        return all([key.startswith('electric') for key in klist])\n    # Logicals\n    @property\n    def is_pure_magnetic(self):\n        \"\"\"\n        Returns True if there are no non-zero electricField components\n        \"\"\"\n        klist = [key for key in self.components if not self.component_is_zero(key)]\n        return all([key.startswith('magnetic') for key in klist])\n\n\n\n    @property\n    def is_static(self):\n        return  self.attrs['harmonic'] == 0\n\n\n\n    def component_is_zero(self, key):\n        \"\"\"\n        Returns True if all elements in a component are zero.\n        \"\"\"\n        a = self[key]\n        return not np.any(a)\n\n\n    # Plotting\n    # TODO: more general plotting\n    def plot(self, component=None, time=None, axes=None, cmap=None, return_figure=False, **kwargs):\n\n        if self.geometry != 'cylindrical':\n            raise NotImplementedError(f'Geometry {self.geometry} not implemented')\n\n        return plot_fieldmesh_cylindrical_2d(self,\n                                             component=component,\n                                             time=time,\n                                             axes=axes,\n                                             return_figure=return_figure,\n                                             cmap=cmap, **kwargs)\n\n    @functools.wraps(plot_fieldmesh_cylindrical_1d) \n    def plot_onaxis(self, *args, **kwargs):\n        assert self.geometry == 'cylindrical'\n        return plot_fieldmesh_cylindrical_1d(self, *args, **kwargs)\n\n\n    def units(self, key):\n        \"\"\"Returns the units of any key\"\"\"\n\n        # Strip any operators\n        _, key = get_operator(key)\n\n        # Fill out aliases \n        if key in component_from_alias:\n            key = component_from_alias[key]         \n\n        return pg_units(key)    \n\n    # openPMD    \n    def write(self, h5, name=None):\n        \"\"\"\n        Writes openPMD-beamphysics format to an open h5 handle, or new file if h5 is a str.\n\n        \"\"\"\n        if isinstance(h5, str):\n            fname = os.path.expandvars(os.path.expanduser(h5))\n            h5 = File(fname, 'w')\n            pmd_field_init(h5, externalFieldPath='/ExternalFieldPath/%T/')\n            g = h5.create_group('/ExternalFieldPath/1/')\n        else:\n            g = h5\n\n        write_pmd_field(g, self.data, name=name)   \n\n    @functools.wraps(write_astra_1d_fieldmap)\n    def write_astra_1d(self, filePath):      \n        return  write_astra_1d_fieldmap(self, filePath)\n\n    def to_astra_1d(self):\n        z, fz = astra_1d_fieldmap_data(self)   \n        dat = np.array([z, fz]).T \n        return {'attrs': {'type': 'astra_1d'}, 'data': dat}\n\n    def write_astra_3d(self, common_filePath, verbose=False):      \n        return  write_astra_3d_fieldmaps(self, common_filePath)\n\n\n    @functools.wraps(create_impact_solrf_ele)      \n    def to_impact_solrf(self, *args, **kwargs):\n        return create_impact_solrf_ele(self, *args, **kwargs)\n\n    def to_cylindrical(self):\n        \"\"\"\n        Returns a new FieldMesh in cylindrical geometry.\n\n        If the current geometry is rectangular, this\n        will use the y=0 slice.\n\n        \"\"\"\n        if self.geometry == 'rectangular':\n            return FieldMesh(data=fieldmesh_rectangular_to_cylindrically_symmetric_data(self))\n        elif self.geometry == 'cylindrical':\n            return self\n        else:\n            raise NotImplementedError(f\"geometry not implemented: {self.geometry}\")\n\n\n    def write_gpt(self, filePath, asci2gdf_bin=None, verbose=True):\n        \"\"\"\n        Writes a GPT field file. \n        \"\"\"\n\n        return write_gpt_fieldmesh(self, filePath, asci2gdf_bin=asci2gdf_bin, verbose=verbose)\n\n    # Superfish\n    @functools.wraps(write_superfish_t7)\n    def write_superfish(self, filePath, verbose=False):\n        \"\"\"\n        Write a Superfish T7 file. \n\n        For static fields, a Poisson T7 file is written.\n\n        For dynamic (`harmonic /= 0`) fields, a Fish T7 file is written\n        \"\"\"\n        return write_superfish_t7(self, filePath, verbose=verbose)\n\n\n\n    @classmethod\n    @functools.wraps(read_superfish_t7)\n    def from_superfish(cls, filename, type=None, geometry='cylindrical'):\n        \"\"\"\n        Class method to parse a superfish T7 style file.\n        \"\"\"        \n        data = read_superfish_t7(filename, type=type, geometry=geometry)\n        c = cls(data=data)\n        return c               \n\n\n    @classmethod\n    def from_ansys_ascii_3d(cls, *, \n                   efile = None,\n                   hfile = None,\n                   frequency = None):\n        \"\"\"\n        Class method to return a FieldMesh from ANSYS ASCII files.\n\n        The format of each file is:\n        header1 (ignored)\n        header2 (ignored)\n        x y z re_fx im_fx re_fy im_fy re_fz im_fz \n        ...\n        in C order, with oscillations as exp(i*omega*t)\n\n        Parameters\n        ----------\n        efile: str\n            Filename with complex electric field data in V/m\n\n        hfile: str\n            Filename with complex magnetic H field data in A/m\n\n        frequency: float\n            Frequency in Hz\n\n        Returns\n        -------\n        FieldMesh\n\n        \"\"\"\n\n        if frequency is None:\n            raise ValueError(f\"Please provide a frequency\")\n\n        data = read_ansys_ascii_3d_fields(efile, hfile, frequency=frequency)\n        return cls(data=data)\n\n\n\n    @classmethod\n    def from_astra_3d(cls, common_filename, frequency=0):\n        \"\"\"\n        Class method to parse multiple 3D astra fieldmap files,\n        based on the common filename.\n        \"\"\"\n\n        data = read_astra_3d_fieldmaps(common_filename, frequency=frequency)\n        return cls(data=data)\n\n    @classmethod\n    def from_onaxis(cls, *,\n                    z=None,\n                    Bz=None,\n                    Ez=None,\n                    frequency=0,\n                    harmonic=None,\n                    eleAnchorPt = 'beginning'\n                   ):\n            \"\"\"\n\n\n            Parameters \n            ----------\n            z: array\n                z-coordinates. Must be regularly spaced.       \n\n            Bz: array, optional\n                magnetic field at r=0 in T\n                Default: None        \n\n            Ez: array, optional\n                Electric field at r=0 in V/m\n                Default: None\n\n            frequency: float, optional\n                fundamental frequency in Hz.\n                Default: 0\n\n            harmonic: int, optional\n                Harmonic of the fundamental the field actually oscillates at.\n                Default: 1 if frequency !=0, otherwise 0. \n\n            eleAnchorPt: str, optional\n                Element anchor point.\n                Should be one of 'beginning', 'center', 'end'\n                Default: 'beginning'\n\n\n            Returns\n            -------\n            field: FieldMesh\n                Instantiated fieldmesh\n\n            \"\"\"\n\n            # Get spacing\n            nz = len(z)\n            dz = np.diff(z)\n            if not np.allclose(dz, dz[0]):\n                raise NotImplementedError(\"Irregular spacing not implemented\")\n            dz = dz[0]    \n\n            components = {}\n            if Ez is not None:\n                Ez = np.squeeze(np.array(Ez))\n                if Ez.ndim != 1:\n                    raise ValueError(f'Ez ndim = {Ez.ndim} must be 1')\n                components['electricField/z'] = Ez.reshape(1,1,len(Ez))\n\n            if Bz is not None:\n                Bz = np.squeeze(np.array(Bz))\n                if Bz.ndim != 1:\n                    raise ValueError(f'Bz ndim = {Bz.ndim} must be 1')\n                components['magneticField/z'] = Bz.reshape(1,1,len(Bz))            \n\n            if Bz is None and Ez is None:\n                raise ValueError('Please enter Ez or Bz')\n\n            # Handle harmonic options\n            if frequency == 0:\n                harmonic = 0\n            elif harmonic is None:\n                harmonic = 1\n\n            attrs = {'eleAnchorPt': eleAnchorPt,\n             'gridGeometry': 'cylindrical',\n             'axisLabels': np.array(['r', 'theta', 'z'], dtype='<U5'),\n             'gridLowerBound': np.array([0, 0, 0]),\n             'gridOriginOffset': np.array([ 0. ,  0. , z.min()]),\n             'gridSpacing': np.array([0. , 0.   , dz]),\n             'gridSize': np.array([1,  1, nz]),\n             'harmonic': harmonic,\n             'fundamentalFrequency': frequency,\n             'RFphase': 0,\n             'fieldScale': 1.0} \n\n            data = dict(attrs=attrs, components=components)\n            return cls(data=data)        \n\n\n\n    @functools.wraps(expand_fieldmesh_from_onaxis)\n    def expand_onaxis(self, *args, **kwargs):\n        return expand_fieldmesh_from_onaxis(self, *args, **kwargs)\n\n\n\n\n    def __eq__(self, other):\n        \"\"\"\n        Checks that all attributes and component internal data are the same\n        \"\"\"\n\n        if not tools.data_are_equal(self.attrs, other.attrs):\n            return False\n\n        return tools.data_are_equal(self.components, other.components)\n\n\n #  def __setattr__(self, key, value):\n #      print('a', key)\n #      if key in component_from_alias:\n #          print('here', key)\n #          comp = component_from_alias[key]\n #          if comp in self.components:\n #              self.components[comp] = value\n\n #  def __getattr__(self, key):\n #      print('a')\n #      if key in component_from_alias:\n #          print('here', key)\n #          comp = component_from_alias[key]\n #          if comp in self.components:\n #              return self.components[comp]\n\n\n\n\n\n    def scaled_component(self, key):\n        \"\"\"\n\n        Retruns a component scaled by the complex factor\n            factor = scale*exp(i*phase)\n\n\n        \"\"\"\n\n        if key in self.components:\n            dat = self.components[key] \n        # Aliases\n        elif key in component_from_alias:\n            comp = component_from_alias[key]\n            if comp in self.components:\n                dat = self.components[comp]   \n            else:\n                # Component not present, make zeros\n                return np.zeros(self.shape)\n        else:\n            raise ValueError(f'Component not available: {key}')\n\n        # Multiply by scale factor\n        factor = self.factor      \n\n        if factor != 1:\n            return factor*dat\n        else:\n            return dat\n\n    # Convenient properties\n    # TODO: Automate this?\n    @property\n    def r(self):\n        return self.coord_vec('r')\n    @property\n    def theta(self):\n        return self.coord_vec('theta')\n    @property\n    def z(self):\n        return self.coord_vec('z')    \n\n    # Deltas\n    ## cartesian\n    @property\n    def dx(self):\n        return self.deltas[self.axis_index('x')]\n    @property\n    def dy(self):\n        return self.deltas[self.axis_index('y')]    \n    ## cylindrical\n    @property\n    def dr(self):\n        return self.deltas[self.axis_index('r')]\n    @property\n    def dtheta(self):\n        return self.deltas[self.axis_index('theta')]\n    @property\n    def dz(self):\n        return self.deltas[self.axis_index('z')]  \n\n    # Scaled components\n    # TODO: Check geometry\n    ## cartesian\n    @property\n    def Bx(self):\n        return self.scaled_component('Bx') \n    @property\n    def By(self):\n        return self.scaled_component('By')  \n    @property\n    def Ex(self):\n        return self.scaled_component('Ex')  \n    @property\n    def Ey(self):\n        return self.scaled_component('Ey')         \n\n    ## cylindrical\n    @property\n    def Br(self):\n        return self.scaled_component('Br')\n    @property\n    def Btheta(self):\n        return self.scaled_component('Btheta')\n    @property\n    def Bz(self):\n        return self.scaled_component('Bz')\n    @property\n    def Er(self):\n        return self.scaled_component('Er')\n    @property\n    def Etheta(self):\n        return self.scaled_component('Etheta')\n    @property\n    def Ez(self):\n        return self.scaled_component('Ez')    \n\n\n\n\n    @property\n    def B(self):\n        if self.geometry=='cylindrical':\n            if self.is_static:\n                return np.hypot(self['Br'], self['Bz'])\n            else:\n                return np.abs(self['Btheta'])\n        else:\n            raise ValueError(f'Unknown geometry: {self.geometry}')    \n\n    @property\n    def E(self):\n        if self.geometry=='cylindrical':\n            return np.hypot(np.abs(self['Er']), np.abs(self['Ez']))\n        else:\n            raise ValueError(f'Unknown geometry: {self.geometry}')    \n\n    def __getitem__(self, key):\n        \"\"\"\n        Returns component data from a key\n\n        If the key starts with:\n\n        - `re_`\n        - `im_`\n        - `abs_`\n\n        the appropriate numpy operator is applied.\n\n\n\n        \"\"\"\n\n        # \n        if key in ['r', 'theta', 'z']:\n            return self.coord_vec(key)\n\n\n        # Raw components\n        if key in self.components:\n            return self.components[key]\n\n        # Check for operators\n        operator, key = get_operator(key)\n\n        # Scaled components\n        if key == 'E':\n            dat = self.E\n        elif key == 'B':\n            dat =  self.B\n        else:\n            dat = self.scaled_component(key)        \n\n        if operator:\n            dat = operator(dat)\n\n        return dat\n\n\n    def copy(self):\n        \"\"\"Returns a deep copy\"\"\"\n        return deepcopy(self)          \n\n    def __repr__(self):\n        memloc = hex(id(self))\n        return f'<FieldMesh with {self.geometry} geometry and {self.shape} shape at {memloc}>'        \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.axis_labels","title":"axis_labels property","text":""},{"location":"api/fields/#pmd_beamphysics.FieldMesh.coord_vecs","title":"coord_vecs property","text":"

Uses gridSpacing, gridSize, and gridOriginOffset to return coordinate vectors.

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.factor","title":"factor property","text":"

factor to multiply fields by, possibly complex.

factor = scale * exp(i*phase)

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.is_pure_electric","title":"is_pure_electric property","text":"

Returns True if there are no non-zero mageneticField components

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.is_pure_magnetic","title":"is_pure_magnetic property","text":"

Returns True if there are no non-zero electricField components

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.meshgrid","title":"meshgrid property","text":"

Usses coordinate vectors to produce a standard numpy meshgrids.

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.phase","title":"phase property writable","text":"

Returns the complex argument phi = -2*pi*RFphase to multiply the oscillating field by.

Can be set.

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.__eq__","title":"__eq__(other)","text":"

Checks that all attributes and component internal data are the same

Source code in pmd_beamphysics/fields/fieldmesh.py
def __eq__(self, other):\n    \"\"\"\n    Checks that all attributes and component internal data are the same\n    \"\"\"\n\n    if not tools.data_are_equal(self.attrs, other.attrs):\n        return False\n\n    return tools.data_are_equal(self.components, other.components)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.__getitem__","title":"__getitem__(key)","text":"

Returns component data from a key

If the key starts with:

  • re_
  • im_
  • abs_

the appropriate numpy operator is applied.

Source code in pmd_beamphysics/fields/fieldmesh.py
def __getitem__(self, key):\n    \"\"\"\n    Returns component data from a key\n\n    If the key starts with:\n\n    - `re_`\n    - `im_`\n    - `abs_`\n\n    the appropriate numpy operator is applied.\n\n\n\n    \"\"\"\n\n    # \n    if key in ['r', 'theta', 'z']:\n        return self.coord_vec(key)\n\n\n    # Raw components\n    if key in self.components:\n        return self.components[key]\n\n    # Check for operators\n    operator, key = get_operator(key)\n\n    # Scaled components\n    if key == 'E':\n        dat = self.E\n    elif key == 'B':\n        dat =  self.B\n    else:\n        dat = self.scaled_component(key)        \n\n    if operator:\n        dat = operator(dat)\n\n    return dat\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.axis_index","title":"axis_index(key)","text":"

Returns axis index for a named axis label key.

Examples:

  • .axis_labels == ('x', 'y', 'z')
  • .axis_index('z') returns 2
Source code in pmd_beamphysics/fields/fieldmesh.py
def axis_index(self, key):\n    \"\"\"\n    Returns axis index for a named axis label key.\n\n    Examples:\n\n    - `.axis_labels == ('x', 'y', 'z')`\n    - `.axis_index('z')` returns `2`\n    \"\"\"\n    for i, name in enumerate(self.axis_labels):\n        if name == key:\n            return i\n    raise ValueError(f'Axis not found: {key}')\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.component_is_zero","title":"component_is_zero(key)","text":"

Returns True if all elements in a component are zero.

Source code in pmd_beamphysics/fields/fieldmesh.py
def component_is_zero(self, key):\n    \"\"\"\n    Returns True if all elements in a component are zero.\n    \"\"\"\n    a = self[key]\n    return not np.any(a)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.coord_vec","title":"coord_vec(key)","text":"

Gets the coordinate vector from a named axis key.

Source code in pmd_beamphysics/fields/fieldmesh.py
def coord_vec(self, key):\n    \"\"\"\n    Gets the coordinate vector from a named axis key. \n    \"\"\"\n    i = self.axis_index(key)\n    return np.linspace(self.mins[i], self.maxs[i], self.shape[i])\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.copy","title":"copy()","text":"

Returns a deep copy

Source code in pmd_beamphysics/fields/fieldmesh.py
def copy(self):\n    \"\"\"Returns a deep copy\"\"\"\n    return deepcopy(self)          \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_ansys_ascii_3d","title":"from_ansys_ascii_3d(*, efile=None, hfile=None, frequency=None) classmethod","text":"

Class method to return a FieldMesh from ANSYS ASCII files.

The format of each file is: header1 (ignored) header2 (ignored) x y z re_fx im_fx re_fy im_fy re_fz im_fz ... in C order, with oscillations as exp(iomegat)

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_ansys_ascii_3d--parameters","title":"Parameters","text":"

efile: str Filename with complex electric field data in V/m

str

Filename with complex magnetic H field data in A/m

float

Frequency in Hz

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_ansys_ascii_3d--returns","title":"Returns","text":"

FieldMesh

Source code in pmd_beamphysics/fields/fieldmesh.py
@classmethod\ndef from_ansys_ascii_3d(cls, *, \n               efile = None,\n               hfile = None,\n               frequency = None):\n    \"\"\"\n    Class method to return a FieldMesh from ANSYS ASCII files.\n\n    The format of each file is:\n    header1 (ignored)\n    header2 (ignored)\n    x y z re_fx im_fx re_fy im_fy re_fz im_fz \n    ...\n    in C order, with oscillations as exp(i*omega*t)\n\n    Parameters\n    ----------\n    efile: str\n        Filename with complex electric field data in V/m\n\n    hfile: str\n        Filename with complex magnetic H field data in A/m\n\n    frequency: float\n        Frequency in Hz\n\n    Returns\n    -------\n    FieldMesh\n\n    \"\"\"\n\n    if frequency is None:\n        raise ValueError(f\"Please provide a frequency\")\n\n    data = read_ansys_ascii_3d_fields(efile, hfile, frequency=frequency)\n    return cls(data=data)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_astra_3d","title":"from_astra_3d(common_filename, frequency=0) classmethod","text":"

Class method to parse multiple 3D astra fieldmap files, based on the common filename.

Source code in pmd_beamphysics/fields/fieldmesh.py
@classmethod\ndef from_astra_3d(cls, common_filename, frequency=0):\n    \"\"\"\n    Class method to parse multiple 3D astra fieldmap files,\n    based on the common filename.\n    \"\"\"\n\n    data = read_astra_3d_fieldmaps(common_filename, frequency=frequency)\n    return cls(data=data)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_onaxis","title":"from_onaxis(*, z=None, Bz=None, Ez=None, frequency=0, harmonic=None, eleAnchorPt='beginning') classmethod","text":""},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_onaxis--parameters","title":"Parameters","text":"

z: array z-coordinates. Must be regularly spaced.

array, optional

magnetic field at r=0 in T Default: None

array, optional

Electric field at r=0 in V/m Default: None

float, optional

fundamental frequency in Hz. Default: 0

int, optional

Harmonic of the fundamental the field actually oscillates at. Default: 1 if frequency !=0, otherwise 0.

str, optional

Element anchor point. Should be one of 'beginning', 'center', 'end' Default: 'beginning'

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_onaxis--returns","title":"Returns","text":"

field: FieldMesh Instantiated fieldmesh

Source code in pmd_beamphysics/fields/fieldmesh.py
@classmethod\ndef from_onaxis(cls, *,\n                z=None,\n                Bz=None,\n                Ez=None,\n                frequency=0,\n                harmonic=None,\n                eleAnchorPt = 'beginning'\n               ):\n        \"\"\"\n\n\n        Parameters \n        ----------\n        z: array\n            z-coordinates. Must be regularly spaced.       \n\n        Bz: array, optional\n            magnetic field at r=0 in T\n            Default: None        \n\n        Ez: array, optional\n            Electric field at r=0 in V/m\n            Default: None\n\n        frequency: float, optional\n            fundamental frequency in Hz.\n            Default: 0\n\n        harmonic: int, optional\n            Harmonic of the fundamental the field actually oscillates at.\n            Default: 1 if frequency !=0, otherwise 0. \n\n        eleAnchorPt: str, optional\n            Element anchor point.\n            Should be one of 'beginning', 'center', 'end'\n            Default: 'beginning'\n\n\n        Returns\n        -------\n        field: FieldMesh\n            Instantiated fieldmesh\n\n        \"\"\"\n\n        # Get spacing\n        nz = len(z)\n        dz = np.diff(z)\n        if not np.allclose(dz, dz[0]):\n            raise NotImplementedError(\"Irregular spacing not implemented\")\n        dz = dz[0]    \n\n        components = {}\n        if Ez is not None:\n            Ez = np.squeeze(np.array(Ez))\n            if Ez.ndim != 1:\n                raise ValueError(f'Ez ndim = {Ez.ndim} must be 1')\n            components['electricField/z'] = Ez.reshape(1,1,len(Ez))\n\n        if Bz is not None:\n            Bz = np.squeeze(np.array(Bz))\n            if Bz.ndim != 1:\n                raise ValueError(f'Bz ndim = {Bz.ndim} must be 1')\n            components['magneticField/z'] = Bz.reshape(1,1,len(Bz))            \n\n        if Bz is None and Ez is None:\n            raise ValueError('Please enter Ez or Bz')\n\n        # Handle harmonic options\n        if frequency == 0:\n            harmonic = 0\n        elif harmonic is None:\n            harmonic = 1\n\n        attrs = {'eleAnchorPt': eleAnchorPt,\n         'gridGeometry': 'cylindrical',\n         'axisLabels': np.array(['r', 'theta', 'z'], dtype='<U5'),\n         'gridLowerBound': np.array([0, 0, 0]),\n         'gridOriginOffset': np.array([ 0. ,  0. , z.min()]),\n         'gridSpacing': np.array([0. , 0.   , dz]),\n         'gridSize': np.array([1,  1, nz]),\n         'harmonic': harmonic,\n         'fundamentalFrequency': frequency,\n         'RFphase': 0,\n         'fieldScale': 1.0} \n\n        data = dict(attrs=attrs, components=components)\n        return cls(data=data)        \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_superfish","title":"from_superfish(filename, type=None, geometry='cylindrical') classmethod","text":"

Class method to parse a superfish T7 style file.

Source code in pmd_beamphysics/fields/fieldmesh.py
@classmethod\n@functools.wraps(read_superfish_t7)\ndef from_superfish(cls, filename, type=None, geometry='cylindrical'):\n    \"\"\"\n    Class method to parse a superfish T7 style file.\n    \"\"\"        \n    data = read_superfish_t7(filename, type=type, geometry=geometry)\n    c = cls(data=data)\n    return c               \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.scaled_component","title":"scaled_component(key)","text":"

Retruns a component scaled by the complex factor factor = scaleexp(iphase)

Source code in pmd_beamphysics/fields/fieldmesh.py
def scaled_component(self, key):\n    \"\"\"\n\n    Retruns a component scaled by the complex factor\n        factor = scale*exp(i*phase)\n\n\n    \"\"\"\n\n    if key in self.components:\n        dat = self.components[key] \n    # Aliases\n    elif key in component_from_alias:\n        comp = component_from_alias[key]\n        if comp in self.components:\n            dat = self.components[comp]   \n        else:\n            # Component not present, make zeros\n            return np.zeros(self.shape)\n    else:\n        raise ValueError(f'Component not available: {key}')\n\n    # Multiply by scale factor\n    factor = self.factor      \n\n    if factor != 1:\n        return factor*dat\n    else:\n        return dat\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.to_cylindrical","title":"to_cylindrical()","text":"

Returns a new FieldMesh in cylindrical geometry.

If the current geometry is rectangular, this will use the y=0 slice.

Source code in pmd_beamphysics/fields/fieldmesh.py
def to_cylindrical(self):\n    \"\"\"\n    Returns a new FieldMesh in cylindrical geometry.\n\n    If the current geometry is rectangular, this\n    will use the y=0 slice.\n\n    \"\"\"\n    if self.geometry == 'rectangular':\n        return FieldMesh(data=fieldmesh_rectangular_to_cylindrically_symmetric_data(self))\n    elif self.geometry == 'cylindrical':\n        return self\n    else:\n        raise NotImplementedError(f\"geometry not implemented: {self.geometry}\")\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.units","title":"units(key)","text":"

Returns the units of any key

Source code in pmd_beamphysics/fields/fieldmesh.py
def units(self, key):\n    \"\"\"Returns the units of any key\"\"\"\n\n    # Strip any operators\n    _, key = get_operator(key)\n\n    # Fill out aliases \n    if key in component_from_alias:\n        key = component_from_alias[key]         \n\n    return pg_units(key)    \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.write","title":"write(h5, name=None)","text":"

Writes openPMD-beamphysics format to an open h5 handle, or new file if h5 is a str.

Source code in pmd_beamphysics/fields/fieldmesh.py
def write(self, h5, name=None):\n    \"\"\"\n    Writes openPMD-beamphysics format to an open h5 handle, or new file if h5 is a str.\n\n    \"\"\"\n    if isinstance(h5, str):\n        fname = os.path.expandvars(os.path.expanduser(h5))\n        h5 = File(fname, 'w')\n        pmd_field_init(h5, externalFieldPath='/ExternalFieldPath/%T/')\n        g = h5.create_group('/ExternalFieldPath/1/')\n    else:\n        g = h5\n\n    write_pmd_field(g, self.data, name=name)   \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.write_gpt","title":"write_gpt(filePath, asci2gdf_bin=None, verbose=True)","text":"

Writes a GPT field file.

Source code in pmd_beamphysics/fields/fieldmesh.py
def write_gpt(self, filePath, asci2gdf_bin=None, verbose=True):\n    \"\"\"\n    Writes a GPT field file. \n    \"\"\"\n\n    return write_gpt_fieldmesh(self, filePath, asci2gdf_bin=asci2gdf_bin, verbose=verbose)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.write_superfish","title":"write_superfish(filePath, verbose=False)","text":"

Write a Superfish T7 file.

For static fields, a Poisson T7 file is written.

For dynamic (harmonic /= 0) fields, a Fish T7 file is written

Source code in pmd_beamphysics/fields/fieldmesh.py
@functools.wraps(write_superfish_t7)\ndef write_superfish(self, filePath, verbose=False):\n    \"\"\"\n    Write a Superfish T7 file. \n\n    For static fields, a Poisson T7 file is written.\n\n    For dynamic (`harmonic /= 0`) fields, a Fish T7 file is written\n    \"\"\"\n    return write_superfish_t7(self, filePath, verbose=verbose)\n
"},{"location":"api/particles/","title":"Particles","text":"

Particle Group class

Initialized on on openPMD beamphysics particle group:

  • h5: open h5 handle, or str that is a file
  • data: raw data

The required bunch data is stored in .data with keys

  • np.array: x, px, y, py, z, pz, t, status, weight
  • str: species

where:

  • x, y, z are positions in units of [m]
  • px, py, pz are momenta in units of [eV/c]
  • t is time in [s]
  • weight is the macro-charge weight in [C], used for all statistical calulations.
  • species is a proper species name: 'electron', etc.

Optional data:

  • np.array: id

where id is a list of unique integers that identify the particles.

Derived data can be computed as attributes:

  • .gamma, .beta, .beta_x, .beta_y, .beta_z: relativistic factors [1].
  • .r, .theta: cylidrical coordinates [m], [1]
  • .pr, .ptheta: momenta in the radial and angular coordinate directions [eV/c]
  • .Lz: angular momentum about the z axis [m*eV/c]
  • .energy : total energy [eV]
  • .kinetic_energy: total energy - mc^2 in [eV].
  • .higher_order_energy: total energy with quadratic fit in z or t subtracted [eV]
  • .p: total momentum in [eV/c]
  • .mass: rest mass in [eV]
  • .xp, .yp: Slopes \\(x' = dx/dz = dp_x/dp_z\\) and \\(y' = dy/dz = dp_y/dp_z\\) [1].

Normalized transvere coordinates can also be calculated as attributes:

  • .x_bar, .px_bar, .y_bar, .py_bar in [sqrt(m)]

The normalization is automatically calculated from the covariance matrix. See functions in .statistics for more advanced usage.

Their cooresponding amplitudes are:

.Jx, .Jy [m]

where Jx = (x_bar^2 + px_bar^2 )/2.

The momenta are normalized by the mass, so that <Jx> = norm_emit_x and similar for y.

Statistics of any of these are calculated with:

  • .min(X)
  • .max(X)
  • .ptp(X)
  • .avg(X)
  • .std(X)
  • .cov(X, Y, ...)
  • .histogramdd(X, Y, ..., bins=10, range=None)

with a string X as the name any of the properties above.

Useful beam physics quantities are given as attributes:

  • .norm_emit_x
  • .norm_emit_y
  • .norm_emit_4d
  • .higher_order_energy_spread
  • .average_current

Twiss parameters, including dispersion, for the \\(x\\) or \\(y\\) plane:

  • .twiss(plane='x', fraction=0.95, p0C=None)

For convenience, plane='xy' will calculate twiss for both planes.

Twiss matched particles, using a simple linear transformation:

  • .twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False)

The weight is required and must sum to > 0. The sum of the weights is in .charge. This can also be set: .charge = 1.234 # C, will rescale the .weight array

All attributes can be accessed with brackets

[key]

Additional keys are allowed for convenience

['min_prop'] will return .min('prop') ['max_prop'] will return .max('prop') ['ptp_prop'] will return .ptp('prop') ['mean_prop'] will return .avg('prop') ['sigma_prop'] will return .std('prop') ['cov_prop1__prop2'] will return .cov('prop1', 'prop2')[0,1]

Units for all attributes can be accessed by:

  • .units(key)

Particles are often stored at the same time (i.e. from a t-based code), or with the same z position (i.e. from an s-based code.) Routines:

  • drift_to_z(z0)
  • drift_to_t(t0)

help to convert these. If no argument is given, particles will be drifted to the mean. Related properties are:

  • .in_t_coordinates returns True if all particles have the same \\(t\\) corrdinate
  • .in_z_coordinates returns True if all particles have the same \\(z\\) corrdinate

Convenient plotting is provided with:

  • .plot(...)
  • .slice_plot(...)

Use help(ParticleGroup.plot), etc. for usage.

Source code in pmd_beamphysics/particles.py
class ParticleGroup:\n    \"\"\"\n    Particle Group class\n\n    Initialized on on openPMD beamphysics particle group:\n\n    - **h5**: open h5 handle, or `str` that is a file\n    - **data**: raw data\n\n    The required bunch data is stored in `.data` with keys\n\n    - `np.array`: `x`, `px`, `y`, `py`, `z`, `pz`, `t`, `status`, `weight`\n    - `str`: `species`\n\n    where:\n\n    - `x`, `y`, `z` are positions in units of [m]\n    - `px`, `py`, `pz` are momenta in units of [eV/c]\n    - `t` is time in [s]\n    - `weight` is the macro-charge weight in [C], used for all statistical calulations.\n    - `species` is a proper species name: `'electron'`, etc. \n\n    Optional data:\n\n    - `np.array`: `id`\n\n    where `id` is a list of unique integers that identify the particles. \n\n\n    Derived data can be computed as attributes:\n\n    - `.gamma`, `.beta`, `.beta_x`, `.beta_y`, `.beta_z`: relativistic factors [1].\n    - `.r`, `.theta`: cylidrical coordinates [m], [1]\n    - `.pr`, `.ptheta`: momenta in the radial and angular coordinate directions [eV/c]\n    - `.Lz`: angular momentum about the z axis [m*eV/c]\n    - `.energy` : total energy [eV]\n    - `.kinetic_energy`: total energy - mc^2 in [eV]. \n    - `.higher_order_energy`: total energy with quadratic fit in z or t subtracted [eV]\n    - `.p`: total momentum in [eV/c]\n    - `.mass`: rest mass in [eV]\n    - `.xp`, `.yp`: Slopes $x' = dx/dz = dp_x/dp_z$ and $y' = dy/dz = dp_y/dp_z$ [1].\n\n    Normalized transvere coordinates can also be calculated as attributes:\n\n    - `.x_bar`, `.px_bar`, `.y_bar`, `.py_bar` in [sqrt(m)]\n\n    The normalization is automatically calculated from the covariance matrix. \n    See functions in `.statistics` for more advanced usage.\n\n    Their cooresponding amplitudes are:\n\n    `.Jx`, `.Jy` [m]\n\n    where `Jx = (x_bar^2 + px_bar^2 )/2`.\n\n    The momenta are normalized by the mass, so that\n    `<Jx> = norm_emit_x`\n    and similar for `y`. \n\n    Statistics of any of these are calculated with:\n\n    - `.min(X)`\n    - `.max(X)`\n    - `.ptp(X)`\n    - `.avg(X)`\n    - `.std(X)`\n    - `.cov(X, Y, ...)`\n    - `.histogramdd(X, Y, ..., bins=10, range=None)`\n\n    with a string `X` as the name any of the properties above.\n\n    Useful beam physics quantities are given as attributes:\n\n    - `.norm_emit_x`\n    - `.norm_emit_y`\n    - `.norm_emit_4d`\n    - `.higher_order_energy_spread`\n    - `.average_current`\n\n    Twiss parameters, including dispersion, for the $x$ or $y$ plane:\n\n    - `.twiss(plane='x', fraction=0.95, p0C=None)`\n\n    For convenience, `plane='xy'` will calculate twiss for both planes.\n\n    Twiss matched particles, using a simple linear transformation:\n\n    - `.twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False)`\n\n    The weight is required and must sum to > 0. The sum of the weights is in `.charge`.\n    This can also be set: `.charge = 1.234` # C, will rescale the .weight array\n\n    All attributes can be accessed with brackets:\n        `[key]`\n\n    Additional keys are allowed for convenience:\n        `['min_prop']`   will return  `.min('prop')`\n        `['max_prop']`   will return  `.max('prop')`\n        `['ptp_prop']`   will return  `.ptp('prop')`\n        `['mean_prop']`  will return  `.avg('prop')`\n        `['sigma_prop']` will return  `.std('prop')`\n        `['cov_prop1__prop2']` will return `.cov('prop1', 'prop2')[0,1]`\n\n    Units for all attributes can be accessed by:\n\n    - `.units(key)`\n\n    Particles are often stored at the same time (i.e. from a t-based code), \n    or with the same z position (i.e. from an s-based code.)\n    Routines: \n\n    - `drift_to_z(z0)`\n    - `drift_to_t(t0)`\n\n    help to convert these. If no argument is given, particles will be drifted to the mean.\n    Related properties are:\n\n    - `.in_t_coordinates` returns `True` if all particles have the same $t$ corrdinate\n    - `.in_z_coordinates` returns `True` if all particles have the same $z$ corrdinate\n\n    Convenient plotting is provided with: \n\n    - `.plot(...)`\n    - `.slice_plot(...)`\n\n    Use `help(ParticleGroup.plot)`, etc. for usage. \n\n\n    \"\"\"\n    def __init__(self, h5=None, data=None):\n\n\n        if h5 and data:\n            # TODO:\n            # Allow merging or changing some array with extra data\n            raise NotImplementedError('Cannot init on both h5 and data')\n\n        if h5:\n            # Allow filename\n            if isinstance(h5, str):\n                fname = os.path.expandvars(h5)\n                assert os.path.exists(fname), f'File does not exist: {fname}'\n\n                with File(fname, 'r') as hh5:\n                    pp = particle_paths(hh5)\n                    assert len(pp) == 1, f'Number of particle paths in {h5}: {len(pp)}'\n                    data = load_bunch_data(hh5[pp[0]])\n\n            else:\n                # Try dict\n                data = load_bunch_data(h5)\n        else:\n            # Fill out data. Exclude species.\n            data = full_data(data)\n            species = list(set(data['species']))\n\n            # Allow for empty data (len=0). Otherwise, check species.\n            if len(species) >= 1:\n                assert len(species) == 1, f'mixed species are not allowed: {species}'\n                data['species'] = species[0]\n\n        self._settable_array_keys = ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight']\n        # Optional data\n        for k in ['id']:\n            if k in data:\n                self._settable_array_keys.append(k)  \n\n        self._settable_scalar_keys = ['species']\n        self._settable_keys =  self._settable_array_keys + self._settable_scalar_keys                       \n        # Internal data. Only allow settable keys\n        self._data = {k:data[k] for k in self._settable_keys}\n\n    #-------------------------------------------------\n    # Access to intrinsic coordinates\n    @property\n    def x(self):\n        \"\"\"\n        x coordinate in [m]\n        \"\"\"\n        return self._data['x']\n    @x.setter\n    def x(self, val):\n        self._data['x'] = full_array(len(self), val)    \n\n    @property\n    def y(self):\n        \"\"\"\n        y coordinate in [m]\n        \"\"\"\n        return self._data['y']\n    @y.setter\n    def y(self, val):\n        self._data['y'] = full_array(len(self), val)              \n\n    @property\n    def z(self):\n        \"\"\"\n        z coordinate in [m]\n        \"\"\"\n        return self._data['z']\n    @z.setter\n    def z(self, val):\n        self._data['z'] = full_array(len(self), val)      \n\n    @property\n    def px(self):\n        \"\"\"\n        px coordinate in [eV/c]\n        \"\"\"\n        return self._data['px']\n    @px.setter\n    def px(self, val):\n        self._data['px'] = full_array(len(self), val)      \n\n    @property\n    def py(self):\n        \"\"\"\n        py coordinate in [eV/c]\n        \"\"\"\n        return self._data['py']\n    @py.setter\n    def py(self, val):\n        self._data['py'] = full_array(len(self), val)    \n\n    @property\n    def pz(self):\n        \"\"\"\n        pz coordinate in [eV/c]\n        \"\"\"\n        return self._data['pz']\n    @pz.setter\n    def pz(self, val):\n        self._data['pz'] = full_array(len(self), val)    \n\n    @property\n    def t(self):\n        \"\"\"\n        t coordinate in [s]\n        \"\"\"\n        return self._data['t']\n    @t.setter\n    def t(self, val):\n        self._data['t'] = full_array(len(self), val)       \n\n    @property\n    def status(self):\n        \"\"\"\n        status coordinate in [1]\n        \"\"\"\n        return self._data['status']\n    @status.setter\n    def status(self, val):\n        self._data['status'] = full_array(len(self), val)  \n\n    @property\n    def weight(self):\n        \"\"\"\n        weight coordinate in [C]\n        \"\"\"\n        return self._data['weight']\n    @weight.setter\n    def weight(self, val):\n        self._data['weight'] = full_array(len(self), val)            \n\n    @property\n    def id(self):\n        \"\"\"\n        id integer \n        \"\"\"\n        if 'id' not in self._data:\n            self.assign_id()      \n\n        return self._data['id']\n    @id.setter\n    def id(self, val):\n        self._data['id'] = full_array(len(self), val)            \n\n\n    @property\n    def species(self):\n        \"\"\"\n        species string\n        \"\"\"\n        return self._data['species']\n    @species.setter\n    def species(self, val):\n        self._data['species'] = val\n\n    @property\n    def data(self):\n        \"\"\"\n        Internal data dict\n        \"\"\"\n        return self._data        \n\n    #-------------------------------------------------\n    # Derived data\n\n    def assign_id(self):\n        \"\"\"\n        Assigns unique ids, integers from 1 to n_particle\n\n        \"\"\"\n        if 'id' not in self._settable_array_keys: \n            self._settable_array_keys.append('id')\n        self.id = np.arange(1, self['n_particle']+1)             \n\n    @property\n    def n_particle(self):\n        \"\"\"Total number of particles. Same as len \"\"\"\n        return len(self)\n\n    @property\n    def n_alive(self):\n        \"\"\"Number of alive particles, defined by status == 1\"\"\"\n        return len(np.where(self.status==1)[0])\n\n    @property\n    def n_dead(self):\n        \"\"\"Number of alive particles, defined by status != 1\"\"\"\n        return self.n_particle - self.n_alive\n\n\n    def units(self, key):\n        \"\"\"Returns the units of any key\"\"\"\n        return pg_units(key)\n\n    @property\n    def mass(self):\n        \"\"\"Rest mass in eV\"\"\"\n        return mass_of(self.species)\n\n    @property\n    def species_charge(self):\n        \"\"\"Species charge in C\"\"\"\n        return charge_of(self.species)\n\n    @property\n    def charge(self):\n        \"\"\"Total charge in C\"\"\"\n        return np.sum(self.weight)\n    @charge.setter\n    def charge(self, val):\n        \"\"\"Rescale weight array so that it sum to this value\"\"\"\n        assert val >0, 'charge must be >0. This is used to weight the particles.'\n        self.weight *= val/self.charge\n\n\n    # Relativistic properties\n    @property\n    def p(self):\n        \"\"\"Total momemtum in eV/c\"\"\"\n        return np.sqrt(self.px**2 + self.py**2 + self.pz**2) \n    @property\n    def energy(self):\n        \"\"\"Total energy in eV\"\"\"\n        return np.sqrt(self.px**2 + self.py**2 + self.pz**2 + self.mass**2) \n    @property\n    def kinetic_energy(self):\n        \"\"\"Kinetic energy in eV\"\"\"\n        return self.energy - self.mass\n\n    # Slopes. Note that these are relative to pz\n    @property\n    def xp(self):\n        \"\"\"x slope px/pz (dimensionless)\"\"\"\n        return self.px/self.pz  \n    @property\n    def yp(self):\n        \"\"\"y slope py/pz (dimensionless)\"\"\"\n        return self.py/self.pz    \n\n    @property\n    def higher_order_energy(self):\n        \"\"\"\n        Fits a quadratic (order=2) to the Energy vs. time, and returns the energy with this subtracted. \n        \"\"\" \n        return self.higher_order_energy_calc(order=2)\n\n    @property\n    def higher_order_energy_spread(self):\n        \"\"\"\n        Legacy syntax to compute the standard deviation of higher_order_energy.\n        \"\"\"\n        return self.std('higher_order_energy')\n\n    def higher_order_energy_calc(self, order=2):\n        \"\"\"\n        Fits a polynmial with order `order` to the Energy vs. time, , and returns the energy with this subtracted. \n        \"\"\"\n        #order=2\n        if self.std('z') < 1e-12:\n            # must be at a screen. Use t\n            t = self.t\n        else:\n            # All particles at the same time. Use z to calc t\n            t = self.z/c_light\n        energy = self.energy\n\n        best_fit_coeffs = np.polynomial.polynomial.polyfit(t, energy, order)\n        best_fit = np.polynomial.polynomial.polyval(t, best_fit_coeffs)\n        return energy - best_fit\n\n    # Polar coordinates. Note that these are centered at x=0, y=0, and not an averge center. \n    @property\n    def r(self):\n        \"\"\"Radius in the xy plane: r = sqrt(x^2 + y^2) in m\"\"\"\n        return np.hypot(self.x, self.y)\n    @property    \n    def theta(self):\n        \"\"\"Angle in xy plane: theta = arctan2(y, x) in radians\"\"\"\n        return np.arctan2(self.y, self.x)\n    @property\n    def pr(self):\n        \"\"\"\n        Momentum in the radial direction in eV/c\n        r_hat = cos(theta) xhat + sin(theta) yhat\n        pr = p dot r_hat\n        \"\"\"\n        theta = self.theta\n        return self.px * np.cos(theta)  + self.py * np.sin(theta)   \n    @property    \n    def ptheta(self):\n        \"\"\"     \n        Momentum in the polar theta direction. \n        theta_hat = -sin(theta) xhat + cos(theta) yhat\n        ptheta = p dot theta_hat\n        Note that Lz = r*ptheta\n        \"\"\"\n        theta = self.theta\n        return -self.px * np.sin(theta)  + self.py * np.cos(theta)  \n    @property    \n    def Lz(self):\n        \"\"\"\n        Angular momentum around the z axis in m*eV/c\n        Lz = x * py - y * px\n        \"\"\"\n        return self.x*self.py - self.y*self.px\n\n\n    # Relativistic quantities\n    @property\n    def gamma(self):\n        \"\"\"Relativistic gamma\"\"\"\n        return self.energy/self.mass\n\n    @gamma.setter\n    def gamma(self, val):\n        beta_x = self.beta_x\n        beta_y = self.beta_y\n        beta_z = self.beta_z\n        beta = self.beta\n        gamma_new = full_array(len(self), val)\n        energy_new = gamma_new * self.mass\n        beta_new = np.sqrt(gamma_new**2 - 1)/gamma_new\n        self._data['px'] = energy_new * beta_new * beta_x / beta\n        self._data['py'] = energy_new * beta_new * beta_y / beta\n        self._data['pz'] = energy_new * beta_new * beta_z / beta\n\n    @property\n    def beta(self):\n        \"\"\"Relativistic beta\"\"\"\n        return self.p/self.energy\n    @property\n    def beta_x(self):\n        \"\"\"Relativistic beta, x component\"\"\"\n        return self.px/self.energy\n\n    @beta_x.setter\n    def beta_x(self, val):\n        self._data['px'] = full_array(len(self), val)*self.energy    \n\n    @property\n    def beta_y(self):\n        \"\"\"Relativistic beta, y component\"\"\"\n        return self.py/self.energy\n\n    @beta_y.setter\n    def beta_y(self, val):\n        self._data['py'] = full_array(len(self), val)*self.energy    \n\n    @property\n    def beta_z(self):\n        \"\"\"Relativistic beta, z component\"\"\"\n        return self.pz/self.energy\n\n    @beta_z.setter\n    def beta_z(self, val):\n        self._data['pz'] = full_array(len(self), val)*self.energy\n\n    # Normalized coordinates for x and y\n    @property \n    def x_bar(self):\n        \"\"\"Normalized x in units of sqrt(m)\"\"\"\n        return normalized_particle_coordinate(self, 'x')\n    @property     \n    def px_bar(self):\n        \"\"\"Normalized px in units of sqrt(m)\"\"\"\n        return normalized_particle_coordinate(self, 'px')    \n    @property\n    def Jx(self):\n        \"\"\"Normalized amplitude J in the x-px plane\"\"\"\n        return particle_amplitude(self, 'x')\n\n    @property \n    def y_bar(self):\n        \"\"\"Normalized y in units of sqrt(m)\"\"\"\n        return normalized_particle_coordinate(self, 'y')\n    @property     \n    def py_bar(self):\n        \"\"\"Normalized py in units of sqrt(m)\"\"\"\n        return normalized_particle_coordinate(self, 'py')\n    @property\n    def Jy(self):\n        \"\"\"Normalized amplitude J in the y-py plane\"\"\"\n        return particle_amplitude(self, 'y')    \n\n    def delta(self, key):\n        \"\"\"Attribute (array) relative to its mean\"\"\"\n        return self[key] - self.avg(key)\n\n\n    # Statistical property functions\n\n    def min(self, key):\n        \"\"\"Minimum of any key\"\"\"\n        return np.min(self[key]) # was: getattr(self, key)\n    def max(self, key):\n        \"\"\"Maximum of any key\"\"\"\n        return np.max(self[key]) \n    def ptp(self, key):\n        \"\"\"Peak-to-Peak = max - min of any key\"\"\"\n        return np.ptp(self[key])     \n\n    def avg(self, key):\n        \"\"\"Statistical average\"\"\"\n        dat = self[key]  # equivalent to self.key for accessing properties above\n        if np.isscalar(dat): \n            return dat\n        return np.average(dat, weights=self.weight)\n    def std(self, key):\n        \"\"\"Standard deviation (actually sample)\"\"\"\n        dat = self[key]\n        if np.isscalar(dat):\n            return 0\n        avg_dat = self.avg(key)\n        return np.sqrt(np.average( (dat - avg_dat)**2, weights=self.weight))\n    def cov(self, *keys):\n        \"\"\"\n        Covariance matrix from any properties\n\n        Example: \n        P = ParticleGroup(h5)\n        P.cov('x', 'px', 'y', 'py')\n\n        \"\"\"\n        dats = np.array([ self[key] for key in keys ])\n        return np.cov(dats, aweights=self.weight)\n\n    def histogramdd(self, *keys, bins=10, range=None):\n        \"\"\"\n        Wrapper for numpy.histogramdd, but accepts property names as keys.\n\n        Computes the multidimensional histogram of keys. Internally uses weights. \n\n        Example:\n            P.histogramdd('x', 'y', bins=50)\n        Returns:\n            np.array with shape 50x50, edge list \n\n        \"\"\"\n        H, edges = np.histogramdd(np.array([self[k] for k in list(keys)]).T, weights=self.weight, bins=bins, range=range)\n\n        return H, edges\n\n\n    # Beam statistics\n    @property\n    def norm_emit_x(self):\n        \"\"\"Normalized emittance in the x plane\"\"\"\n        return norm_emit_calc(self, planes=['x'])\n    @property\n    def norm_emit_y(self):       \n        \"\"\"Normalized emittance in the x plane\"\"\"\n        return norm_emit_calc(self, planes=['y'])\n    @property\n    def norm_emit_4d(self):       \n        \"\"\"Normalized emittance in the xy planes (4D)\"\"\"\n        return norm_emit_calc(self, planes=['x', 'y'])    \n\n    def twiss(self, plane='x', fraction=1, p0c=None):\n        \"\"\"\n        Returns Twiss and Dispersion dict.\n\n        plane can be:\n\n        `'x'`, `'y'`, or `'xy'`\n\n        Optionally a fraction of the particles, based on amplitiude, can be specified.\n        \"\"\"\n        d = {}\n        for p in plane:\n            d.update(particle_twiss_dispersion(self, plane=p, fraction=fraction, p0c=p0c))\n        return d\n\n    def twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False):\n        \"\"\"\n        Returns a ParticleGroup with requested Twiss parameters.\n\n        See: statistics.matched_particles\n        \"\"\"\n\n        return matched_particles(self, beta=beta, alpha=alpha, plane=plane, inplace=inplace)\n\n\n    @property\n    def in_z_coordinates(self):\n        \"\"\"\n        Returns True if all particles have the same z coordinate\n        \"\"\" \n        # Check that z are all the same\n        return len(np.unique(self.z)) == 1           \n\n    @property\n    def in_t_coordinates(self):\n        \"\"\"\n        Returns True if all particles have the same t coordinate\n        \"\"\" \n        # Check that t are all the same\n        return len(np.unique(self.t)) == 1        \n\n\n\n    @property\n    def average_current(self):\n        \"\"\"\n        Simple average `current = charge / dt` in [A], with `dt =  (max_t - min_t)`\n        If particles are in $t$ coordinates, will try` dt = (max_z - min_z)*c_light*beta_z`\n        \"\"\"\n        dt = self.t.ptp()  # ptp 'peak to peak' is max - min\n        if dt == 0:\n            # must be in t coordinates. Calc with \n            dt = self.z.ptp() / (self.avg('beta_z')*c_light)\n        return self.charge / dt\n\n    def bunching(self, wavelength):\n        \"\"\"\n        Calculate the normalized bunching parameter, which is the magnitude of the \n        complex sum of weighted exponentials at a given point.\n\n        The formula for bunching is given by:\n\n        $$\n        B(z, \\lambda) = \\frac{\\left|\\sum w_i e^{i k z_i}\\right|}{\\sum w_i}\n        $$\n\n        where:\n        - \\( z \\) is the position array,\n        - \\( \\lambda \\) is the wavelength,\n        - \\( k = \\frac{2\\pi}{\\lambda} \\) is the wave number,\n        - \\( w_i \\) are the weights.\n\n        Parameters\n        ----------\n        wavelength : float\n            Wavelength of the wave.\n\n\n        Returns\n        -------\n        complex\n            The normalized bunching parameter.\n\n        Raises\n        ------\n        ValueError\n            If `wavelength` is not a positive number.\n        \"\"\"        \n\n        if self.in_z_coordinates:\n            # Approximate z\n            z = self.t * self.avg('beta_z')*c_light\n        else:\n            z = self.z\n\n        return statistics.bunching(z, wavelength, weight=self.weight)\n\n    def __getitem__(self, key):\n        \"\"\"\n        Returns a property or statistical quantity that can be computed:\n\n        - `P['x']` returns the x array\n        - `P['sigmx_x']` returns the std(x) scalar\n        - `P['norm_emit_x']` returns the norm_emit_x scalar\n\n        Parts can also be given. Example: `P[0:10]` returns a new ParticleGroup with the first 10 elements.\n        \"\"\"\n\n        # Allow for non-string operations: \n        if not isinstance(key, str):\n            return particle_parts(self, key)\n\n        if key.startswith('cov_'):\n            subkeys = key[4:].split('__')\n            assert len(subkeys) == 2, f'Too many properties in covariance request: {key}'\n            return self.cov(*subkeys)[0,1]\n        elif key.startswith('delta_'):\n            return self.delta(key[6:])\n        elif key.startswith('sigma_'):\n            return self.std(key[6:])\n        elif key.startswith('mean_'):\n            return self.avg(key[5:])\n        elif key.startswith('min_'):\n            return self.min(key[4:])\n        elif key.startswith('max_'):\n            return self.max(key[4:])     \n        elif key.startswith('ptp_'):\n            return self.ptp(key[4:])   \n        elif 'bunching' in key:\n            wavelength = parse_bunching_str(key)\n            bunching = self.bunching(wavelength) # complex\n\n            # abs or arg (angle):\n            if 'phase_' in key:\n                return np.angle(bunching)\n            else:\n                return np.abs(bunching)\n\n        else:\n            return getattr(self, key) \n\n    def where(self, x):\n        return self[np.where(x)]\n\n    # TODO: should the user be allowed to do this?\n    #def __setitem__(self, key, value):    \n    #    assert key in self._settable_keyes, 'Error: you cannot set:'+str(key)\n    #    \n    #    if key in self._settable_array_keys:\n    #        assert len(value) == self.n_particle\n    #        self.__dict__[key] = value\n    #    elif key == \n    #        print()\n\n    # Simple 'tracking'     \n    def drift(self, delta_t):\n        \"\"\"\n        Drifts particles by time delta_t\n        \"\"\"\n        self.x = self.x + self.beta_x * c_light * delta_t\n        self.y = self.y + self.beta_y * c_light * delta_t\n        self.z = self.z + self.beta_z * c_light * delta_t\n        self.t = self.t + delta_t\n\n    def drift_to_z(self, z=None):\n\n        if z is None:\n            z = self.avg('z')\n        dt = (z - self.z) / (self.beta_z * c_light)\n        self.drift(dt)\n        # Fix z to be exactly this value\n        self.z = np.full(self.n_particle, z)\n\n\n    def drift_to_t(self, t=None):\n        \"\"\"\n        Drifts all particles to the same t\n\n        If no z is given, particles will be drifted to the average t\n        \"\"\"\n        if t is None:\n            t = self.avg('t')\n        dt = t - self.t\n        self.drift(dt)\n        # Fix t to be exactly this value\n        self.t = np.full(self.n_particle, t)\n\n\n    # -------        \n    # dict methods\n\n    # Do not do this, it breaks deepcopy\n    #def __dict__(self):\n    #    return self.data\n\n\n    @functools.wraps(bmad.particlegroup_to_bmad)\n    def to_bmad(self, p0c=None, tref=None):\n        return bmad.particlegroup_to_bmad(self, p0c=p0c, tref=tref)\n\n\n    @classmethod\n    @functools.wraps(bmad.bmad_to_particlegroup_data)\n    def from_bmad(cls, bmad_dict):\n        \"\"\"\n        Convert Bmad particle data as a dict \n        to ParticleGroup data.\n\n        See: ParticleGroup.to_bmad or particlegroup_to_bmad\n\n        Parameters\n        ----------\n        bmad_data: dict\n            Dict with keys:\n            'x'\n            'px'\n            'y'\n            'py'\n            'z'\n            'pz', \n            'charge'\n            'species',\n            'tref'\n            'state'\n\n        Returns\n        -------\n        ParticleGroup\n        \"\"\"        \n        data = bmad.bmad_to_particlegroup_data(bmad_dict)\n        return cls(data=data)\n\n    # -------\n    # Writers\n\n    @functools.wraps(write_astra)    \n    def write_astra(self, filePath, verbose=False, probe=False):\n        write_astra(self, filePath, verbose=verbose, probe=probe)\n\n    def write_bmad(self, filePath, p0c=None, t_ref=0, verbose=False):\n        bmad.write_bmad(self, filePath, p0c=p0c, t_ref=t_ref, verbose=verbose)        \n\n    def write_elegant(self, filePath, verbose=False):\n        write_elegant(self, filePath, verbose=verbose)            \n\n    def write_genesis2_beam_file(self, filePath, n_slice=None, verbose=False):\n        # Get beam columns \n        beam_columns = genesis2_beam_data(self, n_slice=n_slice)\n        # Actually write the file\n        write_genesis2_beam_file(filePath, beam_columns, verbose=verbose)  \n\n    @functools.wraps(write_genesis4_beam)          \n    def write_genesis4_beam(self, filePath, n_slice=None, return_input_str=False, verbose=False):\n        return write_genesis4_beam(self, filePath, n_slice=n_slice, return_input_str=return_input_str, verbose=verbose)\n\n    def write_genesis4_distribution(self, filePath, verbose=False):\n        write_genesis4_distribution(self, filePath, verbose=verbose)\n\n    def write_gpt(self, filePath, asci2gdf_bin=None, verbose=False):\n        write_gpt(self, filePath, asci2gdf_bin=asci2gdf_bin, verbose=verbose)    \n\n    def write_impact(self, filePath, cathode_kinetic_energy_ref=None, include_header=True, verbose=False):\n        return write_impact(self, filePath, cathode_kinetic_energy_ref=cathode_kinetic_energy_ref,\n                            include_header=include_header, verbose=verbose)          \n\n    def write_litrack(self, filePath, p0c=None, verbose=False):        \n        return write_litrack(self, outfile=filePath, p0c=p0c, verbose=verbose)      \n\n    def write_lucretia(self, filePath, ele_name='BEGINNING', t_ref=0, stop_ix=None, verbose=False):       \n        return write_lucretia(self, filePath, ele_name=ele_name, t_ref=t_ref, stop_ix=stop_ix)\n\n    def write_simion(self, filePath, color=0, flip_z_to_x=True, verbose=False):\n        return write_simion(self, filePath, verbose=verbose, color=color, flip_z_to_x=flip_z_to_x)\n\n\n    def write_opal(self, filePath, verbose=False, dist_type='emitted'):\n        return write_opal(self, filePath, verbose=verbose, dist_type=dist_type)\n\n\n    # openPMD    \n    def write(self, h5, name=None):\n        \"\"\"\n        Writes to an open h5 handle, or new file if h5 is a str.\n\n        \"\"\"\n        if isinstance(h5, str):\n            fname = os.path.expandvars(h5)\n            g = File(fname, 'w')\n            pmd_init(g, basePath='/', particlesPath='.' )\n        else:\n            g = h5\n\n        write_pmd_bunch(g, self, name=name)        \n\n\n    # Plotting\n    # --------\n    def plot(self, key1='x', key2=None,\n             bins=None,\n             *,\n             xlim=None,\n             ylim=None,\n             return_figure=False, \n             tex=True, nice=True,\n             **kwargs):\n        \"\"\"\n        1d or 2d density plot. \n\n        If one key is given, this will plot the density of that key.\n        Example:\n            .plot('x')\n\n        If two keys arg given, this will plot a 2d marginal plot.\n        Example:\n            .plot('x', 'px')\n\n\n        Parameters\n        ----------\n        particle_group: ParticleGroup\n            The object to plot\n\n        key1: str, default = 't'\n            Key to bin on the x-axis\n\n        key2: str, default = None\n            Key to bin on the y-axis. \n\n        bins: int, default = None\n           Number of bins. If None, this will use a heuristic: bins = sqrt(n_particle/4)\n\n        xlim: tuple, default = None\n            Manual setting of the x-axis limits. Note that these are in raw, unscaled units. \n\n        ylim: tuple, default = None\n            Manual setting of the y-axis limits. Note that these are in raw, unscaled units. \n\n        tex: bool, default = True\n            Use TEX for labels   \n\n        nice: bool, default = True\n            Scale to nice units\n\n        return_figure: bool, default = False\n            If true, return a matplotlib.figure.Figure object\n\n        **kwargs\n            Any additional kwargs to send to the the plot in: plt.subplots(**kwargs)\n\n\n        Returns\n        -------\n        None or fig: matplotlib.figure.Figure\n            This only returns a figure object if return_figure=T, otherwise returns None\n\n        \"\"\"\n\n        if not key2:\n            fig = density_plot(self, key=key1,\n                               bins=bins,\n                               xlim=xlim,\n                               tex=tex,\n                               nice=nice,\n                               **kwargs)\n        else:\n            fig = marginal_plot(self, key1=key1, key2=key2,\n                                bins=bins,\n                                xlim=xlim,\n                                ylim=ylim,\n                                tex=tex,\n                                nice=nice,\n                                **kwargs)\n\n        if return_figure:\n            return fig\n\n\n\n\n    def slice_statistics(self, *keys,\n                   n_slice=100,\n                   slice_key=None):\n        \"\"\"\n        Slice statistics\n\n        \"\"\"      \n\n        if slice_key is None:\n            if self.in_t_coordinates:\n                slice_key = 'z'\n\n            else:\n                slice_key = 't'  \n\n        if slice_key in ('t', 'delta_t'):\n            density_name = 'current'\n        else:\n            density_name = 'density'\n\n        keys = set(keys)\n        keys.add('mean_'+slice_key)\n        keys.add('ptp_'+slice_key)\n        keys.add('charge')\n        slice_dat = slice_statistics(self, n_slice=n_slice, slice_key=slice_key,\n                                keys=keys)\n\n        slice_dat[density_name] = slice_dat['charge']/ slice_dat['ptp_'+slice_key]\n\n        return slice_dat\n\n    def slice_plot(self, *keys,\n                   n_slice=100,\n                   slice_key=None,\n                   tex=True,\n                   nice=True,\n                   return_figure=False,\n                   xlim=None,\n                   ylim=None,\n                   **kwargs):   \n\n        fig = slice_plot(self, *keys,\n                         n_slice=n_slice,\n                         slice_key=slice_key,\n                         tex=tex,\n                         nice=nice,\n                         xlim=xlim,\n                         ylim=ylim,\n                         **kwargs)\n\n        if return_figure:\n            return fig        \n\n\n    # New constructors\n    def split(self, n_chunks = 100, key='z'):\n        return split_particles(self, n_chunks=n_chunks, key=key)\n\n    def copy(self):\n        \"\"\"Returns a deep copy\"\"\"\n        return deepcopy(self)    \n\n    @functools.wraps(resample_particles)\n    def resample(self, n=0):\n        data = resample_particles(self, n)\n        return ParticleGroup(data=data)\n\n    # Internal sorting\n    def _sort(self, key):\n        \"\"\"Sorts internal arrays by key\"\"\"\n        ixlist = np.argsort(self[key])\n        for k in self._settable_array_keys:\n            self._data[k] = self[k][ixlist]    \n\n    # Operator overloading    \n    def __add__(self, other):\n        \"\"\"\n        Overloads the + operator to join particle groups.\n        Simply calls join_particle_groups\n        \"\"\"\n        return join_particle_groups(self, other)\n\n    # \n    def __contains__(self, item):\n        \"\"\"Checks internal data\"\"\"\n        return True if item in self._data else False    \n\n    def __eq__(self, other):\n        \"\"\"Check equality of internal data\"\"\"\n        if isinstance(other, ParticleGroup):\n            for key in ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', 'id']:\n                if not np.allclose(self[key], other[key]):\n                    return False\n            return True\n\n        return NotImplemented    \n\n    def __len__(self):\n        return len(self[self._settable_array_keys[0]])\n\n    def __str__(self):\n        s = f'ParticleGroup with {self.n_particle} particles with total charge {self.charge} C'\n        return s\n\n    def __repr__(self):\n        memloc = hex(id(self))\n        return f'<ParticleGroup with {self.n_particle} particles at {memloc}>'\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.Jx","title":"Jx property","text":"

Normalized amplitude J in the x-px plane

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.Jy","title":"Jy property","text":"

Normalized amplitude J in the y-py plane

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.Lz","title":"Lz property","text":"

Angular momentum around the z axis in m*eV/c Lz = x * py - y * px

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.average_current","title":"average_current property","text":"

Simple average current = charge / dt in [A], with dt = (max_t - min_t) If particles are in \\(t\\) coordinates, will trydt = (max_z - min_z)*c_light*beta_z

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.beta","title":"beta property","text":"

Relativistic beta

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.beta_x","title":"beta_x property writable","text":"

Relativistic beta, x component

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.beta_y","title":"beta_y property writable","text":"

Relativistic beta, y component

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.beta_z","title":"beta_z property writable","text":"

Relativistic beta, z component

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.charge","title":"charge property writable","text":"

Total charge in C

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.data","title":"data property","text":"

Internal data dict

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.energy","title":"energy property","text":"

Total energy in eV

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.gamma","title":"gamma property writable","text":"

Relativistic gamma

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.higher_order_energy","title":"higher_order_energy property","text":"

Fits a quadratic (order=2) to the Energy vs. time, and returns the energy with this subtracted.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.higher_order_energy_spread","title":"higher_order_energy_spread property","text":"

Legacy syntax to compute the standard deviation of higher_order_energy.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.id","title":"id property writable","text":"

id integer

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.in_t_coordinates","title":"in_t_coordinates property","text":"

Returns True if all particles have the same t coordinate

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.in_z_coordinates","title":"in_z_coordinates property","text":"

Returns True if all particles have the same z coordinate

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.kinetic_energy","title":"kinetic_energy property","text":"

Kinetic energy in eV

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.mass","title":"mass property","text":"

Rest mass in eV

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.n_alive","title":"n_alive property","text":"

Number of alive particles, defined by status == 1

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.n_dead","title":"n_dead property","text":"

Number of alive particles, defined by status != 1

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.n_particle","title":"n_particle property","text":"

Total number of particles. Same as len

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.norm_emit_4d","title":"norm_emit_4d property","text":"

Normalized emittance in the xy planes (4D)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.norm_emit_x","title":"norm_emit_x property","text":"

Normalized emittance in the x plane

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.norm_emit_y","title":"norm_emit_y property","text":"

Normalized emittance in the x plane

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.p","title":"p property","text":"

Total momemtum in eV/c

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.pr","title":"pr property","text":"

Momentum in the radial direction in eV/c r_hat = cos(theta) xhat + sin(theta) yhat pr = p dot r_hat

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.ptheta","title":"ptheta property","text":"

Momentum in the polar theta direction. theta_hat = -sin(theta) xhat + cos(theta) yhat ptheta = p dot theta_hat Note that Lz = r*ptheta

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.px","title":"px property writable","text":"

px coordinate in [eV/c]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.px_bar","title":"px_bar property","text":"

Normalized px in units of sqrt(m)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.py","title":"py property writable","text":"

py coordinate in [eV/c]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.py_bar","title":"py_bar property","text":"

Normalized py in units of sqrt(m)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.pz","title":"pz property writable","text":"

pz coordinate in [eV/c]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.r","title":"r property","text":"

Radius in the xy plane: r = sqrt(x^2 + y^2) in m

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.species","title":"species property writable","text":"

species string

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.species_charge","title":"species_charge property","text":"

Species charge in C

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.status","title":"status property writable","text":"

status coordinate in [1]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.t","title":"t property writable","text":"

t coordinate in [s]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.theta","title":"theta property","text":"

Angle in xy plane: theta = arctan2(y, x) in radians

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.weight","title":"weight property writable","text":"

weight coordinate in [C]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.x","title":"x property writable","text":"

x coordinate in [m]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.x_bar","title":"x_bar property","text":"

Normalized x in units of sqrt(m)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.xp","title":"xp property","text":"

x slope px/pz (dimensionless)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.y","title":"y property writable","text":"

y coordinate in [m]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.y_bar","title":"y_bar property","text":"

Normalized y in units of sqrt(m)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.yp","title":"yp property","text":"

y slope py/pz (dimensionless)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.z","title":"z property writable","text":"

z coordinate in [m]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.__add__","title":"__add__(other)","text":"

Overloads the + operator to join particle groups. Simply calls join_particle_groups

Source code in pmd_beamphysics/particles.py
def __add__(self, other):\n    \"\"\"\n    Overloads the + operator to join particle groups.\n    Simply calls join_particle_groups\n    \"\"\"\n    return join_particle_groups(self, other)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.__contains__","title":"__contains__(item)","text":"

Checks internal data

Source code in pmd_beamphysics/particles.py
def __contains__(self, item):\n    \"\"\"Checks internal data\"\"\"\n    return True if item in self._data else False    \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.__eq__","title":"__eq__(other)","text":"

Check equality of internal data

Source code in pmd_beamphysics/particles.py
def __eq__(self, other):\n    \"\"\"Check equality of internal data\"\"\"\n    if isinstance(other, ParticleGroup):\n        for key in ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', 'id']:\n            if not np.allclose(self[key], other[key]):\n                return False\n        return True\n\n    return NotImplemented    \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.__getitem__","title":"__getitem__(key)","text":"

Returns a property or statistical quantity that can be computed:

  • P['x'] returns the x array
  • P['sigmx_x'] returns the std(x) scalar
  • P['norm_emit_x'] returns the norm_emit_x scalar

Parts can also be given. Example: P[0:10] returns a new ParticleGroup with the first 10 elements.

Source code in pmd_beamphysics/particles.py
def __getitem__(self, key):\n    \"\"\"\n    Returns a property or statistical quantity that can be computed:\n\n    - `P['x']` returns the x array\n    - `P['sigmx_x']` returns the std(x) scalar\n    - `P['norm_emit_x']` returns the norm_emit_x scalar\n\n    Parts can also be given. Example: `P[0:10]` returns a new ParticleGroup with the first 10 elements.\n    \"\"\"\n\n    # Allow for non-string operations: \n    if not isinstance(key, str):\n        return particle_parts(self, key)\n\n    if key.startswith('cov_'):\n        subkeys = key[4:].split('__')\n        assert len(subkeys) == 2, f'Too many properties in covariance request: {key}'\n        return self.cov(*subkeys)[0,1]\n    elif key.startswith('delta_'):\n        return self.delta(key[6:])\n    elif key.startswith('sigma_'):\n        return self.std(key[6:])\n    elif key.startswith('mean_'):\n        return self.avg(key[5:])\n    elif key.startswith('min_'):\n        return self.min(key[4:])\n    elif key.startswith('max_'):\n        return self.max(key[4:])     \n    elif key.startswith('ptp_'):\n        return self.ptp(key[4:])   \n    elif 'bunching' in key:\n        wavelength = parse_bunching_str(key)\n        bunching = self.bunching(wavelength) # complex\n\n        # abs or arg (angle):\n        if 'phase_' in key:\n            return np.angle(bunching)\n        else:\n            return np.abs(bunching)\n\n    else:\n        return getattr(self, key) \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.assign_id","title":"assign_id()","text":"

Assigns unique ids, integers from 1 to n_particle

Source code in pmd_beamphysics/particles.py
def assign_id(self):\n    \"\"\"\n    Assigns unique ids, integers from 1 to n_particle\n\n    \"\"\"\n    if 'id' not in self._settable_array_keys: \n        self._settable_array_keys.append('id')\n    self.id = np.arange(1, self['n_particle']+1)             \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.avg","title":"avg(key)","text":"

Statistical average

Source code in pmd_beamphysics/particles.py
def avg(self, key):\n    \"\"\"Statistical average\"\"\"\n    dat = self[key]  # equivalent to self.key for accessing properties above\n    if np.isscalar(dat): \n        return dat\n    return np.average(dat, weights=self.weight)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.bunching","title":"bunching(wavelength)","text":"

Calculate the normalized bunching parameter, which is the magnitude of the complex sum of weighted exponentials at a given point.

The formula for bunching is given by:

\\[ B(z, \\lambda) = \frac{\\left|\\sum w_i e^{i k z_i} ight|}{\\sum w_i} \\]

where: - \\( z \\) is the position array, - \\( \\lambda \\) is the wavelength, - \\( k = \frac{2\\pi}{\\lambda} \\) is the wave number, - \\( w_i \\) are the weights.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.bunching--parameters","title":"Parameters","text":"

wavelength : float Wavelength of the wave.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.bunching--returns","title":"Returns","text":"

complex The normalized bunching parameter.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.bunching--raises","title":"Raises","text":"

ValueError If wavelength is not a positive number.

Source code in pmd_beamphysics/particles.py
def bunching(self, wavelength):\n    \"\"\"\n    Calculate the normalized bunching parameter, which is the magnitude of the \n    complex sum of weighted exponentials at a given point.\n\n    The formula for bunching is given by:\n\n    $$\n    B(z, \\lambda) = \\frac{\\left|\\sum w_i e^{i k z_i}\\right|}{\\sum w_i}\n    $$\n\n    where:\n    - \\( z \\) is the position array,\n    - \\( \\lambda \\) is the wavelength,\n    - \\( k = \\frac{2\\pi}{\\lambda} \\) is the wave number,\n    - \\( w_i \\) are the weights.\n\n    Parameters\n    ----------\n    wavelength : float\n        Wavelength of the wave.\n\n\n    Returns\n    -------\n    complex\n        The normalized bunching parameter.\n\n    Raises\n    ------\n    ValueError\n        If `wavelength` is not a positive number.\n    \"\"\"        \n\n    if self.in_z_coordinates:\n        # Approximate z\n        z = self.t * self.avg('beta_z')*c_light\n    else:\n        z = self.z\n\n    return statistics.bunching(z, wavelength, weight=self.weight)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.copy","title":"copy()","text":"

Returns a deep copy

Source code in pmd_beamphysics/particles.py
def copy(self):\n    \"\"\"Returns a deep copy\"\"\"\n    return deepcopy(self)    \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.cov","title":"cov(*keys)","text":"

Covariance matrix from any properties

Example: P = ParticleGroup(h5) P.cov('x', 'px', 'y', 'py')

Source code in pmd_beamphysics/particles.py
def cov(self, *keys):\n    \"\"\"\n    Covariance matrix from any properties\n\n    Example: \n    P = ParticleGroup(h5)\n    P.cov('x', 'px', 'y', 'py')\n\n    \"\"\"\n    dats = np.array([ self[key] for key in keys ])\n    return np.cov(dats, aweights=self.weight)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.delta","title":"delta(key)","text":"

Attribute (array) relative to its mean

Source code in pmd_beamphysics/particles.py
def delta(self, key):\n    \"\"\"Attribute (array) relative to its mean\"\"\"\n    return self[key] - self.avg(key)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.drift","title":"drift(delta_t)","text":"

Drifts particles by time delta_t

Source code in pmd_beamphysics/particles.py
def drift(self, delta_t):\n    \"\"\"\n    Drifts particles by time delta_t\n    \"\"\"\n    self.x = self.x + self.beta_x * c_light * delta_t\n    self.y = self.y + self.beta_y * c_light * delta_t\n    self.z = self.z + self.beta_z * c_light * delta_t\n    self.t = self.t + delta_t\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.drift_to_t","title":"drift_to_t(t=None)","text":"

Drifts all particles to the same t

If no z is given, particles will be drifted to the average t

Source code in pmd_beamphysics/particles.py
def drift_to_t(self, t=None):\n    \"\"\"\n    Drifts all particles to the same t\n\n    If no z is given, particles will be drifted to the average t\n    \"\"\"\n    if t is None:\n        t = self.avg('t')\n    dt = t - self.t\n    self.drift(dt)\n    # Fix t to be exactly this value\n    self.t = np.full(self.n_particle, t)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.from_bmad","title":"from_bmad(bmad_dict) classmethod","text":"

Convert Bmad particle data as a dict to ParticleGroup data.

See: ParticleGroup.to_bmad or particlegroup_to_bmad

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.from_bmad--parameters","title":"Parameters","text":"

bmad_data: dict Dict with keys: 'x' 'px' 'y' 'py' 'z' 'pz', 'charge' 'species', 'tref' 'state'

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.from_bmad--returns","title":"Returns","text":"

ParticleGroup

Source code in pmd_beamphysics/particles.py
@classmethod\n@functools.wraps(bmad.bmad_to_particlegroup_data)\ndef from_bmad(cls, bmad_dict):\n    \"\"\"\n    Convert Bmad particle data as a dict \n    to ParticleGroup data.\n\n    See: ParticleGroup.to_bmad or particlegroup_to_bmad\n\n    Parameters\n    ----------\n    bmad_data: dict\n        Dict with keys:\n        'x'\n        'px'\n        'y'\n        'py'\n        'z'\n        'pz', \n        'charge'\n        'species',\n        'tref'\n        'state'\n\n    Returns\n    -------\n    ParticleGroup\n    \"\"\"        \n    data = bmad.bmad_to_particlegroup_data(bmad_dict)\n    return cls(data=data)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.higher_order_energy_calc","title":"higher_order_energy_calc(order=2)","text":"

Fits a polynmial with order order to the Energy vs. time, , and returns the energy with this subtracted.

Source code in pmd_beamphysics/particles.py
def higher_order_energy_calc(self, order=2):\n    \"\"\"\n    Fits a polynmial with order `order` to the Energy vs. time, , and returns the energy with this subtracted. \n    \"\"\"\n    #order=2\n    if self.std('z') < 1e-12:\n        # must be at a screen. Use t\n        t = self.t\n    else:\n        # All particles at the same time. Use z to calc t\n        t = self.z/c_light\n    energy = self.energy\n\n    best_fit_coeffs = np.polynomial.polynomial.polyfit(t, energy, order)\n    best_fit = np.polynomial.polynomial.polyval(t, best_fit_coeffs)\n    return energy - best_fit\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.histogramdd","title":"histogramdd(*keys, bins=10, range=None)","text":"

Wrapper for numpy.histogramdd, but accepts property names as keys.

Computes the multidimensional histogram of keys. Internally uses weights.

Example

P.histogramdd('x', 'y', bins=50)

Returns: np.array with shape 50x50, edge list

Source code in pmd_beamphysics/particles.py
def histogramdd(self, *keys, bins=10, range=None):\n    \"\"\"\n    Wrapper for numpy.histogramdd, but accepts property names as keys.\n\n    Computes the multidimensional histogram of keys. Internally uses weights. \n\n    Example:\n        P.histogramdd('x', 'y', bins=50)\n    Returns:\n        np.array with shape 50x50, edge list \n\n    \"\"\"\n    H, edges = np.histogramdd(np.array([self[k] for k in list(keys)]).T, weights=self.weight, bins=bins, range=range)\n\n    return H, edges\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.max","title":"max(key)","text":"

Maximum of any key

Source code in pmd_beamphysics/particles.py
def max(self, key):\n    \"\"\"Maximum of any key\"\"\"\n    return np.max(self[key]) \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.min","title":"min(key)","text":"

Minimum of any key

Source code in pmd_beamphysics/particles.py
def min(self, key):\n    \"\"\"Minimum of any key\"\"\"\n    return np.min(self[key]) # was: getattr(self, key)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.plot","title":"plot(key1='x', key2=None, bins=None, *, xlim=None, ylim=None, return_figure=False, tex=True, nice=True, **kwargs)","text":"

1d or 2d density plot.

If one key is given, this will plot the density of that key. Example: .plot('x')

If two keys arg given, this will plot a 2d marginal plot. Example: .plot('x', 'px')

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.plot--parameters","title":"Parameters","text":"

particle_group: ParticleGroup The object to plot

str, default = 't'

Key to bin on the x-axis

str, default = None

Key to bin on the y-axis.

int, default = None

Number of bins. If None, this will use a heuristic: bins = sqrt(n_particle/4)

tuple, default = None

Manual setting of the x-axis limits. Note that these are in raw, unscaled units.

tuple, default = None

Manual setting of the y-axis limits. Note that these are in raw, unscaled units.

bool, default = True

Use TEX for labels

bool, default = True

Scale to nice units

bool, default = False

If true, return a matplotlib.figure.Figure object

kwargs Any additional kwargs to send to the the plot in: plt.subplots(kwargs)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.plot--returns","title":"Returns","text":"

None or fig: matplotlib.figure.Figure This only returns a figure object if return_figure=T, otherwise returns None

Source code in pmd_beamphysics/particles.py
def plot(self, key1='x', key2=None,\n         bins=None,\n         *,\n         xlim=None,\n         ylim=None,\n         return_figure=False, \n         tex=True, nice=True,\n         **kwargs):\n    \"\"\"\n    1d or 2d density plot. \n\n    If one key is given, this will plot the density of that key.\n    Example:\n        .plot('x')\n\n    If two keys arg given, this will plot a 2d marginal plot.\n    Example:\n        .plot('x', 'px')\n\n\n    Parameters\n    ----------\n    particle_group: ParticleGroup\n        The object to plot\n\n    key1: str, default = 't'\n        Key to bin on the x-axis\n\n    key2: str, default = None\n        Key to bin on the y-axis. \n\n    bins: int, default = None\n       Number of bins. If None, this will use a heuristic: bins = sqrt(n_particle/4)\n\n    xlim: tuple, default = None\n        Manual setting of the x-axis limits. Note that these are in raw, unscaled units. \n\n    ylim: tuple, default = None\n        Manual setting of the y-axis limits. Note that these are in raw, unscaled units. \n\n    tex: bool, default = True\n        Use TEX for labels   \n\n    nice: bool, default = True\n        Scale to nice units\n\n    return_figure: bool, default = False\n        If true, return a matplotlib.figure.Figure object\n\n    **kwargs\n        Any additional kwargs to send to the the plot in: plt.subplots(**kwargs)\n\n\n    Returns\n    -------\n    None or fig: matplotlib.figure.Figure\n        This only returns a figure object if return_figure=T, otherwise returns None\n\n    \"\"\"\n\n    if not key2:\n        fig = density_plot(self, key=key1,\n                           bins=bins,\n                           xlim=xlim,\n                           tex=tex,\n                           nice=nice,\n                           **kwargs)\n    else:\n        fig = marginal_plot(self, key1=key1, key2=key2,\n                            bins=bins,\n                            xlim=xlim,\n                            ylim=ylim,\n                            tex=tex,\n                            nice=nice,\n                            **kwargs)\n\n    if return_figure:\n        return fig\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.ptp","title":"ptp(key)","text":"

Peak-to-Peak = max - min of any key

Source code in pmd_beamphysics/particles.py
def ptp(self, key):\n    \"\"\"Peak-to-Peak = max - min of any key\"\"\"\n    return np.ptp(self[key])     \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.slice_statistics","title":"slice_statistics(*keys, n_slice=100, slice_key=None)","text":"

Slice statistics

Source code in pmd_beamphysics/particles.py
def slice_statistics(self, *keys,\n               n_slice=100,\n               slice_key=None):\n    \"\"\"\n    Slice statistics\n\n    \"\"\"      \n\n    if slice_key is None:\n        if self.in_t_coordinates:\n            slice_key = 'z'\n\n        else:\n            slice_key = 't'  \n\n    if slice_key in ('t', 'delta_t'):\n        density_name = 'current'\n    else:\n        density_name = 'density'\n\n    keys = set(keys)\n    keys.add('mean_'+slice_key)\n    keys.add('ptp_'+slice_key)\n    keys.add('charge')\n    slice_dat = slice_statistics(self, n_slice=n_slice, slice_key=slice_key,\n                            keys=keys)\n\n    slice_dat[density_name] = slice_dat['charge']/ slice_dat['ptp_'+slice_key]\n\n    return slice_dat\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.std","title":"std(key)","text":"

Standard deviation (actually sample)

Source code in pmd_beamphysics/particles.py
def std(self, key):\n    \"\"\"Standard deviation (actually sample)\"\"\"\n    dat = self[key]\n    if np.isscalar(dat):\n        return 0\n    avg_dat = self.avg(key)\n    return np.sqrt(np.average( (dat - avg_dat)**2, weights=self.weight))\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.twiss","title":"twiss(plane='x', fraction=1, p0c=None)","text":"

Returns Twiss and Dispersion dict.

plane can be:

'x', 'y', or 'xy'

Optionally a fraction of the particles, based on amplitiude, can be specified.

Source code in pmd_beamphysics/particles.py
def twiss(self, plane='x', fraction=1, p0c=None):\n    \"\"\"\n    Returns Twiss and Dispersion dict.\n\n    plane can be:\n\n    `'x'`, `'y'`, or `'xy'`\n\n    Optionally a fraction of the particles, based on amplitiude, can be specified.\n    \"\"\"\n    d = {}\n    for p in plane:\n        d.update(particle_twiss_dispersion(self, plane=p, fraction=fraction, p0c=p0c))\n    return d\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.twiss_match","title":"twiss_match(beta=None, alpha=None, plane='x', p0c=None, inplace=False)","text":"

Returns a ParticleGroup with requested Twiss parameters.

See: statistics.matched_particles

Source code in pmd_beamphysics/particles.py
def twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False):\n    \"\"\"\n    Returns a ParticleGroup with requested Twiss parameters.\n\n    See: statistics.matched_particles\n    \"\"\"\n\n    return matched_particles(self, beta=beta, alpha=alpha, plane=plane, inplace=inplace)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.units","title":"units(key)","text":"

Returns the units of any key

Source code in pmd_beamphysics/particles.py
def units(self, key):\n    \"\"\"Returns the units of any key\"\"\"\n    return pg_units(key)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.write","title":"write(h5, name=None)","text":"

Writes to an open h5 handle, or new file if h5 is a str.

Source code in pmd_beamphysics/particles.py
def write(self, h5, name=None):\n    \"\"\"\n    Writes to an open h5 handle, or new file if h5 is a str.\n\n    \"\"\"\n    if isinstance(h5, str):\n        fname = os.path.expandvars(h5)\n        g = File(fname, 'w')\n        pmd_init(g, basePath='/', particlesPath='.' )\n    else:\n        g = h5\n\n    write_pmd_bunch(g, self, name=name)        \n
"},{"location":"examples/bunching/","title":"Bunching","text":"In\u00a0[1]: Copied!
from pmd_beamphysics import ParticleGroup\n%config InlineBackend.figure_format = 'retina'\n
from pmd_beamphysics import ParticleGroup %config InlineBackend.figure_format = 'retina' In\u00a0[2]: Copied!
P = ParticleGroup( 'data/bmad_particles2.h5')\nP.drift_to_t()\n\nwavelength = 0.1e-6\ndz = (P.z/wavelength % 1) * wavelength\nP.z -= dz\n
P = ParticleGroup( 'data/bmad_particles2.h5') P.drift_to_t() wavelength = 0.1e-6 dz = (P.z/wavelength % 1) * wavelength P.z -= dz In\u00a0[3]: Copied!
P.plot('z')\n
P.plot('z') In\u00a0[4]: Copied!
b = P.bunching(wavelength)\nb\n
b = P.bunching(wavelength) b Out[4]:
(1-3.2133096564432656e-16j)
In\u00a0[5]: Copied!
P['bunching_0.1e-6']\n
P['bunching_0.1e-6'] Out[5]:
1.0
In\u00a0[6]: Copied!
P['bunching_phase_0.1e-6']\n
P['bunching_phase_0.1e-6'] Out[6]:
-3.2133096564432656e-16
In\u00a0[7]: Copied!
P['bunching_0.1_um']\n
P['bunching_0.1_um'] Out[7]:
1.0
In\u00a0[8]: Copied!
import numpy as np\n\nimport matplotlib.pyplot as plt\n
import numpy as np import matplotlib.pyplot as plt In\u00a0[9]: Copied!
wavelengths = wavelength * np.linspace(0.9, 1.1, 200)\n
wavelengths = wavelength * np.linspace(0.9, 1.1, 200) In\u00a0[10]: Copied!
plt.plot(wavelengths*1e6, np.abs(list(map(P.bunching, wavelengths))))\nplt.xlabel('wavelength (\u00b5m)')\nplt.ylabel('bunching')\n
plt.plot(wavelengths*1e6, np.abs(list(map(P.bunching, wavelengths)))) plt.xlabel('wavelength (\u00b5m)') plt.ylabel('bunching') Out[10]:
Text(0, 0.5, 'bunching')
In\u00a0[11]: Copied!
P.slice_plot('bunching_0.1_um')\n
P.slice_plot('bunching_0.1_um') In\u00a0[12]: Copied!
P.slice_plot('bunching_phase_0.1_um')\n
P.slice_plot('bunching_phase_0.1_um') In\u00a0[13]: Copied!
P.in_z_coordinates\n
P.in_z_coordinates Out[13]:
False
In\u00a0[14]: Copied!
P.units('bunching_0.1_um')\n
P.units('bunching_0.1_um') Out[14]:
pmd_unit('', 1, (0, 0, 0, 0, 0, 0, 0))
In\u00a0[15]: Copied!
P.units('bunching_phase_0.1_um')\n
P.units('bunching_phase_0.1_um') Out[15]:
pmd_unit('rad', 1, (0, 0, 0, 0, 0, 0, 0))
In\u00a0[16]: Copied!
from pmd_beamphysics.statistics import bunching\n
from pmd_beamphysics.statistics import bunching In\u00a0[17]: Copied!
?bunching\n
?bunching"},{"location":"examples/bunching/#bunching","title":"Bunching\u00b6","text":"

Bunching at some wavelength $\\lambda$ for a list of particles $z$ is given by the weighted sum of complex phasors:

$$B(z, \\lambda) \\equiv \\frac{\\sum_j w_j e^{i k z_j}}{\\sum w_j} $$

where $k = 2\\pi/\\lambda$ and $w_j$ are the weights of the particles.

See for example D. Ratner's disseratation.

"},{"location":"examples/bunching/#add-bunching-to-particles","title":"Add bunching to particles\u00b6","text":"

This uses a simple method to add perfect bunching at 0.1 \u00b5m

"},{"location":"examples/bunching/#calculate-bunching","title":"Calculate bunching\u00b6","text":"

All of these methods will calculate the bunching. The first returns a complex number bunching

The string attributes return real numbers, magnitude and argument (phase):

  • 'bunching_ returns np.abs(bunching)
  • 'bunching_phase_ returns np.angle(bunching)
"},{"location":"examples/bunching/#simple-plot","title":"Simple plot\u00b6","text":""},{"location":"examples/bunching/#units","title":"Units\u00b6","text":"

Bunching is dimensionless

"},{"location":"examples/bunching/#bunching-function","title":"Bunching function\u00b6","text":"

This is the function that is used.

"},{"location":"examples/labels/","title":"TeX Labels","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n
# Useful for debugging %load_ext autoreload %autoreload 2 In\u00a0[2]: Copied!
%pylab --no-import-all inline\n%config InlineBackend.figure_format = 'retina'\n
%pylab --no-import-all inline %config InlineBackend.figure_format = 'retina'
%pylab is deprecated, use %matplotlib inline and import the required libraries.\nPopulating the interactive namespace from numpy and matplotlib\n
In\u00a0[3]: Copied!
from pmd_beamphysics.labels import texlabel, TEXLABEL, mathlabel\nfrom pmd_beamphysics.units import pg_units\n
from pmd_beamphysics.labels import texlabel, TEXLABEL, mathlabel from pmd_beamphysics.units import pg_units

This is basic function

In\u00a0[4]: Copied!
?texlabel\n
?texlabel

Example:

In\u00a0[5]: Copied!
texlabel('norm_emit_x')\n
texlabel('norm_emit_x') Out[5]:
'\\\\epsilon_{n, x}'

Example with two parts:

In\u00a0[6]: Copied!
texlabel('cov_x__px')\n
texlabel('cov_x__px') Out[6]:
'\\\\left<x, p_x\\\\right>'

Returns None if a label cannot be formed:

In\u00a0[7]: Copied!
texlabel('garbage') is None\n
texlabel('garbage') is None Out[7]:
True
In\u00a0[8]: Copied!
examples = ['cov_x__px', 'sigma_y', 'mean_Jx']\nexamples += list(TEXLABEL)\n
examples = ['cov_x__px', 'sigma_y', 'mean_Jx'] examples += list(TEXLABEL) In\u00a0[9]: Copied!
for key in examples:\n    tex = texlabel(key)\n    s = fr'${tex}$'\n    print(key ,'->', tex)\n    fig, ax = plt.subplots(figsize=(1,1))\n    plt.axis('off')\n    \n    ax.text(0.5, 0.5, s, fontsize=32)\n    plt.show()\n
for key in examples: tex = texlabel(key) s = fr'${tex}$' print(key ,'->', tex) fig, ax = plt.subplots(figsize=(1,1)) plt.axis('off') ax.text(0.5, 0.5, s, fontsize=32) plt.show()
cov_x__px -> \\left<x, p_x\\right>\n
sigma_y -> \\sigma_{ y }\n
mean_Jx -> \\left<J_x\\right>\n
t -> t\n
energy -> E\n
kinetic_energy -> E_{kinetic}\n
Ex -> E_x\n
Ey -> E_y\n
Ez -> E_z\n
Bx -> B_x\n
By -> B_y\n
Bz -> B_z\n
Etheta -> E_{\\theta}\n
Btheta -> B_{\\theta}\n
px -> p_x\n
py -> p_y\n
pz -> p_z\n
p -> p\n
pr -> p_r\n
ptheta -> p_{\\theta}\n
x -> x\n
y -> y\n
z -> z\n
r -> r\n
Jx -> J_x\n
Jy -> J_y\n
beta -> \\beta\n
beta_x -> \\beta_x\n
beta_y -> \\beta_y\n
beta_z -> \\beta_z\n
gamma -> \\gamma\n
theta -> \\theta\n
charge -> Q\n
twiss_alpha_x -> Twiss\\ \\alpha_x\n
twiss_beta_x -> Twiss\\ \\beta_x\n
twiss_gamma_x -> Twiss\\ \\gamma_x\n
twiss_eta_x -> Twiss\\ \\eta_x\n
twiss_etap_x -> Twiss\\ \\eta'_x\n
twiss_emit_x -> Twiss\\ \\epsilon_{x}\n
twiss_norm_emit_x -> Twiss\\ \\epsilon_{n, x}\n
twiss_alpha_y -> Twiss\\ \\alpha_y\n
twiss_beta_y -> Twiss\\ \\beta_y\n
twiss_gamma_y -> Twiss\\ \\gamma_y\n
twiss_eta_y -> Twiss\\ \\eta_y\n
twiss_etap_y -> Twiss\\ \\eta'_y\n
twiss_emit_y -> Twiss\\ \\epsilon_{y}\n
twiss_norm_emit_y -> Twiss\\ \\epsilon_{n, y}\n
average_current -> I_{av}\n
norm_emit_x -> \\epsilon_{n, x}\n
norm_emit_y -> \\epsilon_{n, y}\n
norm_emit_4d -> \\epsilon_{4D}\n
Lz -> L_z\n
xp -> x'\n
yp -> y'\n
x_bar -> \\overline{x}\n
px_bar -> \\overline{p_x}\n
y_bar -> \\overline{y}\n
py_bar -> \\overline{p_y}\n
In\u00a0[10]: Copied!
?mathlabel\n
?mathlabel In\u00a0[11]: Copied!
mathlabel('beta_x', units=pg_units('beta_x'))\n
mathlabel('beta_x', units=pg_units('beta_x')) Out[11]:
'$\\\\beta_x$'

Multiple keys. Note that units are not checked for consistency!

In\u00a0[12]: Copied!
mathlabel('sigma_x', 'sigma_y', units='\u00b5m')\n
mathlabel('sigma_x', 'sigma_y', units='\u00b5m') Out[12]:
'$\\\\sigma_{ x }, \\\\sigma_{ y }~(\\\\mathrm{ \u00b5m } )$'
In\u00a0[13]: Copied!
for key in examples:\n    \n    label = mathlabel(key, units=pg_units(key))\n    print(key ,'->', label)\n    fig, ax = plt.subplots(figsize=(1,1))\n    plt.axis('off')\n    \n    ax.text(0.5, 0.5, label, fontsize=32)\n    plt.show()\n
for key in examples: label = mathlabel(key, units=pg_units(key)) print(key ,'->', label) fig, ax = plt.subplots(figsize=(1,1)) plt.axis('off') ax.text(0.5, 0.5, label, fontsize=32) plt.show()
cov_x__px -> $\\left<x, p_x\\right>~(\\mathrm{ m*eV/c } )$\n
sigma_y -> $\\sigma_{ y }~(\\mathrm{ m } )$\n
mean_Jx -> $\\left<J_x\\right>~(\\mathrm{ m } )$\n
t -> $t~(\\mathrm{ s } )$\n
energy -> $E~(\\mathrm{ eV } )$\n
kinetic_energy -> $E_{kinetic}~(\\mathrm{ eV } )$\n
Ex -> $E_x~(\\mathrm{ V/m } )$\n
Ey -> $E_y~(\\mathrm{ V/m } )$\n
Ez -> $E_z~(\\mathrm{ V/m } )$\n
Bx -> $B_x~(\\mathrm{ T } )$\n
By -> $B_y~(\\mathrm{ T } )$\n
Bz -> $B_z~(\\mathrm{ T } )$\n
Etheta -> $E_{\\theta}~(\\mathrm{ V/m } )$\n
Btheta -> $B_{\\theta}~(\\mathrm{ T } )$\n
px -> $p_x~(\\mathrm{ eV/c } )$\n
py -> $p_y~(\\mathrm{ eV/c } )$\n
pz -> $p_z~(\\mathrm{ eV/c } )$\n
p -> $p~(\\mathrm{ eV/c } )$\n
pr -> $p_r~(\\mathrm{ eV/c } )$\n
ptheta -> $p_{\\theta}~(\\mathrm{ eV/c } )$\n
x -> $x~(\\mathrm{ m } )$\n
y -> $y~(\\mathrm{ m } )$\n
z -> $z~(\\mathrm{ m } )$\n
r -> $r~(\\mathrm{ m } )$\n
Jx -> $J_x~(\\mathrm{ m } )$\n
Jy -> $J_y~(\\mathrm{ m } )$\n
beta -> $\\beta$\n
beta_x -> $\\beta_x$\n
beta_y -> $\\beta_y$\n
beta_z -> $\\beta_z$\n
gamma -> $\\gamma$\n
theta -> $\\theta~(\\mathrm{ rad } )$\n
charge -> $Q~(\\mathrm{ C } )$\n
twiss_alpha_x -> $Twiss\\ \\alpha_x$\n
twiss_beta_x -> $Twiss\\ \\beta_x~(\\mathrm{ m } )$\n
twiss_gamma_x -> $Twiss\\ \\gamma_x~(\\mathrm{ /m } )$\n
twiss_eta_x -> $Twiss\\ \\eta_x~(\\mathrm{ m } )$\n
twiss_etap_x -> $Twiss\\ \\eta'_x$\n
twiss_emit_x -> $Twiss\\ \\epsilon_{x}~(\\mathrm{ m } )$\n
twiss_norm_emit_x -> $Twiss\\ \\epsilon_{n, x}~(\\mathrm{ m } )$\n
twiss_alpha_y -> $Twiss\\ \\alpha_y$\n
twiss_beta_y -> $Twiss\\ \\beta_y~(\\mathrm{ m } )$\n
twiss_gamma_y -> $Twiss\\ \\gamma_y~(\\mathrm{ /m } )$\n
twiss_eta_y -> $Twiss\\ \\eta_y~(\\mathrm{ m } )$\n
twiss_etap_y -> $Twiss\\ \\eta'_y$\n
twiss_emit_y -> $Twiss\\ \\epsilon_{y}~(\\mathrm{ m } )$\n
twiss_norm_emit_y -> $Twiss\\ \\epsilon_{n, y}~(\\mathrm{ m } )$\n
average_current -> $I_{av}~(\\mathrm{ A } )$\n
norm_emit_x -> $\\epsilon_{n, x}~(\\mathrm{ m } )$\n
norm_emit_y -> $\\epsilon_{n, y}~(\\mathrm{ m } )$\n
norm_emit_4d -> $\\epsilon_{4D}~(\\mathrm{ (m)^2 } )$\n
Lz -> $L_z~(\\mathrm{ m*eV/c } )$\n
xp -> $x'~(\\mathrm{ rad } )$\n
yp -> $y'~(\\mathrm{ rad } )$\n
x_bar -> $\\overline{x}~(\\mathrm{ \\sqrt{ m } } )$\n
px_bar -> $\\overline{p_x}~(\\mathrm{ \\sqrt{ m } } )$\n
y_bar -> $\\overline{y}~(\\mathrm{ \\sqrt{ m } } )$\n
py_bar -> $\\overline{p_y}~(\\mathrm{ \\sqrt{ m } } )$\n
"},{"location":"examples/labels/#tex-labels","title":"TeX Labels\u00b6","text":"

TeX labels for most attributes can be retrieved.

"},{"location":"examples/labels/#math-label-with-unit","title":"Math label with unit\u00b6","text":"

mathlabel provides the complete string with optional units

"},{"location":"examples/normalized_coordinates/","title":"Simple Normalized Coordinates","text":"In\u00a0[1]: Copied!
from pmd_beamphysics import ParticleGroup\nfrom pmd_beamphysics.statistics import A_mat_calc, twiss_calc, normalized_particle_coordinate\nimport numpy as np\n\n\nimport matplotlib.pyplot as plt\nimport matplotlib\n%matplotlib inline\n%config InlineBackend.figure_format = 'retina'\nmatplotlib.rcParams['figure.figsize'] = (4,4)\n
from pmd_beamphysics import ParticleGroup from pmd_beamphysics.statistics import A_mat_calc, twiss_calc, normalized_particle_coordinate import numpy as np import matplotlib.pyplot as plt import matplotlib %matplotlib inline %config InlineBackend.figure_format = 'retina' matplotlib.rcParams['figure.figsize'] = (4,4) In\u00a0[2]: Copied!
help(A_mat_calc)\n
help(A_mat_calc)
Help on function A_mat_calc in module pmd_beamphysics.statistics:\n\nA_mat_calc(beta, alpha, inverse=False)\n    Returns the 1D normal form matrix from twiss parameters beta and alpha\n    \n        A =   sqrt(beta)         0 \n             -alpha/sqrt(beta)   1/sqrt(beta) \n    \n    If inverse, the inverse will be returned:\n    \n        A^-1 =  1/sqrt(beta)     0 \n                alpha/sqrt(beta) sqrt(beta)     \n    \n    This corresponds to the linear normal form decomposition:\n    \n        M = A . Rot(theta) . A^-1\n    \n    with a clockwise rotation matrix:\n    \n        Rot(theta) =  cos(theta) sin(theta)\n                     -sin(theta) cos(theta)\n    \n    In the Bmad manual, G_q (Bmad) = A (here) in the Linear Optics chapter.\n    \n    A^-1 can be used to form normalized coordinates: \n        x_bar, px_bar   = A^-1 . (x, px)\n\n

Make phase space circle. This will represent some normalized coordinates:

In\u00a0[3]: Copied!
theta = np.linspace(0, np.pi*2, 100)\nzvec0 = np.array([np.cos(theta), np.sin(theta)])\nplt.scatter(*zvec0)\n
theta = np.linspace(0, np.pi*2, 100) zvec0 = np.array([np.cos(theta), np.sin(theta)]) plt.scatter(*zvec0) Out[3]:
<matplotlib.collections.PathCollection at 0x7fe784cab430>

Make a 'beam' in 'lab coordinates':

In\u00a0[4]: Copied!
MYMAT = np.array([[10, 0],[-3, 5]])\nzvec = np.matmul(MYMAT , zvec0)\nplt.scatter(*zvec)\n
MYMAT = np.array([[10, 0],[-3, 5]]) zvec = np.matmul(MYMAT , zvec0) plt.scatter(*zvec) Out[4]:
<matplotlib.collections.PathCollection at 0x7fe77bc14f70>

With a beam, $\\alpha$ and $\\beta$ can be determined from moments of the covariance matrix.

In\u00a0[5]: Copied!
help(twiss_calc)\n
help(twiss_calc)
Help on function twiss_calc in module pmd_beamphysics.statistics:\n\ntwiss_calc(sigma_mat2)\n    Calculate Twiss parameters from the 2D sigma matrix (covariance matrix):\n    sigma_mat = <x,x>   <x, p>\n                <p, x>  <p, p>\n    \n    This is a simple calculation. Makes no assumptions about units. \n    \n    alpha = -<x, p>/emit\n    beta  =  <x, x>/emit\n    gamma =  <p, p>/emit\n    emit = det(sigma_mat)\n\n

Calculate a sigma matrix, get the determinant:

In\u00a0[6]: Copied!
sigma_mat2 = np.cov(*zvec)\nnp.linalg.det(sigma_mat2)\n
sigma_mat2 = np.cov(*zvec) np.linalg.det(sigma_mat2) Out[6]:
637.5000000000003

Get some twiss:

In\u00a0[7]: Copied!
twiss = twiss_calc(sigma_mat2)\ntwiss\n
twiss = twiss_calc(sigma_mat2) twiss Out[7]:
{'alpha': 0.6059702963017245,\n 'beta': 2.0199009876724157,\n 'gamma': 0.6768648603788545,\n 'emit': 25.248762345905202}

Analyzing matrices:

In\u00a0[8]: Copied!
A = A_mat_calc(twiss['beta'], twiss['alpha'])\nA_inv = A_mat_calc(twiss['beta'], twiss['alpha'], inverse=True)\n
A = A_mat_calc(twiss['beta'], twiss['alpha']) A_inv = A_mat_calc(twiss['beta'], twiss['alpha'], inverse=True)

A_inv turns this back into a circle:

In\u00a0[9]: Copied!
zvec2 = np.matmul(A_inv, zvec)\nplt.scatter(*zvec2)\n
zvec2 = np.matmul(A_inv, zvec) plt.scatter(*zvec2) Out[9]:
<matplotlib.collections.PathCollection at 0x7fe77bb97280>
In\u00a0[10]: Copied!
twiss_calc(np.cov(*zvec2))\n
twiss_calc(np.cov(*zvec2)) Out[10]:
{'alpha': 1.4672219335410167e-16,\n 'beta': 1.0,\n 'gamma': 1.0000000000000002,\n 'emit': 25.24876234590519}
In\u00a0[11]: Copied!
matplotlib.rcParams['figure.figsize'] = (13,8) # Reset plot\n
matplotlib.rcParams['figure.figsize'] = (13,8) # Reset plot In\u00a0[12]: Copied!
help(normalized_particle_coordinate)\n
help(normalized_particle_coordinate)
Help on function normalized_particle_coordinate in module pmd_beamphysics.statistics:\n\nnormalized_particle_coordinate(particle_group, key, twiss=None, mass_normalize=True)\n    Returns a single normalized coordinate array from a ParticleGroup\n    \n    Position or momentum is determined by the key. \n    If the key starts with 'p', it is a momentum, else it is a position,\n    and the\n    \n    Intended use is for key to be one of:\n        x, px, y py\n        \n    and the corresponding normalized coordinates are named with suffix _bar, i.e.:\n        x_bar, px_bar, y_bar, py_bar\n    \n    If mass_normalize (default=True), the momentum will be divided by the mass, so that the units are sqrt(m).\n    \n    These are related to action-angle coordinates\n        J: amplitude\n        phi: phase\n        \n        x_bar =  sqrt(2 J) cos(phi)\n        px_bar = sqrt(2 J) sin(phi)\n        \n    So therefore:\n        J = (x_bar^2 + px_bar^2)/2\n        phi = arctan(px_bar/x_bar)\n    and:        \n        <J> = norm_emit_x\n     \n     Note that the center may need to be subtracted in this case.\n\n

Get some example particles, with a typical transverse phase space plot:

In\u00a0[13]: Copied!
P = ParticleGroup('data/bmad_particles2.h5')\nP.plot('x', 'px')\n
P = ParticleGroup('data/bmad_particles2.h5') P.plot('x', 'px')

If no twiss is given, then the analyzing matrix is computed from the beam itself:

In\u00a0[14]: Copied!
normalized_particle_coordinate(P, 'x', twiss=None)\n
normalized_particle_coordinate(P, 'x', twiss=None) Out[14]:
array([-4.83384095e-04,  9.99855846e-04,  7.35820860e-05, ...,\n       -7.48265408e-05,  4.77803205e-05, -4.18053319e-04])

This is equivelent:

In\u00a0[15]: Copied!
normalized_particle_coordinate(P, 'x', twiss=twiss_calc(P.cov('x', 'px')), mass_normalize=False)/np.sqrt(P.mass)\n
normalized_particle_coordinate(P, 'x', twiss=twiss_calc(P.cov('x', 'px')), mass_normalize=False)/np.sqrt(P.mass) Out[15]:
array([-4.83384095e-04,  9.99855846e-04,  7.35820860e-05, ...,\n       -7.48265408e-05,  4.77803205e-05, -4.18053319e-04])

And is given as a property:

In\u00a0[16]: Copied!
P.x_bar\n
P.x_bar Out[16]:
array([-4.83384095e-04,  9.99855846e-04,  7.35820860e-05, ...,\n       -7.48265408e-05,  4.77803205e-05, -4.18053319e-04])

The amplitude is defined as:

In\u00a0[17]: Copied!
(P.x_bar**2 + P.px_bar**2)/2\n
(P.x_bar**2 + P.px_bar**2)/2 Out[17]:
array([1.16831464e-07, 5.85751290e-07, 3.26876598e-07, ...,\n       3.25916449e-07, 2.21097254e-07, 2.73364174e-07])

This is also given as a property:

In\u00a0[18]: Copied!
P.Jx\n
P.Jx Out[18]:
array([1.16831464e-07, 5.85751290e-07, 3.26876598e-07, ...,\n       3.25916449e-07, 2.21097254e-07, 2.73364174e-07])

Note the mass normalization is the same:

In\u00a0[19]: Copied!
P.Jx.mean(), P['mean_Jx'], P['norm_emit_x']\n
P.Jx.mean(), P['mean_Jx'], P['norm_emit_x'] Out[19]:
(4.883790126887025e-07, 4.883790126887027e-07, 4.881047612307434e-07)

This is now nice and roundish:

In\u00a0[20]: Copied!
P.plot('x_bar', 'px_bar')\n
P.plot('x_bar', 'px_bar')

Jy also works. This gives some sense of where the emittance is larger.

In\u00a0[21]: Copied!
P.plot('t', 'Jy')\n
P.plot('t', 'Jy')

Sort by Jx:

In\u00a0[22]: Copied!
P = P[np.argsort(P.Jx)]\n
P = P[np.argsort(P.Jx)]

Now particles are ordered:

In\u00a0[23]: Copied!
plt.plot(P.Jx)\n
plt.plot(P.Jx) Out[23]:
[<matplotlib.lines.Line2D at 0x7fe77b10d3d0>]

This can be used to calculate the 95% emittance:

In\u00a0[24]: Copied!
P[0:int(0.95*len(P))]['norm_emit_x']\n
P[0:int(0.95*len(P))]['norm_emit_x'] Out[24]:
3.7399940158442355e-07
In\u00a0[25]: Copied!
def twiss_match(x, p, beta0=1, alpha0=0, beta1=1, alpha1=0):\n    \"\"\"\n    Simple Twiss matching. \n    \n    Takes positions x and momenta p, and transforms them according to \n    initial Twiss parameters:\n        beta0, alpha0 \n    into final  Twiss parameters:\n        beta1, alpha1\n        \n    This is simply the matrix ransformation:    \n        xnew  = (   sqrt(beta1/beta0)                  0                 ) . ( x )\n        pnew    (  (alpha0-alpha1)/sqrt(beta0*beta1)   sqrt(beta0/beta1) )   ( p ) \n        \n\n    Returns new x, p\n    \n    \"\"\"\n    m11 = np.sqrt(beta1/beta0)\n    m21 = (alpha0-alpha1)/np.sqrt(beta0*beta1)\n    \n    xnew = x * m11\n    pnew = x * m21 + p / m11\n    \n    return xnew, pnew\n
def twiss_match(x, p, beta0=1, alpha0=0, beta1=1, alpha1=0): \"\"\" Simple Twiss matching. Takes positions x and momenta p, and transforms them according to initial Twiss parameters: beta0, alpha0 into final Twiss parameters: beta1, alpha1 This is simply the matrix ransformation: xnew = ( sqrt(beta1/beta0) 0 ) . ( x ) pnew ( (alpha0-alpha1)/sqrt(beta0*beta1) sqrt(beta0/beta1) ) ( p ) Returns new x, p \"\"\" m11 = np.sqrt(beta1/beta0) m21 = (alpha0-alpha1)/np.sqrt(beta0*beta1) xnew = x * m11 pnew = x * m21 + p / m11 return xnew, pnew

Get some Twiss:

In\u00a0[26]: Copied!
T0 = twiss_calc(P.cov('x', 'xp'))\nT0\n
T0 = twiss_calc(P.cov('x', 'xp')) T0 Out[26]:
{'alpha': -0.7756418199427733,\n 'beta': 9.761411505651415,\n 'gamma': 0.16407670467707158,\n 'emit': 3.127519925999214e-11}

Make a copy and maniplulate:

In\u00a0[27]: Copied!
P2 = P.copy()\nP2.x, P2.px = twiss_match(P.x, P.px/P['mean_p'], beta0=T0['beta'], alpha0=T0['alpha'], beta1=9, alpha1=-2)\nP2.px *= P['mean_p']\n
P2 = P.copy() P2.x, P2.px = twiss_match(P.x, P.px/P['mean_p'], beta0=T0['beta'], alpha0=T0['alpha'], beta1=9, alpha1=-2) P2.px *= P['mean_p'] In\u00a0[28]: Copied!
twiss_calc(P2.cov('x', 'xp'))\n
twiss_calc(P2.cov('x', 'xp')) Out[28]:
{'alpha': -1.9995993293102663,\n 'beta': 9.00102737307016,\n 'gamma': 0.5553141070021174,\n 'emit': 3.12716295233219e-11}

This is a dedicated routine:

In\u00a0[29]: Copied!
def matched_particles(particle_group, beta=None, alpha=None, plane='x', p0c=None, inplace=False):\n    \"\"\"\n    Perfoms simple Twiss 'matching' by applying a linear transformation to\n        x, px if plane == 'x', or x, py if plane == 'y'\n    \n    Returns a new ParticleGroup\n    \n    If inplace, a copy will not be made, and changes will be done in place. \n    \n    \"\"\"\n    \n    assert plane in ('x', 'y'), f'Invalid plane: {plane}'\n    \n    if inplace:\n        P = particle_group\n    else:\n        P = particle_group.copy()\n    \n    if not p0c:\n        p0c = P['mean_p']\n    \n\n    # Use Bmad-style coordinates.\n    # Get plane. \n    if plane == 'x':\n        x = P.x\n        p = P.px/p0c\n    else:\n        x = P.y\n        p = P.py/p0c\n        \n    # Get current Twiss\n    tx = twiss_calc(np.cov(x, p, aweights=P.weight))\n    \n    # If not specified, just fill in the current value.\n    if alpha is None:\n        alpha = tx['alpha']\n    if beta is None:\n        beta = tx['beta']\n    \n    # New coordinates\n    xnew, pnew = twiss_match(x, p, beta0=tx['beta'], alpha0=tx['alpha'], beta1=beta, alpha1=alpha)\n    \n    # Set\n    if plane == 'x':\n        P.x = xnew\n        P.px = pnew*p0c\n    else:\n        P.y = xnew\n        P.py = pnew*p0c\n        \n    return P\n    \n    \n# Check    \nP3 = matched_particles(P, beta=None, alpha=-4, plane='y')\nP.twiss(plane='y'), P3.twiss(plane='y')\n
def matched_particles(particle_group, beta=None, alpha=None, plane='x', p0c=None, inplace=False): \"\"\" Perfoms simple Twiss 'matching' by applying a linear transformation to x, px if plane == 'x', or x, py if plane == 'y' Returns a new ParticleGroup If inplace, a copy will not be made, and changes will be done in place. \"\"\" assert plane in ('x', 'y'), f'Invalid plane: {plane}' if inplace: P = particle_group else: P = particle_group.copy() if not p0c: p0c = P['mean_p'] # Use Bmad-style coordinates. # Get plane. if plane == 'x': x = P.x p = P.px/p0c else: x = P.y p = P.py/p0c # Get current Twiss tx = twiss_calc(np.cov(x, p, aweights=P.weight)) # If not specified, just fill in the current value. if alpha is None: alpha = tx['alpha'] if beta is None: beta = tx['beta'] # New coordinates xnew, pnew = twiss_match(x, p, beta0=tx['beta'], alpha0=tx['alpha'], beta1=beta, alpha1=alpha) # Set if plane == 'x': P.x = xnew P.px = pnew*p0c else: P.y = xnew P.py = pnew*p0c return P # Check P3 = matched_particles(P, beta=None, alpha=-4, plane='y') P.twiss(plane='y'), P3.twiss(plane='y') Out[29]:
({'alpha_y': 0.9058122605337396,\n  'beta_y': 14.683316714787708,\n  'gamma_y': 0.12398396674913406,\n  'emit_y': 3.214301173354259e-11,\n  'eta_y': -0.0001221701145704683,\n  'etap_y': -3.1414468851691344e-07,\n  'norm_emit_y': 5.016420878150227e-07},\n {'alpha_y': -3.9999679865267384,\n  'beta_y': 14.683316715010363,\n  'gamma_y': 1.1577591237176261,\n  'emit_y': 3.214301173290241e-11,\n  'eta_y': -0.00012217012301264445,\n  'etap_y': -4.113188244396527e-05,\n  'norm_emit_y': 5.016420878133589e-07})

These functions are in statistics:

In\u00a0[30]: Copied!
from pmd_beamphysics.statistics import twiss_match, matched_particles\n
from pmd_beamphysics.statistics import twiss_match, matched_particles"},{"location":"examples/normalized_coordinates/#simple-normalized-coordinates","title":"Simple Normalized Coordinates\u00b6","text":"

1D normalized coordinates originate from the normal form decomposition, where the transfer matrix that propagates phase space coordinates $(x, p)$ is decomposed as

$M = A \\cdot R(\\theta) \\cdot A^{-1}$

And the matrix $A$ can be parameterized as

A = $\\begin{pmatrix}\\sqrt{\\beta} & 0\\\\-\\alpha/\\sqrt{\\beta} & 1/\\sqrt{\\beta}\\end{pmatrix}$

"},{"location":"examples/normalized_coordinates/#twiss-parameters","title":"Twiss parameters\u00b6","text":"

Effective Twiss parameters can be calculated from the second order moments of the particles.

This does not change the phase space area.

"},{"location":"examples/normalized_coordinates/#x_bar-px_bar-jx-etc","title":"x_bar, px_bar, Jx, etc.\u00b6","text":"

These are essentially action-angle coordinates, calculated by using the an analyzing twiss dict

"},{"location":"examples/normalized_coordinates/#simple-matching","title":"Simple 'matching'\u00b6","text":"

Often a beam needs to be 'matched' for tracking in some program.

This is a 'faked' tranformation that ultimately would need to be realized by a focusing system.

"},{"location":"examples/particle_examples/","title":"openPMD beamphysics examples","text":"In\u00a0[1]: Copied!
# Nicer plotting\nimport matplotlib\nimport matplotlib.pyplot as plt\n%config InlineBackend.figure_format = 'retina'\nmatplotlib.rcParams['figure.figsize'] = (8,4)\n
# Nicer plotting import matplotlib import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' matplotlib.rcParams['figure.figsize'] = (8,4) In\u00a0[2]: Copied!
from pmd_beamphysics import ParticleGroup\n
from pmd_beamphysics import ParticleGroup In\u00a0[3]: Copied!
P = ParticleGroup( 'data/bmad_particles2.h5')\nP\n
P = ParticleGroup( 'data/bmad_particles2.h5') P Out[3]:
<ParticleGroup with 100000 particles at 0x7fd1710a0400>
In\u00a0[4]: Copied!
P.energy\n
P.energy Out[4]:
array([8.00032916e+09, 7.97408124e+09, 7.97338447e+09, ...,\n       7.97531701e+09, 7.97163591e+09, 7.97170403e+09])
In\u00a0[5]: Copied!
P['mean_energy'], P.units('mean_energy')\n
P['mean_energy'], P.units('mean_energy') Out[5]:
(7974939710.08345, pmd_unit('eV', 1.602176634e-19, (2, 1, -2, 0, 0, 0, 0)))
In\u00a0[6]: Copied!
P.where(P.x < P['mean_x'])\n
P.where(P.x < P['mean_x']) Out[6]:
<ParticleGroup with 50082 particles at 0x7fd1b8c11340>
In\u00a0[7]: Copied!
a = P.plot('x', 'px', figsize=(8,8))\n
a = P.plot('x', 'px', figsize=(8,8)) In\u00a0[8]: Copied!
P.write_elegant('elegant_particles.txt', verbose=True)\n
P.write_elegant('elegant_particles.txt', verbose=True)
writing 100000 particles to elegant_particles.txt\n

x positions, in meters

In\u00a0[9]: Copied!
P.x\n
P.x Out[9]:
array([-1.20890504e-05,  2.50055966e-05,  1.84022924e-06, ...,\n       -1.87135206e-06,  1.19494768e-06, -1.04551798e-05])

relativistic gamma, calculated on the fly

In\u00a0[10]: Copied!
P.gamma\n
P.gamma Out[10]:
array([15656.25362205, 15604.88772858, 15603.52416601, ...,\n       15607.30607107, 15600.10233296, 15600.23563914])

Both are allowed

In\u00a0[11]: Copied!
len(P), P['n_particle']\n
len(P), P['n_particle'] Out[11]:
(100000, 100000)

Statistics on any of these. Note that these properly use the .weight array.

In\u00a0[12]: Copied!
P.avg('gamma'), P.std('p')\n
P.avg('gamma'), P.std('p') Out[12]:
(15606.56770446094, 7440511.955100455)

Covariance matrix of any list of keys

In\u00a0[13]: Copied!
P.cov('x', 'px', 'y', 'kinetic_energy')\n
P.cov('x', 'px', 'y', 'kinetic_energy') Out[13]:
array([[ 3.05290090e-10,  1.93582323e-01,  2.14462846e-12,\n        -3.94841065e+00],\n       [ 1.93582323e-01,  3.26525376e+08, -2.44058325e-05,\n        -5.36815277e+08],\n       [ 2.14462846e-12, -2.44058325e-05,  4.71979014e-10,\n        -8.48100957e-01],\n       [-3.94841065e+00, -5.36815277e+08, -8.48100957e-01,\n         5.53617715e+13]])

These can all be accessed with brackets. sigma_ and mean_ are also allowed

In\u00a0[14]: Copied!
P['sigma_x'], P['sigma_energy'], P['min_y'], P['norm_emit_x'], P['norm_emit_4d']\n
P['sigma_x'], P['sigma_energy'], P['min_y'], P['norm_emit_x'], P['norm_emit_4d'] Out[14]:
(1.7472465109340715e-05,\n 7440511.939853717,\n -0.00017677380499644412,\n 4.881047612307434e-07,\n 2.4484888474798633e-13)

Covariance has a special syntax, items separated by __

In\u00a0[15]: Copied!
P['cov_x__kinetic_energy']\n
P['cov_x__kinetic_energy'] Out[15]:
-3.9484106461190627

n-dimensional histogram. This is a wrapper for numpy.histogramdd

In\u00a0[16]: Copied!
H, edges = P.histogramdd('t', 'delta_pz', bins=(5,10))\nH.shape, edges\n
H, edges = P.histogramdd('t', 'delta_pz', bins=(5,10)) H.shape, edges Out[16]:
((5, 10),\n [array([5.16387938e-06, 5.16387943e-06, 5.16387948e-06, 5.16387953e-06,\n         5.16387958e-06, 5.16387963e-06]),\n  array([-24476455.61834908, -17729298.92490654, -10982142.231464  ,\n          -4234985.53802147,   2512171.15542107,   9259327.8488636 ,\n          16006484.54230614,  22753641.23574867,  29500797.92919121,\n          36247954.62263375,  42995111.31607628])])
In\u00a0[17]: Copied!
ss = P.slice_statistics('norm_emit_x')\nss.keys()\n
ss = P.slice_statistics('norm_emit_x') ss.keys() Out[17]:
dict_keys(['charge', 'mean_t', 'norm_emit_x', 'ptp_t', 'current'])

Multiple keys can also be accepted:

In\u00a0[18]: Copied!
ss = P.slice_statistics('norm_emit_x', 'norm_emit_y', 'twiss')\nss.keys()\n
ss = P.slice_statistics('norm_emit_x', 'norm_emit_y', 'twiss') ss.keys() Out[18]:
dict_keys(['norm_emit_y', 'charge', 'norm_emit_x', 'mean_t', 'twiss', 'ptp_t', 'twiss_alpha_y', 'twiss_beta_y', 'twiss_gamma_y', 'twiss_emit_y', 'twiss_eta_y', 'twiss_etap_y', 'twiss_norm_emit_y', 'twiss_alpha_x', 'twiss_beta_x', 'twiss_gamma_x', 'twiss_emit_x', 'twiss_eta_x', 'twiss_etap_x', 'twiss_norm_emit_x', 'current'])

Note that for a slice key X, the method will also calculate mean_X, ptp_X, as charge so that a density calculated from these. In the special case of X=t, the density will be labeled as current according to common convention.

In\u00a0[19]: Copied!
P.twiss('x')\n
P.twiss('x') Out[19]:
{'alpha_x': -0.7764646310859605,\n 'beta_x': 9.758458404204259,\n 'gamma_x': 0.16425722762079686,\n 'emit_x': 3.1255806600595395e-11,\n 'eta_x': -0.0005687740085942673,\n 'etap_x': -9.69649743612097e-06,\n 'norm_emit_x': 4.877958608683612e-07}

95% emittance calculation, x and y

In\u00a0[20]: Copied!
P.twiss('xy', fraction=0.95)\n
P.twiss('xy', fraction=0.95) Out[20]:
{'alpha_x': -0.765323995145385,\n 'beta_x': 9.233496626510156,\n 'gamma_x': 0.1717356795249758,\n 'emit_x': 2.3954681527227138e-11,\n 'eta_x': -0.0004717155629444416,\n 'etap_x': -1.6006449526750024e-05,\n 'norm_emit_x': 3.738408555668476e-07,\n 'alpha_y': 0.9776071851091841,\n 'beta_y': 14.39339077533324,\n 'gamma_y': 0.13587596132863441,\n 'emit_y': 2.342927040318514e-11,\n 'eta_y': -4.3316354305934096e-05,\n 'etap_y': -6.000905618300805e-07,\n 'norm_emit_y': 3.656364435837357e-07}

This makes new particles:

In\u00a0[21]: Copied!
P2 = P.twiss_match(beta=30, alpha=-3, plane = 'x')\nP2.twiss('x')\n
P2 = P.twiss_match(beta=30, alpha=-3, plane = 'x') P2.twiss('x') Out[21]:
{'alpha_x': -2.9996935644621994,\n 'beta_x': 29.99130803172276,\n 'gamma_x': 0.3333686370097817,\n 'emit_x': 3.1255806601163326e-11,\n 'eta_x': -0.000997118623912468,\n 'etap_x': -7.944656701598246e-05,\n 'norm_emit_x': 4.877958608785158e-07}
In\u00a0[22]: Copied!
P.resample()\n
P.resample() Out[22]:
<ParticleGroup with 100000 particles at 0x7fd170b50310>

With n > 0, particles will be subsampled. Note that this also works for differently weighed particles.

In\u00a0[23]: Copied!
P.resample(1000)\n
P.resample(1000) Out[23]:
<ParticleGroup with 1000 particles at 0x7fd1a0f42040>
In\u00a0[24]: Copied!
P.resample(1000).plot('x', 'px', bins=100)\n
P.resample(1000).plot('x', 'px', bins=100) In\u00a0[25]: Copied!
P.units('x'), P.units('energy'), P.units('norm_emit_x'), P.units('cov_x__kinetic_energy'), P.units('norm_emit_4d')\n
P.units('x'), P.units('energy'), P.units('norm_emit_x'), P.units('cov_x__kinetic_energy'), P.units('norm_emit_4d') Out[25]:
(pmd_unit('m', 1, (1, 0, 0, 0, 0, 0, 0)),\n pmd_unit('eV', 1.602176634e-19, (2, 1, -2, 0, 0, 0, 0)),\n pmd_unit('m', 1, (1, 0, 0, 0, 0, 0, 0)),\n pmd_unit('m*eV', 1.602176634e-19, (3, 1, -2, 0, 0, 0, 0)),\n pmd_unit('(m)^2', 1, (2, 0, 0, 0, 0, 0, 0)))
In\u00a0[26]: Copied!
P.units('mean_energy')\n
P.units('mean_energy') Out[26]:
pmd_unit('eV', 1.602176634e-19, (2, 1, -2, 0, 0, 0, 0))
In\u00a0[27]: Copied!
str(P.units('cov_x__kinetic_energy'))\n
str(P.units('cov_x__kinetic_energy')) Out[27]:
'm*eV'
In\u00a0[28]: Copied!
P.std('z'), P.std('t')\n
P.std('z'), P.std('t') Out[28]:
(0.0, 2.4466662184814374e-14)

Get the central time:

In\u00a0[29]: Copied!
t0 = P.avg('t')\nt0\n
t0 = P.avg('t') t0 Out[29]:
5.163879459127423e-06

Drift all particles to this time. This operates in-place:

In\u00a0[30]: Copied!
P.drift_to_t(t0)\n
P.drift_to_t(t0)

Now these are at different z, and the same t:

In\u00a0[31]: Copied!
P.std('z'), P.avg('t'), set(P.t)\n
P.std('z'), P.avg('t'), set(P.t) Out[31]:
(7.334920780350132e-06, 5.163879459127425e-06, {5.163879459127423e-06})
In\u00a0[32]: Copied!
P.status[0:10] = 0\nP.status, P.n_alive, P.n_dead\n
P.status[0:10] = 0 P.status, P.n_alive, P.n_dead Out[32]:
(array([0, 0, 0, ..., 1, 1, 1], dtype=int32), 99990, 10)

There is a .where convenience routine to make selections easier:

In\u00a0[33]: Copied!
P0 = P.where(P.status==0)\nP1 = P.where(P.status==1)\nlen(P0), P0.charge, P1.charge\n
P0 = P.where(P.status==0) P1 = P.where(P.status==1) len(P0), P0.charge, P1.charge Out[33]:
(10, 2.4999999999999994e-14, 2.4997499999999996e-10)

Copy is a deep copy:

In\u00a0[34]: Copied!
P2 = P1.copy()\n
P2 = P1.copy()

Charge can also be set. This will re-scale the weight array:

In\u00a0[35]: Copied!
P2.charge = 9.8765e-12\nP1.weight[0:2], P2.weight[0:2], P2.charge\n
P2.charge = 9.8765e-12 P1.weight[0:2], P2.weight[0:2], P2.charge Out[35]:
(array([2.5e-15, 2.5e-15]),\n array([9.87748775e-17, 9.87748775e-17]),\n 9.876499999999997e-12)

Some codes provide ids for particles. If not, you can assign an id.

In\u00a0[36]: Copied!
'id' in P2\n
'id' in P2 Out[36]:
False

This will assign an id if none exists.

In\u00a0[37]: Copied!
P2.id, 'id' in P2\n
P2.id, 'id' in P2 Out[37]:
(array([    1,     2,     3, ..., 99988, 99989, 99990]), True)
In\u00a0[38]: Copied!
import h5py\nimport numpy as np\n
import h5py import numpy as np In\u00a0[39]: Copied!
newh5file = 'particles.h5'\n\nwith h5py.File(newh5file, 'w') as h5:\n    P.write(h5)\n    \nwith h5py.File(newh5file, 'r') as h5:\n    P2 = ParticleGroup(h5)\n
newh5file = 'particles.h5' with h5py.File(newh5file, 'w') as h5: P.write(h5) with h5py.File(newh5file, 'r') as h5: P2 = ParticleGroup(h5)

Check if all are the same:

In\u00a0[40]: Copied!
for key in ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', 'id']:\n    same = np.all(P[key] == P2[key])\n    print(key, same)\n
for key in ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', 'id']: same = np.all(P[key] == P2[key]) print(key, same)
x True\npx True\ny True\npy True\nz True\npz True\nt True\nstatus True\nweight True\nid True\n

This does the same check:

In\u00a0[41]: Copied!
P2 == P\n
P2 == P Out[41]:
True

Write Astra-style particles

In\u00a0[42]: Copied!
P.write_astra('astra.dat')\n
P.write_astra('astra.dat') In\u00a0[43]: Copied!
!head astra.dat\n
!head astra.dat
  5.358867254236e-07  -2.266596025469e-08   2.743173452837e-13   5.432293116193e+02   1.634894200076e+01   7.974939693676e+09   5.163879459127e+03   0.000000000000e+00    1   -1\r\n -1.208904511904e-05   2.743402818288e-05  -5.473095269153e-06  -7.699432808273e+03   1.073320266862e+04   2.538945179648e+07  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  2.500557812617e-05   1.840484451196e-06  -6.071970122807e-06   2.432464253407e+04  -2.207080331882e+03  -8.584659148073e+05  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  1.840224855502e-06   2.319774542484e-05  -1.983837488548e-06   1.761897150333e+04  -4.269379756219e+03  -1.555244938371e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  1.282953228685e-05   2.807375273984e-06   7.759268653990e-06   1.898440718152e+04  -5.303751910566e+03  -2.614389107333e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  3.362374691900e-06   4.796982704111e-06  -1.006752695161e-06   1.012222041635e+04   1.266876546973e+04  -1.818436067077e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n -1.441736363766e-05   7.420644576948e-06   1.697647381418e-06  -8.598453241915e+03  -9.693787540264e+02  -4.437182519667e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n -1.715615894267e-05  -2.489925517264e-05   8.689767207313e-06  -1.817734573652e+04   1.917720905016e+04  -4.674564930124e+05  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  5.700269218746e-06   4.764024833770e-05   2.167342605243e-05   8.662840216364e+03  -7.175141639811e+03  -3.156898311773e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  9.426699454873e-07  -5.785285707147e-06   6.353982932653e-06  -1.498628620111e+04  -1.222058411806e+03  -3.802046580825e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n

Optionally, a string can be given:

In\u00a0[44]: Copied!
P.write('particles.h5')\n
P.write('particles.h5') In\u00a0[45]: Copied!
P.plot('x')\n
P.plot('x') In\u00a0[46]: Copied!
P.slice_plot('norm_emit_x', 'norm_emit_y', ylim=(0, 1e-6))\n
P.slice_plot('norm_emit_x', 'norm_emit_y', ylim=(0, 1e-6)) In\u00a0[47]: Copied!
P.plot('z', 'x')\n
P.plot('z', 'x')

Any other key that returbs an arrat can be sliced on

In\u00a0[48]: Copied!
P.slice_plot('sigma_x', slice_key = 'Jx')\n
P.slice_plot('sigma_x', slice_key = 'Jx') In\u00a0[49]: Copied!
P.plot('x', 'px')\n
P.plot('x', 'px')

Optionally the figure object can be returned, and the plot further modified.

In\u00a0[50]: Copied!
fig = P.plot('x', return_figure=True)\nax = fig.axes[0]\nax.set_title('Density Plot')\nax.set_xlim(-50, 50)\n
fig = P.plot('x', return_figure=True) ax = fig.axes[0] ax.set_title('Density Plot') ax.set_xlim(-50, 50) Out[50]:
(-50.0, 50.0)
In\u00a0[51]: Copied!
import copy\n\nfig, ax = plt.subplots()\nax.set_aspect('equal')\nxkey = 'x'\nykey = 'y'\ndatx = P[xkey]\ndaty = P[ykey]\nax.set_xlabel(f'{xkey} ({P.units(xkey)})')\nax.set_ylabel(f'{ykey} ({P.units(ykey)})')\n\ncmap = copy.copy(plt.get_cmap('viridis'))\ncmap.set_under('white')\nax.hexbin(datx, daty, gridsize=40, cmap=cmap, vmin=1e-15)\n
import copy fig, ax = plt.subplots() ax.set_aspect('equal') xkey = 'x' ykey = 'y' datx = P[xkey] daty = P[ykey] ax.set_xlabel(f'{xkey} ({P.units(xkey)})') ax.set_ylabel(f'{ykey} ({P.units(ykey)})') cmap = copy.copy(plt.get_cmap('viridis')) cmap.set_under('white') ax.hexbin(datx, daty, gridsize=40, cmap=cmap, vmin=1e-15) Out[51]:
<matplotlib.collections.PolyCollection at 0x7fd170da8700>
In\u00a0[52]: Copied!
P.plot('delta_z', 'delta_p', figsize=(8,6))\n
P.plot('delta_z', 'delta_p', figsize=(8,6)) In\u00a0[53]: Copied!
H, edges = P.histogramdd('delta_z', 'delta_p', bins=(150, 150))\nextent = [edges[0].min(),edges[0].max(),edges[1].min(),edges[1].max() ]\n\nplt.imshow(H.T, origin='lower', extent = extent, aspect='auto', vmin=1e-15, cmap=cmap)\n
H, edges = P.histogramdd('delta_z', 'delta_p', bins=(150, 150)) extent = [edges[0].min(),edges[0].max(),edges[1].min(),edges[1].max() ] plt.imshow(H.T, origin='lower', extent = extent, aspect='auto', vmin=1e-15, cmap=cmap) Out[53]:
<matplotlib.image.AxesImage at 0x7fd183b8ae20>
In\u00a0[54]: Copied!
from pmd_beamphysics import particle_paths\nfrom pmd_beamphysics.readers import all_components, component_str\n\nH5FILE = 'data/astra_particles.h5'\nh5 = h5py.File(H5FILE, 'r')\n
from pmd_beamphysics import particle_paths from pmd_beamphysics.readers import all_components, component_str H5FILE = 'data/astra_particles.h5' h5 = h5py.File(H5FILE, 'r')

Get the valid paths

In\u00a0[55]: Copied!
ppaths = particle_paths(h5)\nppaths\n
ppaths = particle_paths(h5) ppaths Out[55]:
['/screen/0/./', '/screen/1/./']

Search for all valid components in a single path

In\u00a0[56]: Copied!
ph5 = h5[ppaths[0]]\nall_components(ph5 )\n
ph5 = h5[ppaths[0]] all_components(ph5 ) Out[56]:
['momentum/x',\n 'momentum/y',\n 'momentum/z',\n 'momentumOffset/z',\n 'particleStatus',\n 'position/x',\n 'position/y',\n 'position/z',\n 'positionOffset/z',\n 'time',\n 'timeOffset',\n 'weight']

Get some info

In\u00a0[57]: Copied!
for component in all_components(ph5):\n    info = component_str(ph5, component)\n    print(info)\n
for component in all_components(ph5): info = component_str(ph5, component) print(info)
momentum/x [998 items] is a momentum with units: kg*m/s\nmomentum/y [998 items] is a momentum with units: kg*m/s\nmomentum/z [998 items] is a momentum with units: kg*m/s\nmomentumOffset/z [constant 4.660805218675275e-22 with shape 998] is a momentum with units: kg*m/s\nparticleStatus [998 items]\nposition/x [998 items] is a length with units: m\nposition/y [998 items] is a length with units: m\nposition/z [998 items] is a length with units: m\npositionOffset/z [constant 0.50013 with shape 998] is a length with units: m\ntime [998 items] is a time with units: s\ntimeOffset [constant 2.0826e-09 with shape 998] is a time with units: s\nweight [998 items] is a charge with units: C\n
In\u00a0[58]: Copied!
import os\n\nos.remove('astra.dat')\nos.remove(newh5file)\nos.remove('elegant_particles.txt')\n
import os os.remove('astra.dat') os.remove(newh5file) os.remove('elegant_particles.txt')"},{"location":"examples/particle_examples/#openpmd-beamphysics-examples","title":"openPMD beamphysics examples\u00b6","text":""},{"location":"examples/particle_examples/#basic-usage","title":"Basic Usage\u00b6","text":""},{"location":"examples/particle_examples/#particlegroup-class","title":"ParticleGroup class\u00b6","text":""},{"location":"examples/particle_examples/#basic-statistics","title":"Basic Statistics\u00b6","text":""},{"location":"examples/particle_examples/#slice-statistics","title":"Slice statistics\u00b6","text":"

ParticleGroup can be sliced along one dimension into chunks of an equal number of particles. Here are the routines to create the raw data.

"},{"location":"examples/particle_examples/#advanced-statisics","title":"Advanced statisics\u00b6","text":"

Twiss and Dispersion can be calculated.

These are the projected Twiss parameters.

TODO: normal mode twiss.

"},{"location":"examples/particle_examples/#resampling","title":"Resampling\u00b6","text":"

Particles can be resampled to either scramble the ordering of the particle arrays or subsample.

With no argument or n=0, the same number of particles will be returned:

"},{"location":"examples/particle_examples/#units","title":"Units\u00b6","text":"

Units can be retrieved from any computable quantitiy. These are returned as a pmd_unit type.

"},{"location":"examples/particle_examples/#z-vs-t","title":"z vs t\u00b6","text":"

These particles are from Bmad, at the same z and different times

"},{"location":"examples/particle_examples/#status-weight-id-copy","title":"status, weight, id, copy\u00b6","text":"

status == 1 is alive, otherwise dead. Set the first ten particles to a different status.

n_alive, n_dead count these

"},{"location":"examples/particle_examples/#writing","title":"Writing\u00b6","text":""},{"location":"examples/particle_examples/#plot","title":"Plot\u00b6","text":"

Some plotting is included for convenience. See plot_examples.ipynb for better plotting.

"},{"location":"examples/particle_examples/#1d-density-plot","title":"1D density plot\u00b6","text":""},{"location":"examples/particle_examples/#slice-statistic-plot","title":"Slice statistic plot\u00b6","text":""},{"location":"examples/particle_examples/#2d-density-plot","title":"2D density plot\u00b6","text":""},{"location":"examples/particle_examples/#manual-plotting","title":"Manual plotting\u00b6","text":""},{"location":"examples/particle_examples/#manual-binning-and-plotting","title":"Manual binning and plotting\u00b6","text":""},{"location":"examples/particle_examples/#multiple-particlegroup-in-an-hdf5-file","title":"Multiple ParticleGroup in an HDF5 file\u00b6","text":"

This example has two particlegroups. This also shows how to examine the components, without loading the full data.

"},{"location":"examples/particle_examples/#cleanup","title":"Cleanup\u00b6","text":""},{"location":"examples/plot_examples/","title":"Plot examples","text":"In\u00a0[1]: Copied!
from pmd_beamphysics import ParticleGroup, particle_paths\nimport matplotlib\nimport matplotlib.pyplot as plt\n\n%config InlineBackend.figure_format = 'retina'\nmatplotlib.rcParams['figure.figsize'] = (8,6)\n\nfrom h5py import File\nimport os\n
from pmd_beamphysics import ParticleGroup, particle_paths import matplotlib import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' matplotlib.rcParams['figure.figsize'] = (8,6) from h5py import File import os In\u00a0[2]: Copied!
# Open a file, fine the particle paths from the root attributes\n# Pick one:\nH5FILE = 'data/bmad_particles2.h5'\n#H5FILE = 'data/distgen_particles.h5'\n#H5FILE = 'data/astra_particles.h5'\n\n# Load\nh5 = File(H5FILE, 'r')\nppaths = particle_paths(h5)\nph5 = h5[ppaths[0]]\n\nP = ParticleGroup(ph5)\nppaths\n
# Open a file, fine the particle paths from the root attributes # Pick one: H5FILE = 'data/bmad_particles2.h5' #H5FILE = 'data/distgen_particles.h5' #H5FILE = 'data/astra_particles.h5' # Load h5 = File(H5FILE, 'r') ppaths = particle_paths(h5) ph5 = h5[ppaths[0]] P = ParticleGroup(ph5) ppaths Out[2]:
['/data/00001/particles/']
In\u00a0[3]: Copied!
P.plot('t')\n
P.plot('t') In\u00a0[4]: Copied!
P.t = P.t - P['mean_t']\nP.slice_plot('sigma_x', 'sigma_y', xlim=(None, 80e-15), ylim=(0, 30e-6))\n
P.t = P.t - P['mean_t'] P.slice_plot('sigma_x', 'sigma_y', xlim=(None, 80e-15), ylim=(0, 30e-6)) In\u00a0[5]: Copied!
P.plot('t', 'energy', xlim = (-200e-15, 200e-15), ylim = (7.9e9, 8.1e9))\n
P.plot('t', 'energy', xlim = (-200e-15, 200e-15), ylim = (7.9e9, 8.1e9)) In\u00a0[6]: Copied!
from pmd_beamphysics.plot import density_and_slice_plot\n
from pmd_beamphysics.plot import density_and_slice_plot In\u00a0[7]: Copied!
P.species\n
P.species Out[7]:
'electron'
In\u00a0[8]: Copied!
density_and_slice_plot(P, 't', 'energy', stat_keys = ['sigma_x', 'sigma_y'], n_slice = 200, bins=200)\n
density_and_slice_plot(P, 't', 'energy', stat_keys = ['sigma_x', 'sigma_y'], n_slice = 200, bins=200)"},{"location":"examples/plot_examples/#plot-examples","title":"Plot examples\u00b6","text":""},{"location":"examples/plot_examples/#density-plots","title":"Density plots\u00b6","text":""},{"location":"examples/plot_examples/#slice-statistics-plots","title":"Slice statistics Plots\u00b6","text":""},{"location":"examples/plot_examples/#marginal-plots","title":"Marginal plots\u00b6","text":""},{"location":"examples/plot_examples/#combined-density-and-slice-plot","title":"Combined density and slice plot\u00b6","text":""},{"location":"examples/read_examples/","title":"Read examples","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n
# Useful for debugging %load_ext autoreload %autoreload 2 In\u00a0[2]: Copied!
from pmd_beamphysics import ParticleGroup\nfrom h5py import File\n
from pmd_beamphysics import ParticleGroup from h5py import File In\u00a0[3]: Copied!
from pmd_beamphysics.interfaces.elegant import elegant_h5_to_data\n
from pmd_beamphysics.interfaces.elegant import elegant_h5_to_data

This will convert to a data dict

In\u00a0[4]: Copied!
data = elegant_h5_to_data('data/elegant_raw.h5')\ndata\n
data = elegant_h5_to_data('data/elegant_raw.h5') data Out[4]:
{'x': array([-1.12390181e-04, -7.20268439e-05, -1.14543953e-04, ...,\n         1.05737263e-04, -7.24786314e-05,  5.78264247e-05]),\n 'y': array([-1.23229455e-04, -1.08820261e-04, -9.27723036e-05, ...,\n         8.55980575e-05,  8.58175111e-05,  9.07622257e-05]),\n 'z': array([0, 0, 0, ..., 0, 0, 0]),\n 'px': array([ -3083.62425792,   2225.50915788,   2725.71118129, ...,\n        -15364.10017428,  15821.42533783,  -4733.37386475]),\n 'py': array([ 83545.5978262 ,  83389.57148069,  68275.02698773, ...,\n        -70430.7889418 , -70600.14619666, -69547.74375481]),\n 'pz': array([8.00396787e+09, 8.00007258e+09, 8.00139350e+09, ...,\n        7.99685994e+09, 7.99660958e+09, 7.99818064e+09]),\n 't': array([5.11804566e-06, 5.11804568e-06, 5.11804567e-06, ...,\n        5.11804569e-06, 5.11804569e-06, 5.11804569e-06]),\n 'status': array([1, 1, 1, ..., 1, 1, 1]),\n 'species': 'electron',\n 'weight': array([2.e-17, 2.e-17, 2.e-17, ..., 2.e-17, 2.e-17, 2.e-17]),\n 'id': array([    1,     2,     3, ..., 49998, 49999, 50000], dtype=int32)}

Create ParticleGroup and plot:

In\u00a0[5]: Copied!
P=ParticleGroup(data=data)\nP.plot('delta_t', 'delta_pz')\n
P=ParticleGroup(data=data) P.plot('delta_t', 'delta_pz') In\u00a0[6]: Copied!
from pmd_beamphysics.interfaces.genesis import genesis4_par_to_data\n
from pmd_beamphysics.interfaces.genesis import genesis4_par_to_data In\u00a0[7]: Copied!
P = ParticleGroup(data=genesis4_par_to_data('data/genesis4.par.h5'))\nP\n
P = ParticleGroup(data=genesis4_par_to_data('data/genesis4.par.h5')) P Out[7]:
<ParticleGroup with 14336 particles at 0x7fdbed8b5610>
In\u00a0[8]: Copied!
P.plot('z', 'pz')\n
P.plot('z', 'pz')"},{"location":"examples/read_examples/#read-examples","title":"Read examples\u00b6","text":""},{"location":"examples/read_examples/#elegant-hdf5-format","title":"elegant hdf5 format\u00b6","text":""},{"location":"examples/read_examples/#genesis4-hdf5-format","title":"Genesis4 HDF5 format\u00b6","text":""},{"location":"examples/units/","title":"Units","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n
# Useful for debugging %load_ext autoreload %autoreload 2 In\u00a0[2]: Copied!
from pmd_beamphysics import particle_paths\nfrom pmd_beamphysics.units import pmd_unit, dimension_name, sqrt_unit, known_unit, multiply_units\nfrom h5py import File\nimport numpy as np\n
from pmd_beamphysics import particle_paths from pmd_beamphysics.units import pmd_unit, dimension_name, sqrt_unit, known_unit, multiply_units from h5py import File import numpy as np

This is the basic class:

In\u00a0[3]: Copied!
?pmd_unit\n
?pmd_unit

Get a known units. These can be multiplied and divided:

In\u00a0[4]: Copied!
u1 = known_unit['J']\nu2 = known_unit['m']\nu1, u2, u1/u2, u1*u2\n
u1 = known_unit['J'] u2 = known_unit['m'] u1, u2, u1/u2, u1*u2 Out[4]:
(pmd_unit('J', 1, (2, 1, -2, 0, 0, 0, 0)),\n pmd_unit('m', 1, (1, 0, 0, 0, 0, 0, 0)),\n pmd_unit('J/m', 1.0, (1, 1, -2, 0, 0, 0, 0)),\n pmd_unit('J*m', 1, (3, 1, -2, 0, 0, 0, 0)))

Special function for sqrt:

In\u00a0[5]: Copied!
sqrt_unit(u1)\n
sqrt_unit(u1) Out[5]:
pmd_unit('\\sqrt{ J }', 1.0, (1.0, 0.5, -1.0, 0.0, 0.0, 0.0, 0.0))
In\u00a0[6]: Copied!
# Pick one:\n#H5FILE = 'data/bmad_particles.h5'\nH5FILE = 'data/distgen_particles.h5'\n#H5FILE = 'data/astra_particles.h5'\nh5 = File(H5FILE, 'r')\n\nppaths = particle_paths(h5)\nprint(ppaths)\n
# Pick one: #H5FILE = 'data/bmad_particles.h5' H5FILE = 'data/distgen_particles.h5' #H5FILE = 'data/astra_particles.h5' h5 = File(H5FILE, 'r') ppaths = particle_paths(h5) print(ppaths)
['//']\n

This points to a single particle group:

In\u00a0[7]: Copied!
ph5 = h5[ppaths[0]]\nlist(ph5)\n
ph5 = h5[ppaths[0]] list(ph5) Out[7]:
['momentum', 'particleStatus', 'position', 'time', 'weight']

Each component should have a dimension and a conversion factor to SI:

In\u00a0[8]: Copied!
d = dict(ph5['momentum/x'].attrs)\nd\n
d = dict(ph5['momentum/x'].attrs) d Out[8]:
{'unitDimension': array([ 1,  1, -1,  0,  0,  0,  0]),\n 'unitSI': 5.344285992678308e-28,\n 'unitSymbol': 'eV/c'}
In\u00a0[9]: Copied!
tuple(d['unitDimension'])\n
tuple(d['unitDimension']) Out[9]:
(1, 1, -1, 0, 0, 0, 0)

This will extract the name of this dimension:

In\u00a0[10]: Copied!
dimension_name(d['unitDimension'])\n
dimension_name(d['unitDimension']) Out[10]:
'momentum'
In\u00a0[11]: Copied!
from pmd_beamphysics.units import nice_array\n
from pmd_beamphysics.units import nice_array

This will scale the array, and return the appropriate SI prefix:

In\u00a0[12]: Copied!
x = 1e-4\nunit = 'm'\nnice_array(x)\n
x = 1e-4 unit = 'm' nice_array(x) Out[12]:
(100.00000000000001, 1e-06, '\u00b5')
In\u00a0[13]: Copied!
nice_array([-0.01, 0.01])\n
nice_array([-0.01, 0.01]) Out[13]:
(array([-10.,  10.]), 0.001, 'm')
In\u00a0[14]: Copied!
from pmd_beamphysics.units import nice_scale_prefix\n
from pmd_beamphysics.units import nice_scale_prefix In\u00a0[15]: Copied!
nice_scale_prefix(0.009)\n
nice_scale_prefix(0.009) Out[15]:
(0.001, 'm')
In\u00a0[16]: Copied!
try:\n    u1/1\nexcept:\n    print('you cannot do this')\n
try: u1/1 except: print('you cannot do this')
you cannot do this\n
"},{"location":"examples/units/#units","title":"Units\u00b6","text":"

This package provides unit conversion tools

"},{"location":"examples/units/#openpmd-hdf5-units","title":"openPMD HDF5 units\u00b6","text":"

Open a file, find the particle paths from the root attributes

"},{"location":"examples/units/#nice-arrays","title":"Nice arrays\u00b6","text":""},{"location":"examples/units/#limitations","title":"Limitations\u00b6","text":"

This is a simple class for use with this package. So even simple things like the example below will fail.

For more advanced units, use a package like Pint: https://pint.readthedocs.io/

"},{"location":"examples/write_examples/","title":"Write examples","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n
# Useful for debugging %load_ext autoreload %autoreload 2 In\u00a0[2]: Copied!
from pmd_beamphysics import ParticleGroup, particle_paths, pmd_init\nfrom h5py import File\nimport os\n
from pmd_beamphysics import ParticleGroup, particle_paths, pmd_init from h5py import File import os In\u00a0[3]: Copied!
# Pick one:\n\n#H5File = 'data/bmad_particles2.h5'\nH5FILE = 'data/distgen_particles.h5'\n#H5FILE = 'data/astra_particles.h5'\n\nP = ParticleGroup(H5FILE)\n
# Pick one: #H5File = 'data/bmad_particles2.h5' H5FILE = 'data/distgen_particles.h5' #H5FILE = 'data/astra_particles.h5' P = ParticleGroup(H5FILE)

The regular write routine writes in a proper openPMD format

In\u00a0[4]: Copied!
P.write('openpmd_particles.h5')\n
P.write('openpmd_particles.h5')

An open h5 hande can also be used, but it needs to be properly initialized

In\u00a0[5]: Copied!
with File('openpmd_particles.h5', 'w') as h5:\n    pmd_init(h5, basePath='/', particlesPath='/' )\n    P.write(h5)\n
with File('openpmd_particles.h5', 'w') as h5: pmd_init(h5, basePath='/', particlesPath='/' ) P.write(h5)

This can be read in by another ParticleGroup

In\u00a0[6]: Copied!
P2 = ParticleGroup('openpmd_particles.h5')\n
P2 = ParticleGroup('openpmd_particles.h5')

Check that they are the same:

In\u00a0[7]: Copied!
P2 == P\n
P2 == P Out[7]:
True
In\u00a0[8]: Copied!
P.write_astra('astra_particles.txt')\n
P.write_astra('astra_particles.txt') In\u00a0[9]: Copied!
!head astra_particles.txt\n
!head astra_particles.txt
 -1.289814080985e-05   1.712192978919e-05   0.000000000000e+00  -9.245284702413e-01  -3.316650265292e+00   2.210558337183e+02   1.819664001274e-05   0.000000000000e+00    1    5\r\n -1.184861337727e-03  -2.101371437059e-03   0.000000000000e+00  -3.047044656318e+02  -3.039342419008e+02  -1.005645891013e+02  -9.745607002167e-04   1.000000000000e-06    1    5\r\n -5.181307340245e-04  -2.178353405029e-03   0.000000000000e+00   5.525456229648e+02   2.416723877028e+02  -6.554342847563e+01   1.280843753434e-03   1.000000000000e-06    1    5\r\n -1.773501610902e-03   2.864979597813e-03   0.000000000000e+00  -2.226004747820e+02   9.450238076106e+00  -1.055085411491e+02   3.835366744569e-04   1.000000000000e-06    1    5\r\n  1.686555815999e-03  -2.401048305081e-04   0.000000000000e+00  -1.891692499417e+02   4.859547751754e+01   3.339263495319e+02   1.902998338336e-03   1.000000000000e-06    1    5\r\n -7.779454935491e-04  -6.800063114796e-04   0.000000000000e+00   6.716138938638e+01  -2.064173000222e+02  -1.405963302134e+02   1.779005092730e-04   1.000000000000e-06    1    5\r\n -2.593702199590e-03  -2.301030494125e-03   0.000000000000e+00  -1.455653402031e+01   2.074634953296e+02  -1.397453142110e+02   1.368567098305e-03   1.000000000000e-06    1    5\r\n  1.997801509161e-03   2.648416193086e-03   0.000000000000e+00  -2.124047726665e+00  -6.792723569247e+01   6.931081537770e+01   2.616497721112e-04   1.000000000000e-06    1    5\r\n  1.999741847023e-03  -6.945690451493e-04   0.000000000000e+00  -9.991142908925e+01  -9.189412445573e+01   2.259539675809e+02  -8.681109991004e-04   1.000000000000e-06    1    5\r\n -7.033822974359e-04  -5.677746866954e-04   0.000000000000e+00   7.520962264129e+02   3.125940718167e+02  -1.451032665210e+02   4.315191990002e-04   1.000000000000e-06    1    5\r\n

Check the readback:

In\u00a0[10]: Copied!
from pmd_beamphysics.interfaces.astra import parse_astra_phase_file\nimport numpy as np\nP1 = ParticleGroup(data=parse_astra_phase_file('astra_particles.txt'))\nfor k in ['x', 'px', 'y', 'py', 'z', 'pz']:\n    assert np.allclose(P[k], P1[k])\n
from pmd_beamphysics.interfaces.astra import parse_astra_phase_file import numpy as np P1 = ParticleGroup(data=parse_astra_phase_file('astra_particles.txt')) for k in ['x', 'px', 'y', 'py', 'z', 'pz']: assert np.allclose(P[k], P1[k]) In\u00a0[11]: Copied!
P.write_bmad('bmad_particles.txt')\n
P.write_bmad('bmad_particles.txt') In\u00a0[12]: Copied!
!head bmad_particles.txt\n
!head bmad_particles.txt
!ASCII::3\r\n0 ! ix_ele, not used\r\n1 ! n_bunch\r\n10000 ! n_particle\r\nBEGIN_BUNCH\r\nelectron \r\n1.0000000000000003e-11  ! bunch_charge\r\n0 ! z_center\r\n0 ! t_center\r\n -1.184861337727e-03  -3.047044656318e+02  -2.101371437059e-03  -3.039342419008e+02  -9.563640602039e-13   1.204912446170e+02   1.000000000000e-15  1\r\n
In\u00a0[13]: Copied!
P.to_bmad()\n
P.to_bmad() Out[13]:
{'x': array([-0.00118486, -0.00051813, -0.0017735 , ..., -0.00052658,\n         0.00252813,  0.00113815]),\n 'y': array([-0.00210137, -0.00217835,  0.00286498, ..., -0.00161154,\n         0.00160049, -0.0010351 ]),\n 'px': array([-0.69011879,  1.25144906, -0.50416317, ...,  0.60249121,\n         0.505233  ,  0.74928061]),\n 'py': array([-0.68837433,  0.54735875,  0.02140365, ..., -0.07882388,\n         0.29433475, -0.25918357]),\n 'z': array([ 2.55529374e-07, -4.68009162e-07, -5.64739756e-08, ...,\n        -9.69805227e-09,  4.20165276e-07,  2.70278113e-07]),\n 'pz': array([ 0.01222356,  0.41059669, -0.4315584 , ..., -0.3093279 ,\n         0.02463904,  0.08781142]),\n 'charge': array([1.e-15, 1.e-15, 1.e-15, ..., 1.e-15, 1.e-15, 1.e-15]),\n 'species': 'electron',\n 'p0c': 441.52466167250947,\n 'tref': 1.8196640012738955e-14,\n 'state': array([1, 1, 1, ..., 1, 1, 1])}

Check that the conversion preserves information. Note that == uses np.allclose, because there is roundoff error in the conversion.

In\u00a0[14]: Copied!
assert P == P.from_bmad(P.to_bmad())\n
assert P == P.from_bmad(P.to_bmad()) In\u00a0[15]: Copied!
P.write_elegant('elegant_particles.txt', verbose=True)\n
P.write_elegant('elegant_particles.txt', verbose=True)
writing 10000 particles to elegant_particles.txt\n
In\u00a0[16]: Copied!
!head -n 20 elegant_particles.txt\n
!head -n 20 elegant_particles.txt
SDDS1\r\n! \r\n! Created using the openPMD-beamphysics Python package\r\n! https://github.com/ChristopherMayes/openPMD-beamphysics\r\n! species: electron\r\n!\r\n&parameter name=Charge, type=double, units=C, description=\"total charge in Coulombs\" &end\r\n&column name=t,  type=double, units=s, description=\"time in seconds\" &end\r\n&column name=x,  type=double, units=m, description=\"x in meters\" &end\r\n&column name=xp, type=double, description=\"px/pz\" &end\r\n&column name=y,  type=double, units=m, description=\"y in meters\" &end\r\n&column name=yp, type=double, description=\"py/pz\" &end\r\n&column name=p,  type=double, units=\"m$be$nc\", description=\"relativistic gamma*beta\" &end\r\n&data mode=ascii &end\r\n1.0000000000000003e-11\r\n10000\r\n -9.563640602039e-13  -1.184861337727e-03  -2.528851507845e+00  -2.101371437059e-03  -2.522459145201e+00   8.746038816275e-04\r\n  1.299040393447e-12  -5.181307340245e-04   3.553064606663e+00  -2.178353405029e-03   1.554039289185e+00   1.218815083118e-03\r\n  4.017333144697e-13  -1.773501610902e-03  -1.926488019169e+00   2.864979597813e-03   8.178675472159e-02   4.911575370556e-04\r\n  1.921194978349e-12   1.686555815999e-03  -3.408564376497e-01  -2.401048305081e-04   8.756222989528e-02   1.151365621035e-03\r\n
In\u00a0[17]: Copied!
P.write_genesis2_beam_file('genesis2.beam', n_slice=50, verbose=True)\n
P.write_genesis2_beam_file('genesis2.beam', n_slice=50, verbose=True)
Beam written: genesis2.beam\n
In\u00a0[18]: Copied!
!head genesis2.beam\n
!head genesis2.beam
? VERSION=1.0\r\n? SIZE=50\r\n? COLUMNS TPOS CURPEAK GAMMA0 DELGAM EMITX EMITY RXBEAM RYBEAM XBEAM YBEAM PXBEAM PYBEAM ALPHAX ALPHAY\r\n-1.96236040e-12 2.75359318e+00 1.00000042e+00 3.87022121e-07 1.19639887e-06 9.26536586e-07 2.04948080e-03 1.83621527e-03 -2.06231144e-04 5.85738124e-05 1.38209180e-05 4.17553934e-05 -1.01973407e-02 7.54079832e-04\r\n-1.88646733e-12 2.46723994e+00 1.00000043e+00 3.39783124e-07 1.04698472e-06 1.03470799e-06 2.05064183e-03 1.92139881e-03 -2.53107201e-05 -1.13678787e-04 -2.85190907e-05 -2.90105675e-05 -2.32400675e-02 -2.55583997e-03\r\n-1.80734757e-12 2.61898031e+00 1.00000043e+00 3.65257967e-07 9.78086023e-07 1.03038032e-06 1.97065976e-03 1.88550433e-03 -7.56576638e-05 1.77044304e-04 -4.48907172e-05 6.98821769e-05 5.38708456e-03 4.70844009e-03\r\n-1.72583745e-12 2.30361285e+00 1.00000043e+00 3.43343193e-07 9.42099457e-07 1.07137205e-06 1.90156644e-03 1.92054974e-03 4.82559113e-05 -6.43105052e-05 1.66595205e-05 1.33986604e-05 9.16890850e-02 8.20635086e-03\r\n-1.64047654e-12 2.48779835e+00 1.00000043e+00 3.76740497e-07 1.12901501e-06 1.01496464e-06 2.07085748e-03 1.84284581e-03 -2.63780641e-04 2.16425678e-05 9.07228824e-06 -2.65046022e-05 1.91567484e-03 3.63234933e-03\r\n-1.55820367e-12 2.33273410e+00 1.00000041e+00 3.38657590e-07 9.79796547e-07 1.04648255e-06 1.84685458e-03 1.99189484e-03 3.36520774e-05 1.02123362e-04 3.01402301e-06 6.13937003e-05 -7.84493116e-02 2.84788841e-02\r\n-1.47800120e-12 2.62363489e+00 1.00000044e+00 3.34570582e-07 1.12580877e-06 1.08628803e-06 1.97095399e-03 2.02149469e-03 -1.94415715e-04 -1.05666747e-04 -1.70675102e-05 -5.61809635e-06 -1.81522208e-01 -5.08353286e-03\r\n
In\u00a0[19]: Copied!
input_str = P.write_genesis4_beam('genesis4_beam.h5', n_slice=123, verbose=True, return_input_str=True)\n
input_str = P.write_genesis4_beam('genesis4_beam.h5', n_slice=123, verbose=True, return_input_str=True)
Genesis4 beam file written: genesis4_beam.h5\n

This string is optionally returned for use in the main Genesis4 input file:

In\u00a0[20]: Copied!
print(input_str)\n
print(input_str)
&profile_file\n  label = current\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/current\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = gamma\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/gamma\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = delgam\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/delgam\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = ex\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/ex\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = ey\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/ey\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = xcenter\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/xcenter\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = ycenter\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/ycenter\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = pxcenter\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/pxcenter\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = pycenter\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/pycenter\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = alphax\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/alphax\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = alphay\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/alphay\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = betax\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/betax\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = betay\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/betay\n  isTime = T\n  reverse = T\n&end\n&beam\n  current = @current\n  gamma = @gamma\n  delgam = @delgam\n  ex = @ex\n  ey = @ey\n  xcenter = @xcenter\n  ycenter = @ycenter\n  pxcenter = @pxcenter\n  pycenter = @pycenter\n  alphax = @alphax\n  alphay = @alphay\n  betax = @betax\n  betay = @betay\n&end\n

These are the datasets written:

In\u00a0[21]: Copied!
with File('genesis4_beam.h5', 'r') as h5:\n    for g in h5:\n        print(g, len(h5[g]), h5[g].attrs['unitSymbol'])\n
with File('genesis4_beam.h5', 'r') as h5: for g in h5: print(g, len(h5[g]), h5[g].attrs['unitSymbol'])
alphax 123 \nalphay 123 \nbetax 123 m\nbetay 123 m\ncurrent 123 A\ndelgam 123 \nex 123 m\ney 123 m\ngamma 123 \npxcenter 123 \npycenter 123 \nt 123 s\nxcenter 123 m\nycenter 123 m\n
In\u00a0[22]: Copied!
P.write_genesis4_distribution('genesis4_distribution.h5', verbose=True)\n
P.write_genesis4_distribution('genesis4_distribution.h5', verbose=True)
Datasets x, xp, y, yp, t, p written to: genesis4_distribution.h5\n

This is what is written:

In\u00a0[23]: Copied!
with File('genesis4_distribution.h5', 'r') as h5:\n    for g in h5:\n        print(g, len(h5[g]))\n
with File('genesis4_distribution.h5', 'r') as h5: for g in h5: print(g, len(h5[g]))
p 10000\nt 10000\nx 10000\nxp 10000\ny 10000\nyp 10000\n
In\u00a0[24]: Copied!
P.write_gpt('gpt_particles.txt', verbose=True)\n
P.write_gpt('gpt_particles.txt', verbose=True)
writing 10000 particles to gpt_particles.txt\nASCII particles written. Convert to GDF using: asci2df -o particles.gdf gpt_particles.txt\n
In\u00a0[25]: Copied!
if os.path.exists(os.path.expandvars('$ASCI2GDF_BIN')):\n    P.write_gpt('gpt_particles.gdf', verbose=True, asci2gdf_bin='$ASCI2GDF_BIN')\n
if os.path.exists(os.path.expandvars('$ASCI2GDF_BIN')): P.write_gpt('gpt_particles.gdf', verbose=True, asci2gdf_bin='$ASCI2GDF_BIN') In\u00a0[26]: Copied!
#!head gpt_particles.txt\n
#!head gpt_particles.txt In\u00a0[27]: Copied!
P.drift_to_t(P['mean_t'])\n
P.drift_to_t(P['mean_t'])

This will return settings for Impact-T to use:

In\u00a0[28]: Copied!
P.write_impact('impact_particles.txt')\n
P.write_impact('impact_particles.txt') Out[28]:
{'input_particle_file': 'impact_particles.txt',\n 'Np': 10000,\n 'Tini': 1.8196640012738955e-14,\n 'Flagimg': 0}
In\u00a0[29]: Copied!
!head impact_particles.txt\n
!head impact_particles.txt
10000\r\n-1.185035553808772143e-03 -5.962917646539026830e-04 -2.101545212762252063e-03 -5.947844744118675406e-04 6.889138464881934423e-08 2.357954837617718942e-04\r\n-5.185459410277813361e-04 1.081304810831444467e-03 -2.178535008255722601e-03 4.729410651485857963e-04 -1.168588385748075652e-07 3.043301854978374224e-04\r\n-1.773451522909312962e-03 -4.356182625855454594e-04 2.864977471386669170e-03 1.849365458795189412e-05 -2.599963881922582336e-08 2.261204109504710640e-04\r\n1.686767013783966630e-03 -3.701949875665073173e-04 -2.401590848712557098e-04 9.509897724357652376e-05 -6.196091998406064887e-07 1.086073040365844247e-03\r\n-7.779525032181127363e-04 1.314315604491483489e-04 -6.799847675988795461e-04 -4.039485795854574836e-04 -8.397600117458311948e-09 1.574553206124528761e-04\r\n-2.593690512006350136e-03 -2.848642647956871966e-05 -2.301197068594195913e-03 4.059959327304890142e-04 -6.528501143099030074e-08 1.591207173856017771e-04\r\n1.997801835211931738e-03 -4.156657712632428962e-06 2.648426620219643604e-03 -1.329302842842789139e-04 -4.457257401140207729e-08 5.682333576145901953e-04\r\n1.999690961886589624e-03 -1.955217894072916647e-04 -6.946158470527933476e-04 -1.798323156157682705e-04 2.276631907326255499e-07 8.747763597149907193e-04\r\n-7.035727003852429310e-04 1.471815600429043306e-03 -5.678538239535645561e-04 6.117313388152665482e-04 -1.922838101944327160e-08 1.486354662711140203e-04\r\n
In\u00a0[30]: Copied!
P.drift_to_z()\n
P.drift_to_z() In\u00a0[31]: Copied!
P.write_litrack('litrack.zd', verbose=True)\n
P.write_litrack('litrack.zd', verbose=True)
Using mean_p as the reference momentum: 441.52466167250947 eV/c\nwriting 10000 LiTrack particles to litrack.zd\n
Out[31]:
'litrack.zd'
In\u00a0[32]: Copied!
!head -n 20 litrack.zd\n
!head -n 20 litrack.zd
% LiTrack particles\r\n% \r\n% Created using the openPMD-beamphysics Python package\r\n% https://github.com/ChristopherMayes/openPMD-beamphysics\r\n%\r\n% species: electron\r\n% n_particle: 10000\r\n% total charge: 1.0000000000000003e-11 (C)\r\n% reference momentum p0: 441.52466167250947 (eV/c)\r\n%\r\n% Columns: ct, delta = p/p0 -1\r\n% Units: mm, percent\r\n -2.910140500388e-01   1.222356070583e+00\r\n  3.861082943987e-01   4.105966931910e+01\r\n  1.159491741202e-01  -4.315583986424e+01\r\n  5.750254785583e-01   3.325339997689e+01\r\n  5.234406211768e-02  -4.756793241308e+01\r\n  4.093643740668e-01  -4.942448528425e+01\r\n  8.211012905157e-02  -3.245820052454e+01\r\n -2.559578717432e-01   5.807578013130e+00\r\n
In\u00a0[33]: Copied!
P.write_lucretia('lucretia.mat', ele_name='BEGINNING', t_ref=0, stop_ix=None, verbose=True)\n
P.write_lucretia('lucretia.mat', ele_name='BEGINNING', t_ref=0, stop_ix=None, verbose=True)
writing 10000 particles in the Lucretia format to lucretia.mat\n

Read back:

In\u00a0[34]: Copied!
from pmd_beamphysics.interfaces.lucretia import lucretia_to_data, list_element_names\n\nParticleGroup(data=lucretia_to_data('lucretia.mat', verbose=True))\n
from pmd_beamphysics.interfaces.lucretia import lucretia_to_data, list_element_names ParticleGroup(data=lucretia_to_data('lucretia.mat', verbose=True))
1 elements found in the file!\n10000 particles detected, 0 found dead!\n
Out[34]:
<ParticleGroup with 10000 particles at 0x7f63da85cd00>

Helper function to list the available elements:

In\u00a0[35]: Copied!
list_element_names('lucretia.mat')\n
list_element_names('lucretia.mat') Out[35]:
['BEGINNING']
In\u00a0[36]: Copied!
P.drift_to_t()\n\nP.write_opal('opal_injected.txt', dist_type='injected')\n
P.drift_to_t() P.write_opal('opal_injected.txt', dist_type='injected') In\u00a0[37]: Copied!
!head opal_injected.txt\n
!head opal_injected.txt
10000\r\n -1.185024568260e-03  -5.962917646539e-04  -2.101534254983e-03  -5.947844744119e-04   6.454729863911e-08   2.357954837618e-04\r\n -5.185658620175e-04   1.081304810831e-03  -2.178543721298e-03   4.729410651486e-04  -1.224655448770e-07   3.043301854978e-04\r\n -1.773443497464e-03  -4.356182625855e-04   2.864977130676e-03   1.849365458795e-05  -3.016548099423e-08   2.261204109505e-04\r\n  1.686773833925e-03  -3.701949875665e-04  -2.401608368896e-04   9.509897724358e-05  -6.396180367454e-07   1.086073040366e-03\r\n -7.779549245968e-04   1.314315604491e-04  -6.799773256079e-04  -4.039485795855e-04  -1.129841753741e-08   1.574553206125e-04\r\n -2.593689987198e-03  -2.848642647957e-05  -2.301204548304e-03   4.059959327305e-04  -6.821651066751e-08   1.591207173856e-04\r\n  1.997801911791e-03  -4.156657712632e-06   2.648429069209e-03  -1.329302842843e-04  -5.504120158406e-08   5.682333576146e-04\r\n  1.999694564006e-03  -1.955217894073e-04  -6.946125339825e-04  -1.798323156158e-04   2.115470906675e-07   8.747763597150e-04\r\n -7.035998157808e-04   1.471815600429e-03  -5.678650939369e-04   6.117313388153e-04  -2.196670602435e-08   1.486354662711e-04\r\n

Emitted particles must be at the same z:

In\u00a0[38]: Copied!
P.drift_to_z(P['mean_z'])\nP.write_opal('opal_emitted.txt', dist_type='emitted')\n
P.drift_to_z(P['mean_z']) P.write_opal('opal_emitted.txt', dist_type='emitted') In\u00a0[39]: Copied!
!head opal_emitted.txt\n
!head opal_emitted.txt
10000\r\n -1.184838617375e-03  -5.962917646539e-04  -2.101348774139e-03  -5.947844744119e-04  -1.083461177889e-12   2.357954837618e-04\r\n -5.181626563723e-04   1.081304810831e-03  -2.178367367225e-03   4.729410651486e-04   1.200565320731e-12   3.043301854978e-04\r\n -1.773484302458e-03  -4.356182625855e-04   2.864978863003e-03   1.849365458795e-05   2.691980940714e-13   2.261204109505e-04\r\n  1.686558878409e-03  -3.701949875665e-04  -2.401056172069e-04   9.509897724358e-05   1.893601130019e-12   1.086073040366e-03\r\n -7.779529930790e-04   1.314315604491e-04  -6.799832620349e-04  -4.039485795855e-04   5.764311716727e-15   1.574553206125e-04\r\n -2.593700591157e-03  -2.848642647957e-05  -2.301053417928e-03   4.059959327305e-04   1.198422972634e-12   1.591207173856e-04\r\n  1.997801574883e-03  -4.156657712632e-06   2.648418294875e-03  -1.329302842843e-04   2.271058970013e-13   5.682333576146e-04\r\n  1.999743855144e-03  -1.955217894073e-04  -6.945671981683e-04  -1.798323156158e-04  -8.841733180528e-13   8.747763597150e-04\r\n -7.034712631509e-04   1.471815600429e-03  -5.678116635531e-04   6.117313388153e-04   2.480886363183e-13   1.486354662711e-04\r\n
In\u00a0[40]: Copied!
P.write_simion('simion_particles.ion')\n
P.write_simion('simion_particles.ion') In\u00a0[41]: Copied!
for file in [\n    'astra_particles.txt',\n    'bmad_particles.txt',\n    'elegant_particles.txt',\n    'gpt_particles.txt',\n    'impact_particles.txt',\n    'opal_injected.txt',\n    'opal_emitted.txt',\n    'openpmd_particles.h5',\n    'genesis4_beam.h5',\n    'genesis4_distribution.h5',\n    'genesis2.beam',\n    'litrack.zd',\n    'gpt_particles.gdf',\n    'lucretia.mat',\n    'simion_particles.ion'\n    ]:\n    if os.path.exists(file):\n        os.remove(file)\n
for file in [ 'astra_particles.txt', 'bmad_particles.txt', 'elegant_particles.txt', 'gpt_particles.txt', 'impact_particles.txt', 'opal_injected.txt', 'opal_emitted.txt', 'openpmd_particles.h5', 'genesis4_beam.h5', 'genesis4_distribution.h5', 'genesis2.beam', 'litrack.zd', 'gpt_particles.gdf', 'lucretia.mat', 'simion_particles.ion' ]: if os.path.exists(file): os.remove(file)"},{"location":"examples/write_examples/#write-examples","title":"Write examples\u00b6","text":""},{"location":"examples/write_examples/#openpmd","title":"openPMD\u00b6","text":""},{"location":"examples/write_examples/#astra","title":"Astra\u00b6","text":""},{"location":"examples/write_examples/#bmad-ascii","title":"Bmad ASCII\u00b6","text":""},{"location":"examples/write_examples/#bmad-dict","title":"Bmad dict\u00b6","text":""},{"location":"examples/write_examples/#elegant","title":"elegant\u00b6","text":""},{"location":"examples/write_examples/#genesis-13-v2","title":"Genesis 1.3 v2\u00b6","text":""},{"location":"examples/write_examples/#genesis-13-v4","title":"Genesis 1.3 v4\u00b6","text":""},{"location":"examples/write_examples/#beam-file-slice-statistics","title":"beam file (slice statistics)\u00b6","text":""},{"location":"examples/write_examples/#distribution-file-particles","title":"Distribution file (particles)\u00b6","text":""},{"location":"examples/write_examples/#gpt-ascii","title":"GPT ASCII\u00b6","text":""},{"location":"examples/write_examples/#impact-t","title":"Impact-T\u00b6","text":"

Impact-T particles must all be a the same time:

"},{"location":"examples/write_examples/#litrack","title":"LiTrack\u00b6","text":"

LiTrack particles must be at the same z:

"},{"location":"examples/write_examples/#lucretia","title":"Lucretia\u00b6","text":""},{"location":"examples/write_examples/#opal","title":"OPAL\u00b6","text":"

Injected particled must be at the same time:

"},{"location":"examples/write_examples/#simion","title":"SIMION\u00b6","text":"

Write SIMION input files (*.ion)

"},{"location":"examples/write_examples/#cleanup","title":"Cleanup\u00b6","text":""},{"location":"examples/fields/field_conversion/","title":"FieldMesh Conversion","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n\n# Nicer plotting\nimport matplotlib.pyplot as plt\n%config InlineBackend.figure_format = 'retina'\n\nimport numpy as np\n
# Useful for debugging %load_ext autoreload %autoreload 2 # Nicer plotting import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' import numpy as np In\u00a0[2]: Copied!
from pmd_beamphysics import FieldMesh\n
from pmd_beamphysics import FieldMesh In\u00a0[3]: Copied!
FM1 = FieldMesh('../data/rfgun.h5')\nFM1.plot('re_E', aspect='equal', figsize=(12,4))\n
FM1 = FieldMesh('../data/rfgun.h5') FM1.plot('re_E', aspect='equal', figsize=(12,4)) In\u00a0[4]: Copied!
FM3D = FieldMesh.from_ansys_ascii_3d(efile='../data/ansys_rfgun_2856MHz_E.dat',\n                              hfile='../data/ansys_rfgun_2856MHz_H.dat',\n                              frequency=2856e6)\n\n\n\nFM3D\n
FM3D = FieldMesh.from_ansys_ascii_3d(efile='../data/ansys_rfgun_2856MHz_E.dat', hfile='../data/ansys_rfgun_2856MHz_H.dat', frequency=2856e6) FM3D Out[4]:
<FieldMesh with rectangular geometry and (3, 3, 457) shape at 0x7fa0c4d64ee0>

This will convert to 2D cylindrical:

In\u00a0[5]: Copied!
FM2 = FM3D.to_cylindrical()\nFM2\n
FM2 = FM3D.to_cylindrical() FM2 Out[5]:
<FieldMesh with cylindrical geometry and (2, 1, 457) shape at 0x7fa07afc4a90>

Spacing is different:

In\u00a0[6]: Copied!
FM1.dr, FM2.dr\n
FM1.dr, FM2.dr Out[6]:
(0.00025, 0.001)

Set a scale to rotate and normalize Ez to be mostly real

In\u00a0[7]: Copied!
E0 = FM2.components['electricField/z'][0,0,0]\nFM2.scale = -np.exp(-1j * np.angle(E0)) / np.abs(E0) # - sign to agree with FM1\n\nFM2.Ez[0,0,0]\n
E0 = FM2.components['electricField/z'][0,0,0] FM2.scale = -np.exp(-1j * np.angle(E0)) / np.abs(E0) # - sign to agree with FM1 FM2.Ez[0,0,0] Out[7]:
(-1-1.890985933883628e-16j)
In\u00a0[8]: Copied!
z1 = FM1.coord_vec('z')\nz2 = FM2.coord_vec('z')\n\n\nfig, ax = plt.subplots()\nax.plot(z1, np.real(FM1.Ez[0,0,:]), label='Superfish')\nax.plot(z2, np.real(FM2.Ez[0,0,:]), label='ANSYS')\nax.set_xlabel(r'$z$ (m)')\nax.set_ylabel(r'$E_z$ (V/m)')\nplt.legend()\n
z1 = FM1.coord_vec('z') z2 = FM2.coord_vec('z') fig, ax = plt.subplots() ax.plot(z1, np.real(FM1.Ez[0,0,:]), label='Superfish') ax.plot(z2, np.real(FM2.Ez[0,0,:]), label='ANSYS') ax.set_xlabel(r'$z$ (m)') ax.set_ylabel(r'$E_z$ (V/m)') plt.legend() Out[8]:
<matplotlib.legend.Legend at 0x7fa07af99ee0>
In\u00a0[9]: Copied!
fig, ax = plt.subplots()\nax.plot(z1, np.imag(FM1.Btheta[4,0,:]), label='superfish')\nax.plot(z2, np.imag(FM2.Btheta[1,0,:]), label='ansys')\n\nax.set_title(fr'$r$ = {FM2.dr*1000} mm')\nax.set_xlabel(r'$z$ (m)')\nax.set_ylabel(r'$B_\\theta$ (T)')\nplt.legend()\n
fig, ax = plt.subplots() ax.plot(z1, np.imag(FM1.Btheta[4,0,:]), label='superfish') ax.plot(z2, np.imag(FM2.Btheta[1,0,:]), label='ansys') ax.set_title(fr'$r$ = {FM2.dr*1000} mm') ax.set_xlabel(r'$z$ (m)') ax.set_ylabel(r'$B_\\theta$ (T)') plt.legend() Out[9]:
<matplotlib.legend.Legend at 0x7fa07aebb130>

The magnetic field is out of phase, so use the im_ syntax:

In\u00a0[10]: Copied!
FM2.plot('im_Btheta', aspect='equal', figsize=(12,4))\n
FM2.plot('im_Btheta', aspect='equal', figsize=(12,4))

Max on-axis field:

In\u00a0[11]: Copied!
np.abs(FM2.Ez[0,0,:]).max()\n
np.abs(FM2.Ez[0,0,:]).max() Out[11]:
1.0153992993439902
In\u00a0[12]: Copied!
def check_oscillation(FM, label=''):\n\n    c_light = 299792458.\n    \n    dr = FM.dr\n    omega = FM.frequency*2*np.pi\n    \n    # Check the first off-axis grid points\n    z0 = FM.z\n    Ez0 = np.real(FM.Ez[0,0,:])\n    B1 = -np.imag(FM.Btheta[1,0,:])\n    \n    plt.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$')\n    plt.plot(z0, B1*2/dr *c_light**2/omega, '--', label=r'$-\\frac{r}{2}\\frac{\\omega}{c^2} \\Im\\left(B_\\theta\\right)$')\n    plt.ylabel('field (V/m)')\n    plt.xlabel('z (m)')\n    plt.legend()\n    plt.title(fr'Complex field oscillation{label}')\n
def check_oscillation(FM, label=''): c_light = 299792458. dr = FM.dr omega = FM.frequency*2*np.pi # Check the first off-axis grid points z0 = FM.z Ez0 = np.real(FM.Ez[0,0,:]) B1 = -np.imag(FM.Btheta[1,0,:]) plt.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$') plt.plot(z0, B1*2/dr *c_light**2/omega, '--', label=r'$-\\frac{r}{2}\\frac{\\omega}{c^2} \\Im\\left(B_\\theta\\right)$') plt.ylabel('field (V/m)') plt.xlabel('z (m)') plt.legend() plt.title(fr'Complex field oscillation{label}') In\u00a0[13]: Copied!
check_oscillation(FM1, ', Superfish')\n
check_oscillation(FM1, ', Superfish') In\u00a0[14]: Copied!
check_oscillation(FM2, ', ANSYS')\n
check_oscillation(FM2, ', ANSYS')"},{"location":"examples/fields/field_conversion/#fieldmesh-conversion","title":"FieldMesh Conversion\u00b6","text":""},{"location":"examples/fields/field_conversion/#2d-cylindrically-symmetric-rf-gun-fieldmesh","title":"2D cylindrically symmetric RF gun FieldMesh\u00b6","text":"

This data was originally generated using Superfish.

"},{"location":"examples/fields/field_conversion/#3d-rectangular-field-from-ansys","title":"3D rectangular field from ANSYS\u00b6","text":""},{"location":"examples/fields/field_conversion/#verify-the-oscillation","title":"Verify the oscillation\u00b6","text":"

Complex fields oscillate as $e^{-i\\omega t}$. For TM fields, the spatial components $E_z$ and $B_\\theta$ near the axis

$\\Re E_{z} = -\\frac{r}{2}\\frac{\\omega}{c^2} \\Im B_\\theta$

"},{"location":"examples/fields/field_examples/","title":"FieldMesh Examples","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n\n# Nicer plotting\nimport matplotlib.pyplot as plt\n%config InlineBackend.figure_format = 'retina'\n\nimport numpy as np\n
# Useful for debugging %load_ext autoreload %autoreload 2 # Nicer plotting import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' import numpy as np In\u00a0[2]: Copied!
from pmd_beamphysics import FieldMesh, tools\n
from pmd_beamphysics import FieldMesh, tools In\u00a0[3]: Copied!
FM = FieldMesh('../data/solenoid.h5')\nFM\n
FM = FieldMesh('../data/solenoid.h5') FM Out[3]:
<FieldMesh with cylindrical geometry and (101, 1, 201) shape at 0x7ff4c83e0df0>

Built-in plotting:

In\u00a0[4]: Copied!
FM.plot('B', aspect='equal')\n
FM.plot('B', aspect='equal')

On-axis field plotting

In\u00a0[5]: Copied!
FM.plot_onaxis()\n
FM.plot_onaxis() In\u00a0[6]: Copied!
FM.attrs, FM.components.keys()\n
FM.attrs, FM.components.keys() Out[6]:
({'eleAnchorPt': 'beginning',\n  'gridGeometry': 'cylindrical',\n  'axisLabels': array(['r', 'theta', 'z'], dtype='<U5'),\n  'gridLowerBound': array([0, 1, 0]),\n  'gridOriginOffset': array([ 0. ,  0. , -0.1]),\n  'gridSpacing': array([0.001, 0.   , 0.001]),\n  'gridSize': array([101,   1, 201]),\n  'harmonic': 0,\n  'fundamentalFrequency': 0,\n  'RFphase': 0,\n  'fieldScale': 1.0},\n dict_keys(['magneticField/z', 'magneticField/r']))
In\u00a0[7]: Copied!
FM.shape\n
FM.shape Out[7]:
(101, 1, 201)
In\u00a0[8]: Copied!
FM.frequency\n
FM.frequency Out[8]:
0

Coordinate vectors: .r, .theta, .z, etc.

In\u00a0[9]: Copied!
FM.r, FM.dr\n
FM.r, FM.dr Out[9]:
(array([0.   , 0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007, 0.008,\n        0.009, 0.01 , 0.011, 0.012, 0.013, 0.014, 0.015, 0.016, 0.017,\n        0.018, 0.019, 0.02 , 0.021, 0.022, 0.023, 0.024, 0.025, 0.026,\n        0.027, 0.028, 0.029, 0.03 , 0.031, 0.032, 0.033, 0.034, 0.035,\n        0.036, 0.037, 0.038, 0.039, 0.04 , 0.041, 0.042, 0.043, 0.044,\n        0.045, 0.046, 0.047, 0.048, 0.049, 0.05 , 0.051, 0.052, 0.053,\n        0.054, 0.055, 0.056, 0.057, 0.058, 0.059, 0.06 , 0.061, 0.062,\n        0.063, 0.064, 0.065, 0.066, 0.067, 0.068, 0.069, 0.07 , 0.071,\n        0.072, 0.073, 0.074, 0.075, 0.076, 0.077, 0.078, 0.079, 0.08 ,\n        0.081, 0.082, 0.083, 0.084, 0.085, 0.086, 0.087, 0.088, 0.089,\n        0.09 , 0.091, 0.092, 0.093, 0.094, 0.095, 0.096, 0.097, 0.098,\n        0.099, 0.1  ]),\n 0.001)

Grid info

In\u00a0[10]: Copied!
FM.mins, FM.maxs, FM.deltas\n
FM.mins, FM.maxs, FM.deltas Out[10]:
(array([ 0. ,  0. , -0.1]),\n array([0.1, 0. , 0.1]),\n array([0.001, 0.   , 0.001]))

Convenient logicals

In\u00a0[11]: Copied!
FM.is_static, FM.is_pure_magnetic, FM.is_pure_magnetic,  FM.is_pure_electric\n
FM.is_static, FM.is_pure_magnetic, FM.is_pure_magnetic, FM.is_pure_electric Out[11]:
(True, True, True, False)
In\u00a0[12]: Copied!
FM.components\n
FM.components Out[12]:
{'magneticField/z': array([[[ 4.10454985e-03,  4.31040451e-03,  4.52986744e-03, ...,\n           4.67468517e-04,  3.93505841e-04,  3.31380794e-04]],\n \n        [[ 4.10132316e-03,  4.30698128e-03,  4.52613784e-03, ...,\n           4.63910019e-04,  3.90463457e-04,  3.28826095e-04]],\n \n        [[ 4.09178241e-03,  4.29666227e-03,  4.51500745e-03, ...,\n           4.53304832e-04,  3.81497195e-04,  3.21252672e-04]],\n \n        ...,\n \n        [[-8.55276742e-05, -9.25454620e-05, -9.97134392e-05, ...,\n          -1.67910069e-13, -1.66617291e-13, -1.69112101e-13]],\n \n        [[-8.66606075e-05, -9.34605759e-05, -1.00393739e-04, ...,\n          -1.63746446e-13, -1.62385457e-13, -1.63975660e-13]],\n \n        [[-8.76493773e-05, -9.42325632e-05, -1.00947206e-04, ...,\n          -1.59165583e-13, -1.57653026e-13, -1.58633209e-13]]]),\n 'magneticField/r': array([[[ 0.00000000e+00,  0.00000000e+00,  0.00000000e+00, ...,\n           0.00000000e+00,  0.00000000e+00,  0.00000000e+00]],\n \n        [[-9.96833640e-05, -1.06224487e-04, -1.13203127e-04, ...,\n           4.01781064e-05,  3.37384918e-05,  2.82880488e-05]],\n \n        [[-1.99034573e-04, -2.12052909e-04, -2.25955469e-04, ...,\n           7.94909429e-05,  6.67047656e-05,  5.59040132e-05]],\n \n        ...,\n \n        [[-3.28171418e-04, -3.29256623e-04, -3.30141629e-04, ...,\n           5.98175517e-14,  5.84734577e-14,  5.07218297e-14]],\n \n        [[-3.18048731e-04, -3.18985999e-04, -3.19728362e-04, ...,\n           5.74787071e-14,  5.71575012e-14,  5.06326785e-14]],\n \n        [[-3.08270029e-04, -3.09070567e-04, -3.09682173e-04, ...,\n           5.47143752e-14,  5.54052993e-14,  4.98595495e-14]]])}

Convenient access to component data

In\u00a0[13]: Copied!
FM.Bz is FM['magneticField/z']\n
FM.Bz is FM['magneticField/z'] Out[13]:
True

Setting .scale will set the underlying attribute

In\u00a0[14]: Copied!
FM.scale = 2\nFM.attrs['fieldScale'], FM.scale\n
FM.scale = 2 FM.attrs['fieldScale'], FM.scale Out[14]:
(2, 2)

Raw components accessed by their full key

In\u00a0[15]: Copied!
FM['magneticField/z']\n
FM['magneticField/z'] Out[15]:
array([[[ 4.10454985e-03,  4.31040451e-03,  4.52986744e-03, ...,\n          4.67468517e-04,  3.93505841e-04,  3.31380794e-04]],\n\n       [[ 4.10132316e-03,  4.30698128e-03,  4.52613784e-03, ...,\n          4.63910019e-04,  3.90463457e-04,  3.28826095e-04]],\n\n       [[ 4.09178241e-03,  4.29666227e-03,  4.51500745e-03, ...,\n          4.53304832e-04,  3.81497195e-04,  3.21252672e-04]],\n\n       ...,\n\n       [[-8.55276742e-05, -9.25454620e-05, -9.97134392e-05, ...,\n         -1.67910069e-13, -1.66617291e-13, -1.69112101e-13]],\n\n       [[-8.66606075e-05, -9.34605759e-05, -1.00393739e-04, ...,\n         -1.63746446e-13, -1.62385457e-13, -1.63975660e-13]],\n\n       [[-8.76493773e-05, -9.42325632e-05, -1.00947206e-04, ...,\n         -1.59165583e-13, -1.57653026e-13, -1.58633209e-13]]])

Scaled component accessed by shorter keys, e.g.

In\u00a0[16]: Copied!
FM['Bz']\n
FM['Bz'] Out[16]:
array([[[ 8.20909970e-03,  8.62080901e-03,  9.05973488e-03, ...,\n          9.34937033e-04,  7.87011682e-04,  6.62761588e-04]],\n\n       [[ 8.20264631e-03,  8.61396257e-03,  9.05227569e-03, ...,\n          9.27820038e-04,  7.80926913e-04,  6.57652189e-04]],\n\n       [[ 8.18356483e-03,  8.59332454e-03,  9.03001490e-03, ...,\n          9.06609663e-04,  7.62994390e-04,  6.42505344e-04]],\n\n       ...,\n\n       [[-1.71055348e-04, -1.85090924e-04, -1.99426878e-04, ...,\n         -3.35820138e-13, -3.33234581e-13, -3.38224202e-13]],\n\n       [[-1.73321215e-04, -1.86921152e-04, -2.00787478e-04, ...,\n         -3.27492892e-13, -3.24770914e-13, -3.27951319e-13]],\n\n       [[-1.75298755e-04, -1.88465126e-04, -2.01894411e-04, ...,\n         -3.18331166e-13, -3.15306051e-13, -3.17266417e-13]]])
In\u00a0[17]: Copied!
FM['magneticField/z'].max(), FM['Bz'].max()\n
FM['magneticField/z'].max(), FM['Bz'].max() Out[17]:
(2.150106838829148, 4.300213677658296)
In\u00a0[18]: Copied!
FM = FieldMesh('../data/rfgun.h5')\nFM.plot('re_E', aspect='equal', figsize=(12,4))\n
FM = FieldMesh('../data/rfgun.h5') FM.plot('re_E', aspect='equal', figsize=(12,4))

The magnetic field is out of phase, so use the im_ syntax:

In\u00a0[19]: Copied!
FM.plot('im_Btheta', aspect='equal', figsize=(12,4))\n
FM.plot('im_Btheta', aspect='equal', figsize=(12,4))

Max on-axis field:

In\u00a0[20]: Copied!
np.abs(FM.Ez[0,0,:]).max()\n
np.abs(FM.Ez[0,0,:]).max() Out[20]:
1.0
In\u00a0[21]: Copied!
c_light = 299792458.\n\ndr = FM.dr\nomega = FM.frequency*2*np.pi\n\n# Check the first off-axis grid points\nz0 = FM.z\nEz0 = np.real(FM.Ez[0,0,:])\nB1 = -np.imag(FM.Btheta[1,0,:])\n\nplt.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$')\nplt.plot(z0, B1*2/dr *c_light**2/omega, '--', label=r'$-\\frac{r}{2}\\frac{\\omega}{c^2} \\Im\\left(B_\\theta\\right)$')\nplt.ylabel('field (V/m)')\nplt.xlabel('z (m)')\nplt.legend()\nplt.title(r'Complex field oscillation')\n
c_light = 299792458. dr = FM.dr omega = FM.frequency*2*np.pi # Check the first off-axis grid points z0 = FM.z Ez0 = np.real(FM.Ez[0,0,:]) B1 = -np.imag(FM.Btheta[1,0,:]) plt.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$') plt.plot(z0, B1*2/dr *c_light**2/omega, '--', label=r'$-\\frac{r}{2}\\frac{\\omega}{c^2} \\Im\\left(B_\\theta\\right)$') plt.ylabel('field (V/m)') plt.xlabel('z (m)') plt.legend() plt.title(r'Complex field oscillation') Out[21]:
Text(0.5, 1.0, 'Complex field oscillation')
In\u00a0[22]: Copied!
FM.units('Bz')\n
FM.units('Bz') Out[22]:
pmd_unit('T', 1, (0, 1, -2, -1, 0, 0, 0))

This also works:

In\u00a0[23]: Copied!
FM.units('abs_Ez')\n
FM.units('abs_Ez') Out[23]:
pmd_unit('V/m', 1, (1, 1, -3, -1, 0, 0, 0))
In\u00a0[24]: Copied!
FM.write('rfgun2.h5')\n
FM.write('rfgun2.h5')

Read back and make sure the data are the same.

In\u00a0[25]: Copied!
FM2 = FieldMesh('rfgun2.h5')\n\nassert FM == FM2\n
FM2 = FieldMesh('rfgun2.h5') assert FM == FM2

Write to open HDF5 file and test reload:

In\u00a0[26]: Copied!
import h5py\nwith h5py.File('test.h5', 'w') as h5:\n    FM.write(h5, name='myfield')\n    FM2 = FieldMesh(h5=h5['myfield'])\n    assert FM == FM2\n
import h5py with h5py.File('test.h5', 'w') as h5: FM.write(h5, name='myfield') FM2 = FieldMesh(h5=h5['myfield']) assert FM == FM2 In\u00a0[27]: Copied!
FM.write_astra_1d('astra_1d.dat')\n
FM.write_astra_1d('astra_1d.dat')

Another method returns the array data with some annotation

In\u00a0[28]: Copied!
FM.to_astra_1d()\n
FM.to_astra_1d() Out[28]:
{'attrs': {'type': 'astra_1d'},\n 'data': array([[ 0.00000000e+00, -1.00000000e+00],\n        [ 2.50000000e-04, -9.99967412e-01],\n        [ 5.00000000e-04, -9.99865106e-01],\n        ...,\n        [ 1.29500000e-01,  1.45678834e-04],\n        [ 1.29750000e-01,  1.40392476e-04],\n        [ 1.30000000e-01,  1.35399670e-04]])}
In\u00a0[29]: Copied!
idata = FM.to_impact_solrf()\nidata.keys()\n
idata = FM.to_impact_solrf() idata.keys() Out[29]:
dict_keys(['line', 'rfdata', 'ele', 'fmap'])

This is an element that can be used with LUME-Impact

In\u00a0[30]: Copied!
idata['ele']\n
idata['ele'] Out[30]:
{'L': 0.13,\n 'type': 'solrf',\n 'zedge': 0,\n 'rf_field_scale': 1,\n 'rf_frequency': 2855998506.158,\n 'theta0_deg': 0.0,\n 'filename': 'rfdata666',\n 'radius': 0.15,\n 'x_offset': 0,\n 'y_offset': 0,\n 'x_rotation': 0.0,\n 'y_rotation': 0.0,\n 'z_rotation': 0.0,\n 'solenoid_field_scale': 0,\n 'name': 'solrf_666',\n 's': 0.13}

This is a line that would be used

In\u00a0[31]: Copied!
idata['line']\n
idata['line'] Out[31]:
'0.13 0 0 105 0 1 2855998506.158 0.0 666 0.15 0 0 0 0 0 0 /name:solrf_666'

Data that would be written to the rfdata999 file

In\u00a0[32]: Copied!
idata['rfdata']\n
idata['rfdata'] Out[32]:
array([ 5.90000000e+01, -1.30000000e-01,  1.30000000e-01,  2.60000000e-01,\n        2.38794165e-01, -3.04717859e-01, -3.56926831e-17, -7.49524991e-01,\n        7.29753328e-18, -2.59757413e-01, -4.68937899e-17,  1.27535902e-01,\n       -6.30755527e-17,  4.49651550e-02, -1.49234509e-16,  4.67567146e-03,\n       -1.74265243e-16,  3.32025307e-02,  6.08199268e-17, -2.74904837e-03,\n        1.81697659e-16, -1.55710320e-02, -1.45475359e-16,  2.64475111e-03,\n       -1.20376479e-16,  9.51814907e-04,  2.52133666e-16, -2.68547441e-03,\n        4.07323339e-16,  1.50824509e-03,  3.51376926e-16,  7.16574075e-04,\n        1.71392948e-16, -9.25573616e-04, -1.42917735e-16,  2.40084183e-04,\n       -1.19272642e-16,  2.61495768e-04,  1.87867359e-16, -2.41687337e-04,\n       -9.72891282e-18,  6.22859548e-05, -3.09382521e-16,  7.85163760e-05,\n       -2.59194056e-16, -8.56926328e-05, -2.56768407e-16,  2.38418521e-06,\n       -2.89351931e-16,  2.84696849e-05, -1.77696470e-16, -2.10693774e-05,\n       -8.56616099e-18,  4.74764623e-06, -1.26336157e-16,  9.74703896e-06,\n       -1.86248124e-16, -6.17509459e-06, -2.08540055e-16, -1.04844029e-06,\n       -1.86006363e-16,  3.01586958e-06,  1.90371674e-16,  1.00000000e+00,\n       -1.30000000e-01,  1.30000000e-01,  2.60000000e-01,  0.00000000e+00])

This is the fieldmap that makes that data:

In\u00a0[33]: Copied!
fmap = idata['fmap']\nfmap.keys()\n
fmap = idata['fmap'] fmap.keys() Out[33]:
dict_keys(['info', 'data', 'field'])

Additional info:

In\u00a0[34]: Copied!
fmap['info']\n
fmap['info'] Out[34]:
{'format': 'solrf',\n 'Ez_scale': 1.0,\n 'Ez_err': 7.68588630296298e-08,\n 'Bz_scale': 0.0,\n 'Bz_err': 0,\n 'zmin': -0.13,\n 'zmax': 0.13}
In\u00a0[35]: Copied!
from pmd_beamphysics.interfaces.impact import fourier_field_reconsruction\nL = z0.ptp()\nzlist = np.linspace(0, L, len(Ez0))\nfcoefs = fmap['field']['Ez']['fourier_coefficients']\nreconstructed_Ez0 = np.array([fourier_field_reconsruction(z, fcoefs, z0=-L, zlen = 2*L) for z in zlist])\n\nfig, ax = plt.subplots()\nax2 = ax.twinx()\nax.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$', color='black')\nax.plot(zlist, reconstructed_Ez0, '--',  label='reconstructed', color='red', )\nax2.plot(zlist, abs(reconstructed_Ez0/Ez0 -1), '--',  label='reconstructed', color='black' )\nax2.set_ylabel('relative error')\nax2.set_yscale('log')\nax.set_ylabel('field (V/m)')\nax.set_xlabel('z (m)')\nplt.legend()\n
from pmd_beamphysics.interfaces.impact import fourier_field_reconsruction L = z0.ptp() zlist = np.linspace(0, L, len(Ez0)) fcoefs = fmap['field']['Ez']['fourier_coefficients'] reconstructed_Ez0 = np.array([fourier_field_reconsruction(z, fcoefs, z0=-L, zlen = 2*L) for z in zlist]) fig, ax = plt.subplots() ax2 = ax.twinx() ax.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$', color='black') ax.plot(zlist, reconstructed_Ez0, '--', label='reconstructed', color='red', ) ax2.plot(zlist, abs(reconstructed_Ez0/Ez0 -1), '--', label='reconstructed', color='black' ) ax2.set_ylabel('relative error') ax2.set_yscale('log') ax.set_ylabel('field (V/m)') ax.set_xlabel('z (m)') plt.legend() Out[35]:
<matplotlib.legend.Legend at 0x7ff4742ea8b0>

This function can also be used to study the reconstruction error as a function of the number of coefficients:

In\u00a0[36]: Copied!
ncoefs = np.arange(10, FM2.shape[2]//2)\nerrs = np.array([FM2.to_impact_solrf(n_coef = n, zmirror=True)['fmap']['info']['Ez_err'] for n in ncoefs])\n\nfig, ax = plt.subplots()\nax.plot(ncoefs, errs, marker='.', color='black')\nax.set_xlabel('n_coef')\nax.set_ylabel('Ez reconstruction error')\nax.set_yscale('log')\n
ncoefs = np.arange(10, FM2.shape[2]//2) errs = np.array([FM2.to_impact_solrf(n_coef = n, zmirror=True)['fmap']['info']['Ez_err'] for n in ncoefs]) fig, ax = plt.subplots() ax.plot(ncoefs, errs, marker='.', color='black') ax.set_xlabel('n_coef') ax.set_ylabel('Ez reconstruction error') ax.set_yscale('log') In\u00a0[37]: Copied!
#FM.write_gpt('solenoid.gdf', asci2gdf_bin='$ASCI2GDF_BIN', verbose=True)\nFM.write_gpt('rfgun_for_gpt.txt', verbose=True)\n
#FM.write_gpt('solenoid.gdf', asci2gdf_bin='$ASCI2GDF_BIN', verbose=True) FM.write_gpt('rfgun_for_gpt.txt', verbose=True)
ASCII field data written. Convert to GDF using: asci2df -o field.gdf rfgun_for_gpt.txt\n
Out[37]:
'rfgun_for_gpt.txt'
In\u00a0[38]: Copied!
FM.write_superfish('rfgun2.t7')\n
FM.write_superfish('rfgun2.t7') Out[38]:
'rfgun2.t7'
In\u00a0[39]: Copied!
FM3 = FieldMesh.from_superfish('rfgun2.t7')\nFM3\n
FM3 = FieldMesh.from_superfish('rfgun2.t7') FM3 Out[39]:
<FieldMesh with cylindrical geometry and (61, 1, 521) shape at 0x7ff475451d30>
In\u00a0[40]: Copied!
help(FieldMesh.from_superfish)\n
help(FieldMesh.from_superfish)
Help on method read_superfish_t7 in module pmd_beamphysics.interfaces.superfish:\n\nread_superfish_t7(type=None, geometry='cylindrical') method of builtins.type instance\n    Parses a T7 file written by Posson/Superfish.\n    \n    Fish or Poisson T7 are automatically detected according to the second line.\n    \n    For Poisson problems, the type must be specified.\n    \n    Superfish fields oscillate as:\n        Er, Ez ~ cos(wt)\n        Hphi   ~ -sin(wt)\n      \n    For complex fields oscillating as e^-iwt\n    \n        Re(Ex*e^-iwt)   ~ cos\n        Re(-iB*e^-iwt) ~ -sin        \n    and therefore B = -i * mu_0 * H_phi is the complex magnetic field in Tesla\n    \n    \n    Parameters:\n    ----------\n    filename: str\n        T7 filename to read\n    type: str, optional\n        For Poisson files, required to be 'electric' or 'magnetic'. \n        Not used for Fish files\n    geometry: str, optional\n        field geometry, currently required to be the default: 'cylindrical'\n    \n    Returns:\n    -------\n    fieldmesh_data: dict of dicts:\n        attrs\n        components\n        \n        \n    A FieldMesh object is instantiated from this as:\n        FieldMesh(data=fieldmesh_data)\n\n

Note that writing the ASCII and conversions alter the data slightly

In\u00a0[41]: Copied!
FM == FM3\n
FM == FM3 Out[41]:
False

But the data are all close:

In\u00a0[42]: Copied!
for c in FM.components:\n    close = np.allclose(FM.components[c], FM3.components[c])\n    equal = np.all(FM.components[c] == FM3.components[c])\n    print(c, equal, close)\n
for c in FM.components: close = np.allclose(FM.components[c], FM3.components[c]) equal = np.all(FM.components[c] == FM3.components[c]) print(c, equal, close)
electricField/z False True\nelectricField/r False True\nmagneticField/theta False True\n
In\u00a0[43]: Copied!
FM3D = FieldMesh.from_ansys_ascii_3d(efile='../data/ansys_rfgun_2856MHz_E.dat',\n                              hfile='../data/ansys_rfgun_2856MHz_H.dat',\n                              frequency=2856e6)\n\n\n\nFM3D\n
FM3D = FieldMesh.from_ansys_ascii_3d(efile='../data/ansys_rfgun_2856MHz_E.dat', hfile='../data/ansys_rfgun_2856MHz_H.dat', frequency=2856e6) FM3D Out[43]:
<FieldMesh with rectangular geometry and (3, 3, 457) shape at 0x7ff47415a910>
In\u00a0[44]: Copied!
FM3D.attrs\n
FM3D.attrs Out[44]:
{'eleAnchorPt': 'beginning',\n 'gridGeometry': 'rectangular',\n 'axisLabels': ('x', 'y', 'z'),\n 'gridLowerBound': (0, 0, 0),\n 'gridOriginOffset': (-0.001, -0.001, 0.0),\n 'gridSpacing': (0.001, 0.001, 0.00025),\n 'gridSize': (3, 3, 457),\n 'harmonic': 1,\n 'fundamentalFrequency': 2856000000.0,\n 'RFphase': 0,\n 'fieldScale': 1.0}

This can then be written:

In\u00a0[45]: Copied!
FM3D.write('../data/rfgun_rectangular.h5')\n
FM3D.write('../data/rfgun_rectangular.h5')

The y=0 plane can be extracted to be used as cylindrically symmetric data:

In\u00a0[46]: Copied!
FM2D = FM3D.to_cylindrical()\nFM2D\n
FM2D = FM3D.to_cylindrical() FM2D Out[46]:
<FieldMesh with cylindrical geometry and (2, 1, 457) shape at 0x7ff47415ad30>
In\u00a0[47]: Copied!
import os\nfor file in ('test.h5',\n             'astra_1d.dat',\n             'rfgun_for_gpt.txt',\n             'rfgun2.h5',\n             'rfgun2.t7'):\n    os.remove(file)\n
import os for file in ('test.h5', 'astra_1d.dat', 'rfgun_for_gpt.txt', 'rfgun2.h5', 'rfgun2.t7'): os.remove(file)"},{"location":"examples/fields/field_examples/#fieldmesh-examples","title":"FieldMesh Examples\u00b6","text":""},{"location":"examples/fields/field_examples/#internal-data","title":"Internal data\u00b6","text":"

attributes and components

"},{"location":"examples/fields/field_examples/#properties","title":"Properties\u00b6","text":"

Convenient access to these

"},{"location":"examples/fields/field_examples/#components","title":"Components\u00b6","text":""},{"location":"examples/fields/field_examples/#oscillating-fields","title":"Oscillating fields\u00b6","text":"

Oscillating fields have .harmonic > 0

"},{"location":"examples/fields/field_examples/#verify-the-oscillation","title":"Verify the oscillation\u00b6","text":"

Complex fields oscillate as $e^{-i\\omega t}$. For TM fields, the spatial components $E_z$ and $B_\\theta$ near the axis

$\\Re E_{z} = -\\frac{r}{2}\\frac{\\omega}{c^2} \\Im B_\\theta$

"},{"location":"examples/fields/field_examples/#units","title":"Units\u00b6","text":""},{"location":"examples/fields/field_examples/#write","title":"Write\u00b6","text":""},{"location":"examples/fields/field_examples/#write-astra-1d","title":"Write Astra 1D\u00b6","text":"

Astra primarily uses simple 1D (on-axis) fieldmaps.

"},{"location":"examples/fields/field_examples/#write-impact-t","title":"Write Impact-T\u00b6","text":"

Impact-T uses a particular Fourier representation for 1D fields. These routines form this data.

"},{"location":"examples/fields/field_examples/#write-gpt","title":"Write GPT\u00b6","text":""},{"location":"examples/fields/field_examples/#read-superfish","title":"Read Superfish\u00b6","text":"

Proper Superfish T7 can also be read.

"},{"location":"examples/fields/field_examples/#read-ansys","title":"Read ANSYS\u00b6","text":"

Read ANSYS E and H ASCII files:

"},{"location":"examples/fields/field_examples/#cleanup","title":"Cleanup\u00b6","text":""},{"location":"examples/fields/field_expansion/","title":"Field expansion","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n\n# Nicer plotting\nimport matplotlib.pyplot as plt\n%config InlineBackend.figure_format = 'retina'\n\nimport numpy as np\n
# Useful for debugging %load_ext autoreload %autoreload 2 # Nicer plotting import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' import numpy as np In\u00a0[2]: Copied!
from pmd_beamphysics import FieldMesh\n
from pmd_beamphysics import FieldMesh In\u00a0[3]: Copied!
FM = FieldMesh('../data/solenoid.h5')\nFM.plot()\n
FM = FieldMesh('../data/solenoid.h5') FM.plot() In\u00a0[4]: Copied!
FM.plot_onaxis()\n
FM.plot_onaxis() In\u00a0[5]: Copied!
from pmd_beamphysics.fields.expansion import fft_derivative_array, spline_derivative_array\n
from pmd_beamphysics.fields.expansion import fft_derivative_array, spline_derivative_array In\u00a0[6]: Copied!
Z = FM.coord_vec('z')\nDZ = FM.dz\nFZ = FM.Bz[0,0,:]\n\ndfield1 = fft_derivative_array(FZ, DZ, ncoef=10)\ndfield2 = spline_derivative_array(Z, FZ, s=1e-9)\n
Z = FM.coord_vec('z') DZ = FM.dz FZ = FM.Bz[0,0,:] dfield1 = fft_derivative_array(FZ, DZ, ncoef=10) dfield2 = spline_derivative_array(Z, FZ, s=1e-9) In\u00a0[7]: Copied!
plt.plot(Z, dfield1[:,1], label='fft')\nplt.plot(Z, dfield2[:,1], label='spline')\nplt.xlabel('z (m)')\nplt.ylabel(r'$dB_z/dz$' + r\" (T/m)\")\n
plt.plot(Z, dfield1[:,1], label='fft') plt.plot(Z, dfield2[:,1], label='spline') plt.xlabel('z (m)') plt.ylabel(r'$dB_z/dz$' + r\" (T/m)\") Out[7]:
Text(0, 0.5, '$dB_z/dz$ (T/m)')
In\u00a0[8]: Copied!
plt.plot(Z, dfield1[:,2], label='fft')\nplt.plot(Z, dfield2[:,2], label='spline')\nplt.xlabel('z (m)')\nplt.ylabel(r'$d^2B_z/dz^2$' + r\" (T/m^2)\")\nplt.legend()\n
plt.plot(Z, dfield1[:,2], label='fft') plt.plot(Z, dfield2[:,2], label='spline') plt.xlabel('z (m)') plt.ylabel(r'$d^2B_z/dz^2$' + r\" (T/m^2)\") plt.legend() Out[8]:
<matplotlib.legend.Legend at 0x7f32e4463ac0>
In\u00a0[9]: Copied!
plt.plot(Z, dfield1[:,3], label='fft')\nplt.plot(Z, dfield2[:,3], label='spline')\nplt.xlabel('z (m)')\nplt.ylabel(r'$d^3B_z/dz^3$' + r\" (T/m^3)\")\nplt.legend()\n
plt.plot(Z, dfield1[:,3], label='fft') plt.plot(Z, dfield2[:,3], label='spline') plt.xlabel('z (m)') plt.ylabel(r'$d^3B_z/dz^3$' + r\" (T/m^3)\") plt.legend() Out[9]:
<matplotlib.legend.Legend at 0x7f32e4412430>
In\u00a0[10]: Copied!
FM2 = FieldMesh.from_onaxis(z=Z, Bz=FZ)\nFM2.plot_onaxis()\n
FM2 = FieldMesh.from_onaxis(z=Z, Bz=FZ) FM2.plot_onaxis() In\u00a0[11]: Copied!
FM3 = FM2.expand_onaxis(dr=FM.dr, nr=10)\nFM3\n
FM3 = FM2.expand_onaxis(dr=FM.dr, nr=10) FM3 Out[11]:
<FieldMesh with cylindrical geometry and (10, 1, 201) shape at 0x7f32e437bbe0>
In\u00a0[12]: Copied!
FM3.plot('Br')\n
FM3.plot('Br') In\u00a0[13]: Copied!
def compare(fm1, fm2, component='Ez'):\n    \n    z = fm1.coord_vec('z')\n    dr = fm1.dr\n    nr = min(fm1.shape[0], fm2.shape[0])\n    \n    unit = fm1.units(component)\n    Fz1 =  np.squeeze(fm1[component])[0:nr, :]\n    Fz2 =  np.squeeze(fm2[component])[0:nr, :]\n    err = abs(Fz1-Fz2) / np.abs(Fz1).max() \n    \n    extent = np.array([z.min(), z.max(), 0, dr*(nr-1)]) * 1000\n    plt.imshow(err, origin='lower', extent = extent , aspect='auto')\n    plt.xlabel('z (mm)')\n    plt.ylabel('r (mm)')\n    \n    plt.title(f\"{component} expansion error, max err = {err.max()}\")\n    plt.colorbar(label=f'relatic expansion error')\n    \ncompare(FM, FM3, 'B')\n
def compare(fm1, fm2, component='Ez'): z = fm1.coord_vec('z') dr = fm1.dr nr = min(fm1.shape[0], fm2.shape[0]) unit = fm1.units(component) Fz1 = np.squeeze(fm1[component])[0:nr, :] Fz2 = np.squeeze(fm2[component])[0:nr, :] err = abs(Fz1-Fz2) / np.abs(Fz1).max() extent = np.array([z.min(), z.max(), 0, dr*(nr-1)]) * 1000 plt.imshow(err, origin='lower', extent = extent , aspect='auto') plt.xlabel('z (mm)') plt.ylabel('r (mm)') plt.title(f\"{component} expansion error, max err = {err.max()}\") plt.colorbar(label=f'relatic expansion error') compare(FM, FM3, 'B') In\u00a0[14]: Copied!
FM = FieldMesh('../data/rfgun.h5')\nFM.plot()\n
FM = FieldMesh('../data/rfgun.h5') FM.plot() In\u00a0[15]: Copied!
Z = FM.coord_vec('z')\nDZ = FM.dz\nFZ = np.real(FM.Ez[0,0,:])\n\nFM2 = FieldMesh.from_onaxis(z=Z, Ez=FZ, frequency=FM.frequency)\nFM2.plot_onaxis()\n
Z = FM.coord_vec('z') DZ = FM.dz FZ = np.real(FM.Ez[0,0,:]) FM2 = FieldMesh.from_onaxis(z=Z, Ez=FZ, frequency=FM.frequency) FM2.plot_onaxis() In\u00a0[\u00a0]: Copied!
\n
In\u00a0[16]: Copied!
NR = 40\nFM3 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='fft')\ncompare(FM, FM3, 'Er')\n
NR = 40 FM3 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='fft') compare(FM, FM3, 'Er') In\u00a0[17]: Copied!
compare(FM, FM3, 'Ez')\n
compare(FM, FM3, 'Ez') In\u00a0[18]: Copied!
compare(FM, FM3, 'Btheta')\n
compare(FM, FM3, 'Btheta') In\u00a0[19]: Copied!
NR = 40\nFM4 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='spline', spline_s=1e-9)\ncompare(FM, FM4, 'Er')\n
NR = 40 FM4 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='spline', spline_s=1e-9) compare(FM, FM4, 'Er') In\u00a0[20]: Copied!
compare(FM, FM4, 'Ez')\n
compare(FM, FM4, 'Ez') In\u00a0[21]: Copied!
compare(FM, FM4, 'Btheta')\n
compare(FM, FM4, 'Btheta') In\u00a0[22]: Copied!
compare(FM, FM4, 'Btheta')\n
compare(FM, FM4, 'Btheta') In\u00a0[23]: Copied!
# Differences between the two methods\ncompare(FM3, FM4, 'E')\n
# Differences between the two methods compare(FM3, FM4, 'E') In\u00a0[24]: Copied!
def compare2(comp='Er'):\n    NR = 10\n    dr = FM.dr\n    r = (NR-1)*FM.dr\n    FM5 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='fft', ncoef=15)\n    \n    if comp.startswith('E'):\n        func = np.real\n    else:\n        func = np.imag\n    \n    f0 = func(FM[comp][NR-1,0,:])\n    \n    f5 = func(FM5[comp][NR-1,0,:])\n    \n    FM6 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='spline', spline_s=1e-9)\n    f6 = func(FM6[comp][NR-1,0,:])\n    \n    fix, ax = plt.subplots()\n    ax2 = ax.twinx()\n    ax.plot(f0, label='original')\n    ax.plot(f5, '--', label='fourier')\n    ax.plot(f6, '--', label='spline')\n    ax.legend(loc='upper left')\n    ax2.plot(abs((f5-f0)/f0), color='purple', label='relative fourier error')\n    ax2.plot(abs((f6-f0)/f0), color='grey', label='relative spline error')\n    ax2.set_yscale('log')\n    ax.set_ylabel(comp)\n    ax2.set_ylabel('relative error')\n    ax2.legend(loc='upper right')\n    ax.set_xlabel('index along z')\n
def compare2(comp='Er'): NR = 10 dr = FM.dr r = (NR-1)*FM.dr FM5 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='fft', ncoef=15) if comp.startswith('E'): func = np.real else: func = np.imag f0 = func(FM[comp][NR-1,0,:]) f5 = func(FM5[comp][NR-1,0,:]) FM6 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='spline', spline_s=1e-9) f6 = func(FM6[comp][NR-1,0,:]) fix, ax = plt.subplots() ax2 = ax.twinx() ax.plot(f0, label='original') ax.plot(f5, '--', label='fourier') ax.plot(f6, '--', label='spline') ax.legend(loc='upper left') ax2.plot(abs((f5-f0)/f0), color='purple', label='relative fourier error') ax2.plot(abs((f6-f0)/f0), color='grey', label='relative spline error') ax2.set_yscale('log') ax.set_ylabel(comp) ax2.set_ylabel('relative error') ax2.legend(loc='upper right') ax.set_xlabel('index along z') In\u00a0[25]: Copied!
compare2('Er')\n
compare2('Er')
/tmp/ipykernel_5526/1820762901.py:25: RuntimeWarning: divide by zero encountered in divide\n  ax2.plot(abs((f5-f0)/f0), color='purple', label='relative fourier error')\n/tmp/ipykernel_5526/1820762901.py:26: RuntimeWarning: divide by zero encountered in divide\n  ax2.plot(abs((f6-f0)/f0), color='grey', label='relative spline error')\n
In\u00a0[26]: Copied!
compare2('Ez')\n
compare2('Ez') In\u00a0[27]: Copied!
compare2('Btheta')\n
compare2('Btheta')"},{"location":"examples/fields/field_expansion/#field-expansion","title":"Field expansion\u00b6","text":""},{"location":"examples/fields/field_expansion/#derivative-array","title":"Derivative array\u00b6","text":"

Field expansions depend on numerical derivatives of the on-axis field. Here are two methods.

"},{"location":"examples/fields/field_expansion/#fieldmesh-from-1d-data","title":"FieldMesh from 1D data\u00b6","text":""},{"location":"examples/fields/field_expansion/#expansion-1d-2d","title":"Expansion 1D -> 2D\u00b6","text":""},{"location":"examples/fields/field_expansion/#rf-gun-1d-2d","title":"RF Gun 1D -> 2D\u00b6","text":""},{"location":"examples/fields/field_expansion/#spline-based-expansion","title":"Spline-based expansion\u00b6","text":""},{"location":"examples/fields/field_expansion/#compare-fourier-and-spline","title":"Compare Fourier and Spline\u00b6","text":""},{"location":"examples/fields/field_tracking/","title":"Field Phasing and Scaling (Autophase)","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n\n# Nicer plotting\nimport matplotlib.pyplot as plt\n%matplotlib inline\n%config InlineBackend.figure_format = 'retina'\n\nimport numpy as np\n
# Useful for debugging %load_ext autoreload %autoreload 2 # Nicer plotting import matplotlib.pyplot as plt %matplotlib inline %config InlineBackend.figure_format = 'retina' import numpy as np In\u00a0[2]: Copied!
from pmd_beamphysics import FieldMesh\n
from pmd_beamphysics import FieldMesh In\u00a0[3]: Copied!
FM = FieldMesh('../data/rfgun.h5')\nFM.plot(aspect='equal', figsize=(12,8))\n
FM = FieldMesh('../data/rfgun.h5') FM.plot(aspect='equal', figsize=(12,8)) In\u00a0[4]: Copied!
# On-axis field\nz0 = FM.coord_vec('z')\nEz0 = FM.Ez[0,0,:]  # this is complex\nplt.plot(z0, np.real(Ez0))\n
# On-axis field z0 = FM.coord_vec('z') Ez0 = FM.Ez[0,0,:] # this is complex plt.plot(z0, np.real(Ez0)) Out[4]:
[<matplotlib.lines.Line2D at 0x1228734c0>]
In\u00a0[5]: Copied!
from pmd_beamphysics.fields.analysis import accelerating_voltage_and_phase\n
from pmd_beamphysics.fields.analysis import accelerating_voltage_and_phase In\u00a0[6]: Copied!
?accelerating_voltage_and_phase\n
?accelerating_voltage_and_phase In\u00a0[7]: Copied!
V0, phase0 = accelerating_voltage_and_phase(z0, -Ez0*120e6, FM.frequency)\n\nV0, (phase0 * 180/np.pi) % 360\n
V0, phase0 = accelerating_voltage_and_phase(z0, -Ez0*120e6, FM.frequency) V0, (phase0 * 180/np.pi) % 360 Out[7]:
(5795904.446882586, 322.1355626180106)

Equations of motion:

$\\frac{dz}{dt} = \\frac{pc}{\\sqrt{(pc)^2 + m^2 c^4)}} c$

$\\frac{dp}{dt} = q E_z $

$E_z = \\Re f(z) \\exp(-i \\omega t) $

In\u00a0[8]: Copied!
from pmd_beamphysics.fields.analysis import track_field_1d\nfrom pmd_beamphysics.units import mec2, c_light\n
from pmd_beamphysics.fields.analysis import track_field_1d from pmd_beamphysics.units import mec2, c_light In\u00a0[9]: Copied!
?track_field_1d\n
?track_field_1d In\u00a0[10]: Copied!
Z = FM.coord_vec('z')\nE = FM.Ez[0,0,:]*np.exp(1j*2*np.pi /360 * 0)*120e6 \n\n# Final z (m) and pz (eV/c)\ntrack_field_1d(Z, E, FM.frequency, pz0=0, t0=0)\n
Z = FM.coord_vec('z') E = FM.Ez[0,0,:]*np.exp(1j*2*np.pi /360 * 0)*120e6 # Final z (m) and pz (eV/c) track_field_1d(Z, E, FM.frequency, pz0=0, t0=0) Out[10]:
(0.13000001229731462, 3896770.3798088795)
In\u00a0[11]: Copied!
# Use debug mode to see the actual track\nsol = track_field_1d(Z, E, FM.frequency, pz0=0, t0=0, debug=True, max_step=1/FM.frequency/100)\n
# Use debug mode to see the actual track sol = track_field_1d(Z, E, FM.frequency, pz0=0, t0=0, debug=True, max_step=1/FM.frequency/100) In\u00a0[12]: Copied!
# Plot the track\nfig, ax = plt.subplots()\n\nax2 = ax.twinx()\n\nax.set_xlabel('f*t')\nax.set_ylabel('z (m)')\nax2.set_ylabel('KE (MeV)')\n\nax.plot(sol.t*FM.frequency, sol.y[0])\nax2.plot(sol.t*FM.frequency, (np.hypot(sol.y[1], mec2)-mec2)/1e6, color='red')\n
# Plot the track fig, ax = plt.subplots() ax2 = ax.twinx() ax.set_xlabel('f*t') ax.set_ylabel('z (m)') ax2.set_ylabel('KE (MeV)') ax.plot(sol.t*FM.frequency, sol.y[0]) ax2.plot(sol.t*FM.frequency, (np.hypot(sol.y[1], mec2)-mec2)/1e6, color='red') Out[12]:
[<matplotlib.lines.Line2D at 0x122e69940>]
In\u00a0[13]: Copied!
from pmd_beamphysics.fields.analysis import autophase_field\n
from pmd_beamphysics.fields.analysis import autophase_field In\u00a0[14]: Copied!
phase_deg1, pz1 = autophase_field(FM, pz0=0, scale=120e6, verbose=True)\nphase_deg1, pz1\n
phase_deg1, pz1 = autophase_field(FM, pz0=0, scale=120e6, verbose=True) phase_deg1, pz1
v=c voltage: 5795904.446882586 V, phase: -37.86443738198939 deg\n    iterations: 18\n    function calls: 23\n
Out[14]:
(304.334830187332, 6234145.7957780445)
In\u00a0[15]: Copied!
# Use debug mode to visualize. This returns the phasiing function\nphase_f = autophase_field(FM, pz0=0, scale=120e6, debug=True)\nphase_f(304.3348289439232)\n
# Use debug mode to visualize. This returns the phasiing function phase_f = autophase_field(FM, pz0=0, scale=120e6, debug=True) phase_f(304.3348289439232) Out[15]:
6234145.795777954
In\u00a0[16]: Copied!
plist = np.linspace(280, 330, 100)\npzlist = np.array([phase_f(p) for p in plist])\n\nplt.plot(plist, pzlist/1e6)\nplt.scatter(phase_deg1, pz1/1e6, color='red')\nplt.xlabel('phase (deg)')\nplt.ylabel('pz (MeV/c)')\n
plist = np.linspace(280, 330, 100) pzlist = np.array([phase_f(p) for p in plist]) plt.plot(plist, pzlist/1e6) plt.scatter(phase_deg1, pz1/1e6, color='red') plt.xlabel('phase (deg)') plt.ylabel('pz (MeV/c)') Out[16]:
Text(0, 0.5, 'pz (MeV/c)')
In\u00a0[17]: Copied!
from pmd_beamphysics.fields.analysis import autophase_and_scale_field\n?autophase_and_scale_field\n
from pmd_beamphysics.fields.analysis import autophase_and_scale_field ?autophase_and_scale_field In\u00a0[18]: Copied!
phase_deg2, scale2 = autophase_and_scale_field(FM, 6e6, pz0=0, verbose=True)\nphase_deg2, scale2\n
phase_deg2, scale2 = autophase_and_scale_field(FM, 6e6, pz0=0, verbose=True) phase_deg2, scale2
v=c voltage: 0.048299203724021564 V, phase: -37.86443738198939 deg\n    Pass 1 delta energy: 6000000.288677918 at phase  304.9786263011349 deg\n    Pass 2 delta energy: 5999999.999982537 at phase  305.14631150985355 deg\n
Out[18]:
(305.14631150985355, 125273551.27124627)
In\u00a0[19]: Copied!
# Use debug mode to visualize. This returns the phasing function\nps_f = autophase_and_scale_field(FM, 6e6, pz0=0, debug=True)\nps_f(phase_deg2, scale2)\n
# Use debug mode to visualize. This returns the phasing function ps_f = autophase_and_scale_field(FM, 6e6, pz0=0, debug=True) ps_f(phase_deg2, scale2) Out[19]:
5999999.999982537
In\u00a0[20]: Copied!
plist = np.linspace(280, 330, 100)\ndenergy = np.array([ps_f(p, scale2) for p in plist])\n\nplt.plot(plist, denergy/1e6)\nplt.scatter(phase_deg2, ps_f(phase_deg2, scale2)/1e6, color='red', label='Autophased')\nplt.xlabel('phase (deg)')\nplt.ylabel('Voltage (MV)')\nplt.legend()\n
plist = np.linspace(280, 330, 100) denergy = np.array([ps_f(p, scale2) for p in plist]) plt.plot(plist, denergy/1e6) plt.scatter(phase_deg2, ps_f(phase_deg2, scale2)/1e6, color='red', label='Autophased') plt.xlabel('phase (deg)') plt.ylabel('Voltage (MV)') plt.legend() Out[20]:
<matplotlib.legend.Legend at 0x1232768b0>
"},{"location":"examples/fields/field_tracking/#field-phasing-and-scaling-autophase","title":"Field Phasing and Scaling (Autophase)\u00b6","text":""},{"location":"examples/fields/field_tracking/#get-field","title":"Get field\u00b6","text":""},{"location":"examples/fields/field_tracking/#vc-voltage-and-phase","title":"v=c voltage and phase\u00b6","text":""},{"location":"examples/fields/field_tracking/#tracking","title":"Tracking\u00b6","text":""},{"location":"examples/fields/field_tracking/#autophase","title":"Autophase\u00b6","text":""},{"location":"examples/fields/field_tracking/#autophase-and-scale","title":"Autophase and Scale\u00b6","text":""}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"openPMD-beamphysics","text":"

Tools for analyzing and viewing particle data in the openPMD standard, extension beamphysics defined in: beamphysics extension

"},{"location":"#python-classes","title":"Python classes","text":"

This package provides two feature-rich classes for handling openPMD-beamphysics standard data:

  • ParticleGroup for handling particle data

  • FieldMesh - for handling external field mesh data

For usage see the examples.

"},{"location":"#installation","title":"Installation","text":"

Installing openpmd-beamphysics from the conda-forge channel can be achieved by adding conda-forge to your channels with:

conda config --add channels conda-forge\n

Once the conda-forge channel has been enabled, openpmd-beamphysics can be installed with:

conda install openpmd-beamphysics\n

It is possible to list all of the versions of openpmd-beamphysics available on your platform with:

conda search openpmd-beamphysics --channel conda-forge\n
"},{"location":"api/fields/","title":"Fields","text":"

Class for openPMD External Field Mesh data.

Initialized on on openPMD beamphysics particle group:

  • h5: open h5 handle, or str that is a file
  • data: raw data

The required data is stored in ._data, and consists of dicts:

  • 'attrs'
  • 'components'

Component data is always 3D.

Initialization from openPMD-beamphysics HDF5 file:

  • FieldMesh('file.h5')

Initialization from a data dict:

  • FieldMesh(data=data)

Derived properties:

  • .r, .theta, .z
  • .Br, .Btheta, .Bz
  • .Er, .Etheta, .Ez
  • .E, .B

  • .phase

  • .scale
  • .factor

  • .harmonic

  • .frequency

  • .shape

  • .geometry
  • .mins, .maxs, .deltas
  • .meshgrid
  • .dr, .dtheta, .dz

Booleans:

  • .is_pure_electric
  • .is_pure_magnetic
  • .is_static

Units and labels

  • .units
  • .axis_labels

Plotting:

  • .plot
  • .plot_onaxis

Writers

  • .write
  • .write_astra_1d
  • .write_astra_3d
  • .to_cylindrical
  • .to_astra_1d
  • .to_impact_solrf
  • .write_gpt
  • .write_superfish

Constructors (class methods):

  • .from_ansys_ascii_3d
  • .from_astra_3d
  • .from_superfish
  • .from_onaxis
  • .expand_onaxis
Source code in pmd_beamphysics/fields/fieldmesh.py
class FieldMesh:\n    \"\"\"\n    Class for openPMD External Field Mesh data.\n\n    Initialized on on openPMD beamphysics particle group:\n\n    - **h5**: open h5 handle, or str that is a file\n    - **data**: raw data\n\n    The required data is stored in ._data, and consists of dicts:\n\n    - `'attrs'`\n    - `'components'`\n\n    Component data is always 3D.\n\n    Initialization from openPMD-beamphysics HDF5 file:\n\n    - `FieldMesh('file.h5')`\n\n    Initialization from a data dict:\n\n    - `FieldMesh(data=data)`\n\n    Derived properties:\n\n    - `.r`, `.theta`, `.z`\n    - `.Br`, `.Btheta`, `.Bz`\n    - `.Er`, `.Etheta`, `.Ez`\n    - `.E`, `.B`\n\n    - `.phase`\n    - `.scale`\n    - `.factor`\n\n    - `.harmonic`\n    - `.frequency`\n\n    - `.shape`\n    - `.geometry`\n    - `.mins`, `.maxs`, `.deltas`\n    - `.meshgrid`\n    - `.dr`, `.dtheta`, `.dz`\n\n    Booleans:\n\n    - `.is_pure_electric`\n    - `.is_pure_magnetic`\n    - `.is_static`\n\n    Units and labels\n\n    - `.units`\n    - `.axis_labels`\n\n    Plotting:\n\n    - `.plot`\n    - `.plot_onaxis`\n\n    Writers\n\n    - `.write`\n    - `.write_astra_1d`\n    - `.write_astra_3d`\n    - `.to_cylindrical`\n    - `.to_astra_1d`\n    - `.to_impact_solrf`\n    - `.write_gpt`\n    - `.write_superfish`\n\n    Constructors (class methods):\n\n    - `.from_ansys_ascii_3d`\n    - `.from_astra_3d`\n    - `.from_superfish`\n    - `.from_onaxis`\n    - `.expand_onaxis`\n\n\n\n\n    \"\"\"\n    def __init__(self, h5=None, data=None):\n\n        if h5:\n            # Allow filename\n            if isinstance(h5, str):\n                fname = os.path.expandvars(os.path.expanduser(h5))\n                assert os.path.exists(fname), f'File does not exist: {fname}'\n\n                with File(fname, 'r') as hh5:\n                    fp = field_paths(hh5)\n                    assert len(fp) == 1, f'Number of field paths in {h5}: {len(fp)}'\n                    data = load_field_data_h5(hh5[fp[0]])\n\n            else:\n                data = load_field_data_h5(h5)\n        else:\n            data = load_field_data_dict(data)\n\n        # Internal data\n        self._data = data\n\n        # Aliases (Do not set these! Set via slicing: .Bz[:] = 0\n        #for k in self.components:\n        #    alias = component_alias[k]\n        #    self.__dict__[alias] =  self.components[k]\n\n\n    # Direct access to internal data        \n    @property\n    def attrs(self):\n        return self._data['attrs']\n\n    @property\n    def components(self):\n        return self._data['components']  \n\n    @property\n    def data(self):\n        return self._data\n\n\n    # Conveniences \n    @property\n    def shape(self):\n        return tuple(self.attrs['gridSize'])\n\n    @property\n    def geometry(self):\n        return self.attrs['gridGeometry']\n\n    @property\n    def scale(self):\n        return self.attrs['fieldScale']    \n    @scale.setter\n    def scale(self, val):\n        self.attrs['fieldScale']  = val\n\n    @property\n    def phase(self):\n        \"\"\"\n        Returns the complex argument `phi = -2*pi*RFphase`\n        to multiply the oscillating field by. \n\n        Can be set. \n        \"\"\"\n        return -self.attrs['RFphase']*2*np.pi\n    @phase.setter\n    def phase(self, val):\n        \"\"\"\n        Complex argument in radians\n        \"\"\"\n        self.attrs['RFphase']  = -val/(2*np.pi)    \n\n    @property\n    def factor(self):\n        \"\"\"\n        factor to multiply fields by, possibly complex.\n\n        `factor = scale * exp(i*phase)`\n        \"\"\"\n        return np.real_if_close(self.scale * np.exp(1j*self.phase))           \n\n    @property\n    def axis_labels(self):\n        \"\"\"\n\n        \"\"\"\n        return axis_labels_from_geometry[self.geometry]\n\n    def axis_index(self, key):\n        \"\"\"\n        Returns axis index for a named axis label key.\n\n        Examples:\n\n        - `.axis_labels == ('x', 'y', 'z')`\n        - `.axis_index('z')` returns `2`\n        \"\"\"\n        for i, name in enumerate(self.axis_labels):\n            if name == key:\n                return i\n        raise ValueError(f'Axis not found: {key}')\n\n    @property\n    def coord_vecs(self):\n        \"\"\"\n        Uses gridSpacing, gridSize, and gridOriginOffset to return coordinate vectors.\n        \"\"\"\n        return [np.linspace(x0, x1, nx) for x0, x1, nx in zip(self.mins, self.maxs, self.shape)]\n\n    def coord_vec(self, key):\n        \"\"\"\n        Gets the coordinate vector from a named axis key. \n        \"\"\"\n        i = self.axis_index(key)\n        return np.linspace(self.mins[i], self.maxs[i], self.shape[i])\n\n    @property \n    def meshgrid(self):\n        \"\"\"\n        Usses coordinate vectors to produce a standard numpy meshgrids. \n        \"\"\"\n        vecs = self.coord_vecs\n        return np.meshgrid(*vecs, indexing='ij')\n\n\n\n    @property \n    def mins(self):\n        return np.array(self.attrs['gridOriginOffset'])\n    @property\n    def deltas(self):\n        return np.array(self.attrs['gridSpacing'])\n    @property\n    def maxs(self):\n        return self.deltas*(np.array(self.attrs['gridSize'])-1) + self.mins      \n\n    @property\n    def frequency(self):\n        if self.is_static:\n            return 0\n        else:\n            return self.attrs['harmonic']*self.attrs['fundamentalFrequency']\n\n\n    # Logicals\n    @property\n    def is_pure_electric(self):\n        \"\"\"\n        Returns True if there are no non-zero mageneticField components\n        \"\"\"\n        klist = [key for key in self.components if not self.component_is_zero(key)]\n        return all([key.startswith('electric') for key in klist])\n    # Logicals\n    @property\n    def is_pure_magnetic(self):\n        \"\"\"\n        Returns True if there are no non-zero electricField components\n        \"\"\"\n        klist = [key for key in self.components if not self.component_is_zero(key)]\n        return all([key.startswith('magnetic') for key in klist])\n\n\n\n    @property\n    def is_static(self):\n        return  self.attrs['harmonic'] == 0\n\n\n\n    def component_is_zero(self, key):\n        \"\"\"\n        Returns True if all elements in a component are zero.\n        \"\"\"\n        a = self[key]\n        return not np.any(a)\n\n\n    # Plotting\n    # TODO: more general plotting\n    def plot(self, component=None, time=None, axes=None, cmap=None, return_figure=False, **kwargs):\n\n        if self.geometry != 'cylindrical':\n            raise NotImplementedError(f'Geometry {self.geometry} not implemented')\n\n        return plot_fieldmesh_cylindrical_2d(self,\n                                             component=component,\n                                             time=time,\n                                             axes=axes,\n                                             return_figure=return_figure,\n                                             cmap=cmap, **kwargs)\n\n    @functools.wraps(plot_fieldmesh_cylindrical_1d) \n    def plot_onaxis(self, *args, **kwargs):\n        assert self.geometry == 'cylindrical'\n        return plot_fieldmesh_cylindrical_1d(self, *args, **kwargs)\n\n\n    def units(self, key):\n        \"\"\"Returns the units of any key\"\"\"\n\n        # Strip any operators\n        _, key = get_operator(key)\n\n        # Fill out aliases \n        if key in component_from_alias:\n            key = component_from_alias[key]         \n\n        return pg_units(key)    \n\n    # openPMD    \n    def write(self, h5, name=None):\n        \"\"\"\n        Writes openPMD-beamphysics format to an open h5 handle, or new file if h5 is a str.\n\n        \"\"\"\n        if isinstance(h5, str):\n            fname = os.path.expandvars(os.path.expanduser(h5))\n            h5 = File(fname, 'w')\n            pmd_field_init(h5, externalFieldPath='/ExternalFieldPath/%T/')\n            g = h5.create_group('/ExternalFieldPath/1/')\n        else:\n            g = h5\n\n        write_pmd_field(g, self.data, name=name)   \n\n    @functools.wraps(write_astra_1d_fieldmap)\n    def write_astra_1d(self, filePath):      \n        return  write_astra_1d_fieldmap(self, filePath)\n\n    def to_astra_1d(self):\n        z, fz = astra_1d_fieldmap_data(self)   \n        dat = np.array([z, fz]).T \n        return {'attrs': {'type': 'astra_1d'}, 'data': dat}\n\n    def write_astra_3d(self, common_filePath, verbose=False):      \n        return  write_astra_3d_fieldmaps(self, common_filePath)\n\n\n    @functools.wraps(create_impact_solrf_ele)      \n    def to_impact_solrf(self, *args, **kwargs):\n        return create_impact_solrf_ele(self, *args, **kwargs)\n\n    def to_cylindrical(self):\n        \"\"\"\n        Returns a new FieldMesh in cylindrical geometry.\n\n        If the current geometry is rectangular, this\n        will use the y=0 slice.\n\n        \"\"\"\n        if self.geometry == 'rectangular':\n            return FieldMesh(data=fieldmesh_rectangular_to_cylindrically_symmetric_data(self))\n        elif self.geometry == 'cylindrical':\n            return self\n        else:\n            raise NotImplementedError(f\"geometry not implemented: {self.geometry}\")\n\n\n    def write_gpt(self, filePath, asci2gdf_bin=None, verbose=True):\n        \"\"\"\n        Writes a GPT field file. \n        \"\"\"\n\n        return write_gpt_fieldmesh(self, filePath, asci2gdf_bin=asci2gdf_bin, verbose=verbose)\n\n    # Superfish\n    @functools.wraps(write_superfish_t7)\n    def write_superfish(self, filePath, verbose=False):\n        \"\"\"\n        Write a Superfish T7 file. \n\n        For static fields, a Poisson T7 file is written.\n\n        For dynamic (`harmonic /= 0`) fields, a Fish T7 file is written\n        \"\"\"\n        return write_superfish_t7(self, filePath, verbose=verbose)\n\n\n\n    @classmethod\n    @functools.wraps(read_superfish_t7)\n    def from_superfish(cls, filename, type=None, geometry='cylindrical'):\n        \"\"\"\n        Class method to parse a superfish T7 style file.\n        \"\"\"        \n        data = read_superfish_t7(filename, type=type, geometry=geometry)\n        c = cls(data=data)\n        return c               \n\n\n    @classmethod\n    def from_ansys_ascii_3d(cls, *, \n                   efile = None,\n                   hfile = None,\n                   frequency = None):\n        \"\"\"\n        Class method to return a FieldMesh from ANSYS ASCII files.\n\n        The format of each file is:\n        header1 (ignored)\n        header2 (ignored)\n        x y z re_fx im_fx re_fy im_fy re_fz im_fz \n        ...\n        in C order, with oscillations as exp(i*omega*t)\n\n        Parameters\n        ----------\n        efile: str\n            Filename with complex electric field data in V/m\n\n        hfile: str\n            Filename with complex magnetic H field data in A/m\n\n        frequency: float\n            Frequency in Hz\n\n        Returns\n        -------\n        FieldMesh\n\n        \"\"\"\n\n        if frequency is None:\n            raise ValueError(f\"Please provide a frequency\")\n\n        data = read_ansys_ascii_3d_fields(efile, hfile, frequency=frequency)\n        return cls(data=data)\n\n\n\n    @classmethod\n    def from_astra_3d(cls, common_filename, frequency=0):\n        \"\"\"\n        Class method to parse multiple 3D astra fieldmap files,\n        based on the common filename.\n        \"\"\"\n\n        data = read_astra_3d_fieldmaps(common_filename, frequency=frequency)\n        return cls(data=data)\n\n    @classmethod\n    def from_onaxis(cls, *,\n                    z=None,\n                    Bz=None,\n                    Ez=None,\n                    frequency=0,\n                    harmonic=None,\n                    eleAnchorPt = 'beginning'\n                   ):\n            \"\"\"\n\n\n            Parameters \n            ----------\n            z: array\n                z-coordinates. Must be regularly spaced.       \n\n            Bz: array, optional\n                magnetic field at r=0 in T\n                Default: None        \n\n            Ez: array, optional\n                Electric field at r=0 in V/m\n                Default: None\n\n            frequency: float, optional\n                fundamental frequency in Hz.\n                Default: 0\n\n            harmonic: int, optional\n                Harmonic of the fundamental the field actually oscillates at.\n                Default: 1 if frequency !=0, otherwise 0. \n\n            eleAnchorPt: str, optional\n                Element anchor point.\n                Should be one of 'beginning', 'center', 'end'\n                Default: 'beginning'\n\n\n            Returns\n            -------\n            field: FieldMesh\n                Instantiated fieldmesh\n\n            \"\"\"\n\n            # Get spacing\n            nz = len(z)\n            dz = np.diff(z)\n            if not np.allclose(dz, dz[0]):\n                raise NotImplementedError(\"Irregular spacing not implemented\")\n            dz = dz[0]    \n\n            components = {}\n            if Ez is not None:\n                Ez = np.squeeze(np.array(Ez))\n                if Ez.ndim != 1:\n                    raise ValueError(f'Ez ndim = {Ez.ndim} must be 1')\n                components['electricField/z'] = Ez.reshape(1,1,len(Ez))\n\n            if Bz is not None:\n                Bz = np.squeeze(np.array(Bz))\n                if Bz.ndim != 1:\n                    raise ValueError(f'Bz ndim = {Bz.ndim} must be 1')\n                components['magneticField/z'] = Bz.reshape(1,1,len(Bz))            \n\n            if Bz is None and Ez is None:\n                raise ValueError('Please enter Ez or Bz')\n\n            # Handle harmonic options\n            if frequency == 0:\n                harmonic = 0\n            elif harmonic is None:\n                harmonic = 1\n\n            attrs = {'eleAnchorPt': eleAnchorPt,\n             'gridGeometry': 'cylindrical',\n             'axisLabels': np.array(['r', 'theta', 'z'], dtype='<U5'),\n             'gridLowerBound': np.array([0, 0, 0]),\n             'gridOriginOffset': np.array([ 0. ,  0. , z.min()]),\n             'gridSpacing': np.array([0. , 0.   , dz]),\n             'gridSize': np.array([1,  1, nz]),\n             'harmonic': harmonic,\n             'fundamentalFrequency': frequency,\n             'RFphase': 0,\n             'fieldScale': 1.0} \n\n            data = dict(attrs=attrs, components=components)\n            return cls(data=data)        \n\n\n\n    @functools.wraps(expand_fieldmesh_from_onaxis)\n    def expand_onaxis(self, *args, **kwargs):\n        return expand_fieldmesh_from_onaxis(self, *args, **kwargs)\n\n\n\n\n    def __eq__(self, other):\n        \"\"\"\n        Checks that all attributes and component internal data are the same\n        \"\"\"\n\n        if not tools.data_are_equal(self.attrs, other.attrs):\n            return False\n\n        return tools.data_are_equal(self.components, other.components)\n\n\n #  def __setattr__(self, key, value):\n #      print('a', key)\n #      if key in component_from_alias:\n #          print('here', key)\n #          comp = component_from_alias[key]\n #          if comp in self.components:\n #              self.components[comp] = value\n\n #  def __getattr__(self, key):\n #      print('a')\n #      if key in component_from_alias:\n #          print('here', key)\n #          comp = component_from_alias[key]\n #          if comp in self.components:\n #              return self.components[comp]\n\n\n\n\n\n    def scaled_component(self, key):\n        \"\"\"\n\n        Retruns a component scaled by the complex factor\n            factor = scale*exp(i*phase)\n\n\n        \"\"\"\n\n        if key in self.components:\n            dat = self.components[key] \n        # Aliases\n        elif key in component_from_alias:\n            comp = component_from_alias[key]\n            if comp in self.components:\n                dat = self.components[comp]   \n            else:\n                # Component not present, make zeros\n                return np.zeros(self.shape)\n        else:\n            raise ValueError(f'Component not available: {key}')\n\n        # Multiply by scale factor\n        factor = self.factor      \n\n        if factor != 1:\n            return factor*dat\n        else:\n            return dat\n\n    # Convenient properties\n    # TODO: Automate this?\n    @property\n    def r(self):\n        return self.coord_vec('r')\n    @property\n    def theta(self):\n        return self.coord_vec('theta')\n    @property\n    def z(self):\n        return self.coord_vec('z')    \n\n    # Deltas\n    ## cartesian\n    @property\n    def dx(self):\n        return self.deltas[self.axis_index('x')]\n    @property\n    def dy(self):\n        return self.deltas[self.axis_index('y')]    \n    ## cylindrical\n    @property\n    def dr(self):\n        return self.deltas[self.axis_index('r')]\n    @property\n    def dtheta(self):\n        return self.deltas[self.axis_index('theta')]\n    @property\n    def dz(self):\n        return self.deltas[self.axis_index('z')]  \n\n    # Scaled components\n    # TODO: Check geometry\n    ## cartesian\n    @property\n    def Bx(self):\n        return self.scaled_component('Bx') \n    @property\n    def By(self):\n        return self.scaled_component('By')  \n    @property\n    def Ex(self):\n        return self.scaled_component('Ex')  \n    @property\n    def Ey(self):\n        return self.scaled_component('Ey')         \n\n    ## cylindrical\n    @property\n    def Br(self):\n        return self.scaled_component('Br')\n    @property\n    def Btheta(self):\n        return self.scaled_component('Btheta')\n    @property\n    def Bz(self):\n        return self.scaled_component('Bz')\n    @property\n    def Er(self):\n        return self.scaled_component('Er')\n    @property\n    def Etheta(self):\n        return self.scaled_component('Etheta')\n    @property\n    def Ez(self):\n        return self.scaled_component('Ez')    \n\n\n\n\n    @property\n    def B(self):\n        if self.geometry=='cylindrical':\n            if self.is_static:\n                return np.hypot(self['Br'], self['Bz'])\n            else:\n                return np.abs(self['Btheta'])\n        else:\n            raise ValueError(f'Unknown geometry: {self.geometry}')    \n\n    @property\n    def E(self):\n        if self.geometry=='cylindrical':\n            return np.hypot(np.abs(self['Er']), np.abs(self['Ez']))\n        else:\n            raise ValueError(f'Unknown geometry: {self.geometry}')    \n\n    def __getitem__(self, key):\n        \"\"\"\n        Returns component data from a key\n\n        If the key starts with:\n\n        - `re_`\n        - `im_`\n        - `abs_`\n\n        the appropriate numpy operator is applied.\n\n\n\n        \"\"\"\n\n        # \n        if key in ['r', 'theta', 'z']:\n            return self.coord_vec(key)\n\n\n        # Raw components\n        if key in self.components:\n            return self.components[key]\n\n        # Check for operators\n        operator, key = get_operator(key)\n\n        # Scaled components\n        if key == 'E':\n            dat = self.E\n        elif key == 'B':\n            dat =  self.B\n        else:\n            dat = self.scaled_component(key)        \n\n        if operator:\n            dat = operator(dat)\n\n        return dat\n\n\n    def copy(self):\n        \"\"\"Returns a deep copy\"\"\"\n        return deepcopy(self)          \n\n    def __repr__(self):\n        memloc = hex(id(self))\n        return f'<FieldMesh with {self.geometry} geometry and {self.shape} shape at {memloc}>'        \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.axis_labels","title":"axis_labels property","text":""},{"location":"api/fields/#pmd_beamphysics.FieldMesh.coord_vecs","title":"coord_vecs property","text":"

Uses gridSpacing, gridSize, and gridOriginOffset to return coordinate vectors.

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.factor","title":"factor property","text":"

factor to multiply fields by, possibly complex.

factor = scale * exp(i*phase)

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.is_pure_electric","title":"is_pure_electric property","text":"

Returns True if there are no non-zero mageneticField components

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.is_pure_magnetic","title":"is_pure_magnetic property","text":"

Returns True if there are no non-zero electricField components

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.meshgrid","title":"meshgrid property","text":"

Usses coordinate vectors to produce a standard numpy meshgrids.

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.phase","title":"phase property writable","text":"

Returns the complex argument phi = -2*pi*RFphase to multiply the oscillating field by.

Can be set.

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.__eq__","title":"__eq__(other)","text":"

Checks that all attributes and component internal data are the same

Source code in pmd_beamphysics/fields/fieldmesh.py
def __eq__(self, other):\n    \"\"\"\n    Checks that all attributes and component internal data are the same\n    \"\"\"\n\n    if not tools.data_are_equal(self.attrs, other.attrs):\n        return False\n\n    return tools.data_are_equal(self.components, other.components)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.__getitem__","title":"__getitem__(key)","text":"

Returns component data from a key

If the key starts with:

  • re_
  • im_
  • abs_

the appropriate numpy operator is applied.

Source code in pmd_beamphysics/fields/fieldmesh.py
def __getitem__(self, key):\n    \"\"\"\n    Returns component data from a key\n\n    If the key starts with:\n\n    - `re_`\n    - `im_`\n    - `abs_`\n\n    the appropriate numpy operator is applied.\n\n\n\n    \"\"\"\n\n    # \n    if key in ['r', 'theta', 'z']:\n        return self.coord_vec(key)\n\n\n    # Raw components\n    if key in self.components:\n        return self.components[key]\n\n    # Check for operators\n    operator, key = get_operator(key)\n\n    # Scaled components\n    if key == 'E':\n        dat = self.E\n    elif key == 'B':\n        dat =  self.B\n    else:\n        dat = self.scaled_component(key)        \n\n    if operator:\n        dat = operator(dat)\n\n    return dat\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.axis_index","title":"axis_index(key)","text":"

Returns axis index for a named axis label key.

Examples:

  • .axis_labels == ('x', 'y', 'z')
  • .axis_index('z') returns 2
Source code in pmd_beamphysics/fields/fieldmesh.py
def axis_index(self, key):\n    \"\"\"\n    Returns axis index for a named axis label key.\n\n    Examples:\n\n    - `.axis_labels == ('x', 'y', 'z')`\n    - `.axis_index('z')` returns `2`\n    \"\"\"\n    for i, name in enumerate(self.axis_labels):\n        if name == key:\n            return i\n    raise ValueError(f'Axis not found: {key}')\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.component_is_zero","title":"component_is_zero(key)","text":"

Returns True if all elements in a component are zero.

Source code in pmd_beamphysics/fields/fieldmesh.py
def component_is_zero(self, key):\n    \"\"\"\n    Returns True if all elements in a component are zero.\n    \"\"\"\n    a = self[key]\n    return not np.any(a)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.coord_vec","title":"coord_vec(key)","text":"

Gets the coordinate vector from a named axis key.

Source code in pmd_beamphysics/fields/fieldmesh.py
def coord_vec(self, key):\n    \"\"\"\n    Gets the coordinate vector from a named axis key. \n    \"\"\"\n    i = self.axis_index(key)\n    return np.linspace(self.mins[i], self.maxs[i], self.shape[i])\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.copy","title":"copy()","text":"

Returns a deep copy

Source code in pmd_beamphysics/fields/fieldmesh.py
def copy(self):\n    \"\"\"Returns a deep copy\"\"\"\n    return deepcopy(self)          \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_ansys_ascii_3d","title":"from_ansys_ascii_3d(*, efile=None, hfile=None, frequency=None) classmethod","text":"

Class method to return a FieldMesh from ANSYS ASCII files.

The format of each file is: header1 (ignored) header2 (ignored) x y z re_fx im_fx re_fy im_fy re_fz im_fz ... in C order, with oscillations as exp(iomegat)

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_ansys_ascii_3d--parameters","title":"Parameters","text":"

efile: str Filename with complex electric field data in V/m

str

Filename with complex magnetic H field data in A/m

float

Frequency in Hz

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_ansys_ascii_3d--returns","title":"Returns","text":"

FieldMesh

Source code in pmd_beamphysics/fields/fieldmesh.py
@classmethod\ndef from_ansys_ascii_3d(cls, *, \n               efile = None,\n               hfile = None,\n               frequency = None):\n    \"\"\"\n    Class method to return a FieldMesh from ANSYS ASCII files.\n\n    The format of each file is:\n    header1 (ignored)\n    header2 (ignored)\n    x y z re_fx im_fx re_fy im_fy re_fz im_fz \n    ...\n    in C order, with oscillations as exp(i*omega*t)\n\n    Parameters\n    ----------\n    efile: str\n        Filename with complex electric field data in V/m\n\n    hfile: str\n        Filename with complex magnetic H field data in A/m\n\n    frequency: float\n        Frequency in Hz\n\n    Returns\n    -------\n    FieldMesh\n\n    \"\"\"\n\n    if frequency is None:\n        raise ValueError(f\"Please provide a frequency\")\n\n    data = read_ansys_ascii_3d_fields(efile, hfile, frequency=frequency)\n    return cls(data=data)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_astra_3d","title":"from_astra_3d(common_filename, frequency=0) classmethod","text":"

Class method to parse multiple 3D astra fieldmap files, based on the common filename.

Source code in pmd_beamphysics/fields/fieldmesh.py
@classmethod\ndef from_astra_3d(cls, common_filename, frequency=0):\n    \"\"\"\n    Class method to parse multiple 3D astra fieldmap files,\n    based on the common filename.\n    \"\"\"\n\n    data = read_astra_3d_fieldmaps(common_filename, frequency=frequency)\n    return cls(data=data)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_onaxis","title":"from_onaxis(*, z=None, Bz=None, Ez=None, frequency=0, harmonic=None, eleAnchorPt='beginning') classmethod","text":""},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_onaxis--parameters","title":"Parameters","text":"

z: array z-coordinates. Must be regularly spaced.

array, optional

magnetic field at r=0 in T Default: None

array, optional

Electric field at r=0 in V/m Default: None

float, optional

fundamental frequency in Hz. Default: 0

int, optional

Harmonic of the fundamental the field actually oscillates at. Default: 1 if frequency !=0, otherwise 0.

str, optional

Element anchor point. Should be one of 'beginning', 'center', 'end' Default: 'beginning'

"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_onaxis--returns","title":"Returns","text":"

field: FieldMesh Instantiated fieldmesh

Source code in pmd_beamphysics/fields/fieldmesh.py
@classmethod\ndef from_onaxis(cls, *,\n                z=None,\n                Bz=None,\n                Ez=None,\n                frequency=0,\n                harmonic=None,\n                eleAnchorPt = 'beginning'\n               ):\n        \"\"\"\n\n\n        Parameters \n        ----------\n        z: array\n            z-coordinates. Must be regularly spaced.       \n\n        Bz: array, optional\n            magnetic field at r=0 in T\n            Default: None        \n\n        Ez: array, optional\n            Electric field at r=0 in V/m\n            Default: None\n\n        frequency: float, optional\n            fundamental frequency in Hz.\n            Default: 0\n\n        harmonic: int, optional\n            Harmonic of the fundamental the field actually oscillates at.\n            Default: 1 if frequency !=0, otherwise 0. \n\n        eleAnchorPt: str, optional\n            Element anchor point.\n            Should be one of 'beginning', 'center', 'end'\n            Default: 'beginning'\n\n\n        Returns\n        -------\n        field: FieldMesh\n            Instantiated fieldmesh\n\n        \"\"\"\n\n        # Get spacing\n        nz = len(z)\n        dz = np.diff(z)\n        if not np.allclose(dz, dz[0]):\n            raise NotImplementedError(\"Irregular spacing not implemented\")\n        dz = dz[0]    \n\n        components = {}\n        if Ez is not None:\n            Ez = np.squeeze(np.array(Ez))\n            if Ez.ndim != 1:\n                raise ValueError(f'Ez ndim = {Ez.ndim} must be 1')\n            components['electricField/z'] = Ez.reshape(1,1,len(Ez))\n\n        if Bz is not None:\n            Bz = np.squeeze(np.array(Bz))\n            if Bz.ndim != 1:\n                raise ValueError(f'Bz ndim = {Bz.ndim} must be 1')\n            components['magneticField/z'] = Bz.reshape(1,1,len(Bz))            \n\n        if Bz is None and Ez is None:\n            raise ValueError('Please enter Ez or Bz')\n\n        # Handle harmonic options\n        if frequency == 0:\n            harmonic = 0\n        elif harmonic is None:\n            harmonic = 1\n\n        attrs = {'eleAnchorPt': eleAnchorPt,\n         'gridGeometry': 'cylindrical',\n         'axisLabels': np.array(['r', 'theta', 'z'], dtype='<U5'),\n         'gridLowerBound': np.array([0, 0, 0]),\n         'gridOriginOffset': np.array([ 0. ,  0. , z.min()]),\n         'gridSpacing': np.array([0. , 0.   , dz]),\n         'gridSize': np.array([1,  1, nz]),\n         'harmonic': harmonic,\n         'fundamentalFrequency': frequency,\n         'RFphase': 0,\n         'fieldScale': 1.0} \n\n        data = dict(attrs=attrs, components=components)\n        return cls(data=data)        \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.from_superfish","title":"from_superfish(filename, type=None, geometry='cylindrical') classmethod","text":"

Class method to parse a superfish T7 style file.

Source code in pmd_beamphysics/fields/fieldmesh.py
@classmethod\n@functools.wraps(read_superfish_t7)\ndef from_superfish(cls, filename, type=None, geometry='cylindrical'):\n    \"\"\"\n    Class method to parse a superfish T7 style file.\n    \"\"\"        \n    data = read_superfish_t7(filename, type=type, geometry=geometry)\n    c = cls(data=data)\n    return c               \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.scaled_component","title":"scaled_component(key)","text":"

Retruns a component scaled by the complex factor factor = scaleexp(iphase)

Source code in pmd_beamphysics/fields/fieldmesh.py
def scaled_component(self, key):\n    \"\"\"\n\n    Retruns a component scaled by the complex factor\n        factor = scale*exp(i*phase)\n\n\n    \"\"\"\n\n    if key in self.components:\n        dat = self.components[key] \n    # Aliases\n    elif key in component_from_alias:\n        comp = component_from_alias[key]\n        if comp in self.components:\n            dat = self.components[comp]   \n        else:\n            # Component not present, make zeros\n            return np.zeros(self.shape)\n    else:\n        raise ValueError(f'Component not available: {key}')\n\n    # Multiply by scale factor\n    factor = self.factor      \n\n    if factor != 1:\n        return factor*dat\n    else:\n        return dat\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.to_cylindrical","title":"to_cylindrical()","text":"

Returns a new FieldMesh in cylindrical geometry.

If the current geometry is rectangular, this will use the y=0 slice.

Source code in pmd_beamphysics/fields/fieldmesh.py
def to_cylindrical(self):\n    \"\"\"\n    Returns a new FieldMesh in cylindrical geometry.\n\n    If the current geometry is rectangular, this\n    will use the y=0 slice.\n\n    \"\"\"\n    if self.geometry == 'rectangular':\n        return FieldMesh(data=fieldmesh_rectangular_to_cylindrically_symmetric_data(self))\n    elif self.geometry == 'cylindrical':\n        return self\n    else:\n        raise NotImplementedError(f\"geometry not implemented: {self.geometry}\")\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.units","title":"units(key)","text":"

Returns the units of any key

Source code in pmd_beamphysics/fields/fieldmesh.py
def units(self, key):\n    \"\"\"Returns the units of any key\"\"\"\n\n    # Strip any operators\n    _, key = get_operator(key)\n\n    # Fill out aliases \n    if key in component_from_alias:\n        key = component_from_alias[key]         \n\n    return pg_units(key)    \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.write","title":"write(h5, name=None)","text":"

Writes openPMD-beamphysics format to an open h5 handle, or new file if h5 is a str.

Source code in pmd_beamphysics/fields/fieldmesh.py
def write(self, h5, name=None):\n    \"\"\"\n    Writes openPMD-beamphysics format to an open h5 handle, or new file if h5 is a str.\n\n    \"\"\"\n    if isinstance(h5, str):\n        fname = os.path.expandvars(os.path.expanduser(h5))\n        h5 = File(fname, 'w')\n        pmd_field_init(h5, externalFieldPath='/ExternalFieldPath/%T/')\n        g = h5.create_group('/ExternalFieldPath/1/')\n    else:\n        g = h5\n\n    write_pmd_field(g, self.data, name=name)   \n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.write_gpt","title":"write_gpt(filePath, asci2gdf_bin=None, verbose=True)","text":"

Writes a GPT field file.

Source code in pmd_beamphysics/fields/fieldmesh.py
def write_gpt(self, filePath, asci2gdf_bin=None, verbose=True):\n    \"\"\"\n    Writes a GPT field file. \n    \"\"\"\n\n    return write_gpt_fieldmesh(self, filePath, asci2gdf_bin=asci2gdf_bin, verbose=verbose)\n
"},{"location":"api/fields/#pmd_beamphysics.FieldMesh.write_superfish","title":"write_superfish(filePath, verbose=False)","text":"

Write a Superfish T7 file.

For static fields, a Poisson T7 file is written.

For dynamic (harmonic /= 0) fields, a Fish T7 file is written

Source code in pmd_beamphysics/fields/fieldmesh.py
@functools.wraps(write_superfish_t7)\ndef write_superfish(self, filePath, verbose=False):\n    \"\"\"\n    Write a Superfish T7 file. \n\n    For static fields, a Poisson T7 file is written.\n\n    For dynamic (`harmonic /= 0`) fields, a Fish T7 file is written\n    \"\"\"\n    return write_superfish_t7(self, filePath, verbose=verbose)\n
"},{"location":"api/particles/","title":"Particles","text":"

Particle Group class

Initialized on on openPMD beamphysics particle group:

  • h5: open h5 handle, or str that is a file
  • data: raw data

The required bunch data is stored in .data with keys

  • np.array: x, px, y, py, z, pz, t, status, weight
  • str: species

where:

  • x, y, z are positions in units of [m]
  • px, py, pz are momenta in units of [eV/c]
  • t is time in [s]
  • weight is the macro-charge weight in [C], used for all statistical calulations.
  • species is a proper species name: 'electron', etc.

Optional data:

  • np.array: id

where id is a list of unique integers that identify the particles.

Derived data can be computed as attributes:

  • .gamma, .beta, .beta_x, .beta_y, .beta_z: relativistic factors [1].
  • .r, .theta: cylidrical coordinates [m], [1]
  • .pr, .ptheta: momenta in the radial and angular coordinate directions [eV/c]
  • .Lz: angular momentum about the z axis [m*eV/c]
  • .energy : total energy [eV]
  • .kinetic_energy: total energy - mc^2 in [eV].
  • .higher_order_energy: total energy with quadratic fit in z or t subtracted [eV]
  • .p: total momentum in [eV/c]
  • .mass: rest mass in [eV]
  • .xp, .yp: Slopes \\(x' = dx/dz = dp_x/dp_z\\) and \\(y' = dy/dz = dp_y/dp_z\\) [1].

Normalized transvere coordinates can also be calculated as attributes:

  • .x_bar, .px_bar, .y_bar, .py_bar in [sqrt(m)]

The normalization is automatically calculated from the covariance matrix. See functions in .statistics for more advanced usage.

Their cooresponding amplitudes are:

.Jx, .Jy [m]

where Jx = (x_bar^2 + px_bar^2 )/2.

The momenta are normalized by the mass, so that <Jx> = norm_emit_x and similar for y.

Statistics of any of these are calculated with:

  • .min(X)
  • .max(X)
  • .ptp(X)
  • .avg(X)
  • .std(X)
  • .cov(X, Y, ...)
  • .histogramdd(X, Y, ..., bins=10, range=None)

with a string X as the name any of the properties above.

Useful beam physics quantities are given as attributes:

  • .norm_emit_x
  • .norm_emit_y
  • .norm_emit_4d
  • .higher_order_energy_spread
  • .average_current

Twiss parameters, including dispersion, for the \\(x\\) or \\(y\\) plane:

  • .twiss(plane='x', fraction=0.95, p0C=None)

For convenience, plane='xy' will calculate twiss for both planes.

Twiss matched particles, using a simple linear transformation:

  • .twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False)

The weight is required and must sum to > 0. The sum of the weights is in .charge. This can also be set: .charge = 1.234 # C, will rescale the .weight array

All attributes can be accessed with brackets

[key]

Additional keys are allowed for convenience

['min_prop'] will return .min('prop') ['max_prop'] will return .max('prop') ['ptp_prop'] will return .ptp('prop') ['mean_prop'] will return .avg('prop') ['sigma_prop'] will return .std('prop') ['cov_prop1__prop2'] will return .cov('prop1', 'prop2')[0,1]

Units for all attributes can be accessed by:

  • .units(key)

Particles are often stored at the same time (i.e. from a t-based code), or with the same z position (i.e. from an s-based code.) Routines:

  • drift_to_z(z0)
  • drift_to_t(t0)

help to convert these. If no argument is given, particles will be drifted to the mean. Related properties are:

  • .in_t_coordinates returns True if all particles have the same \\(t\\) corrdinate
  • .in_z_coordinates returns True if all particles have the same \\(z\\) corrdinate

Convenient plotting is provided with:

  • .plot(...)
  • .slice_plot(...)

Use help(ParticleGroup.plot), etc. for usage.

Source code in pmd_beamphysics/particles.py
class ParticleGroup:\n    \"\"\"\n    Particle Group class\n\n    Initialized on on openPMD beamphysics particle group:\n\n    - **h5**: open h5 handle, or `str` that is a file\n    - **data**: raw data\n\n    The required bunch data is stored in `.data` with keys\n\n    - `np.array`: `x`, `px`, `y`, `py`, `z`, `pz`, `t`, `status`, `weight`\n    - `str`: `species`\n\n    where:\n\n    - `x`, `y`, `z` are positions in units of [m]\n    - `px`, `py`, `pz` are momenta in units of [eV/c]\n    - `t` is time in [s]\n    - `weight` is the macro-charge weight in [C], used for all statistical calulations.\n    - `species` is a proper species name: `'electron'`, etc. \n\n    Optional data:\n\n    - `np.array`: `id`\n\n    where `id` is a list of unique integers that identify the particles. \n\n\n    Derived data can be computed as attributes:\n\n    - `.gamma`, `.beta`, `.beta_x`, `.beta_y`, `.beta_z`: relativistic factors [1].\n    - `.r`, `.theta`: cylidrical coordinates [m], [1]\n    - `.pr`, `.ptheta`: momenta in the radial and angular coordinate directions [eV/c]\n    - `.Lz`: angular momentum about the z axis [m*eV/c]\n    - `.energy` : total energy [eV]\n    - `.kinetic_energy`: total energy - mc^2 in [eV]. \n    - `.higher_order_energy`: total energy with quadratic fit in z or t subtracted [eV]\n    - `.p`: total momentum in [eV/c]\n    - `.mass`: rest mass in [eV]\n    - `.xp`, `.yp`: Slopes $x' = dx/dz = dp_x/dp_z$ and $y' = dy/dz = dp_y/dp_z$ [1].\n\n    Normalized transvere coordinates can also be calculated as attributes:\n\n    - `.x_bar`, `.px_bar`, `.y_bar`, `.py_bar` in [sqrt(m)]\n\n    The normalization is automatically calculated from the covariance matrix. \n    See functions in `.statistics` for more advanced usage.\n\n    Their cooresponding amplitudes are:\n\n    `.Jx`, `.Jy` [m]\n\n    where `Jx = (x_bar^2 + px_bar^2 )/2`.\n\n    The momenta are normalized by the mass, so that\n    `<Jx> = norm_emit_x`\n    and similar for `y`. \n\n    Statistics of any of these are calculated with:\n\n    - `.min(X)`\n    - `.max(X)`\n    - `.ptp(X)`\n    - `.avg(X)`\n    - `.std(X)`\n    - `.cov(X, Y, ...)`\n    - `.histogramdd(X, Y, ..., bins=10, range=None)`\n\n    with a string `X` as the name any of the properties above.\n\n    Useful beam physics quantities are given as attributes:\n\n    - `.norm_emit_x`\n    - `.norm_emit_y`\n    - `.norm_emit_4d`\n    - `.higher_order_energy_spread`\n    - `.average_current`\n\n    Twiss parameters, including dispersion, for the $x$ or $y$ plane:\n\n    - `.twiss(plane='x', fraction=0.95, p0C=None)`\n\n    For convenience, `plane='xy'` will calculate twiss for both planes.\n\n    Twiss matched particles, using a simple linear transformation:\n\n    - `.twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False)`\n\n    The weight is required and must sum to > 0. The sum of the weights is in `.charge`.\n    This can also be set: `.charge = 1.234` # C, will rescale the .weight array\n\n    All attributes can be accessed with brackets:\n        `[key]`\n\n    Additional keys are allowed for convenience:\n        `['min_prop']`   will return  `.min('prop')`\n        `['max_prop']`   will return  `.max('prop')`\n        `['ptp_prop']`   will return  `.ptp('prop')`\n        `['mean_prop']`  will return  `.avg('prop')`\n        `['sigma_prop']` will return  `.std('prop')`\n        `['cov_prop1__prop2']` will return `.cov('prop1', 'prop2')[0,1]`\n\n    Units for all attributes can be accessed by:\n\n    - `.units(key)`\n\n    Particles are often stored at the same time (i.e. from a t-based code), \n    or with the same z position (i.e. from an s-based code.)\n    Routines: \n\n    - `drift_to_z(z0)`\n    - `drift_to_t(t0)`\n\n    help to convert these. If no argument is given, particles will be drifted to the mean.\n    Related properties are:\n\n    - `.in_t_coordinates` returns `True` if all particles have the same $t$ corrdinate\n    - `.in_z_coordinates` returns `True` if all particles have the same $z$ corrdinate\n\n    Convenient plotting is provided with: \n\n    - `.plot(...)`\n    - `.slice_plot(...)`\n\n    Use `help(ParticleGroup.plot)`, etc. for usage. \n\n\n    \"\"\"\n    def __init__(self, h5=None, data=None):\n\n\n        if h5 and data:\n            # TODO:\n            # Allow merging or changing some array with extra data\n            raise NotImplementedError('Cannot init on both h5 and data')\n\n        if h5:\n            # Allow filename\n            if isinstance(h5, str):\n                fname = os.path.expandvars(h5)\n                assert os.path.exists(fname), f'File does not exist: {fname}'\n\n                with File(fname, 'r') as hh5:\n                    pp = particle_paths(hh5)\n                    assert len(pp) == 1, f'Number of particle paths in {h5}: {len(pp)}'\n                    data = load_bunch_data(hh5[pp[0]])\n\n            else:\n                # Try dict\n                data = load_bunch_data(h5)\n        else:\n            # Fill out data. Exclude species.\n            data = full_data(data)\n            species = list(set(data['species']))\n\n            # Allow for empty data (len=0). Otherwise, check species.\n            if len(species) >= 1:\n                assert len(species) == 1, f'mixed species are not allowed: {species}'\n                data['species'] = species[0]\n\n        self._settable_array_keys = ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight']\n        # Optional data\n        for k in ['id']:\n            if k in data:\n                self._settable_array_keys.append(k)  \n\n        self._settable_scalar_keys = ['species']\n        self._settable_keys =  self._settable_array_keys + self._settable_scalar_keys                       \n        # Internal data. Only allow settable keys\n        self._data = {k:data[k] for k in self._settable_keys}\n\n    #-------------------------------------------------\n    # Access to intrinsic coordinates\n    @property\n    def x(self):\n        \"\"\"\n        x coordinate in [m]\n        \"\"\"\n        return self._data['x']\n    @x.setter\n    def x(self, val):\n        self._data['x'] = full_array(len(self), val)    \n\n    @property\n    def y(self):\n        \"\"\"\n        y coordinate in [m]\n        \"\"\"\n        return self._data['y']\n    @y.setter\n    def y(self, val):\n        self._data['y'] = full_array(len(self), val)              \n\n    @property\n    def z(self):\n        \"\"\"\n        z coordinate in [m]\n        \"\"\"\n        return self._data['z']\n    @z.setter\n    def z(self, val):\n        self._data['z'] = full_array(len(self), val)      \n\n    @property\n    def px(self):\n        \"\"\"\n        px coordinate in [eV/c]\n        \"\"\"\n        return self._data['px']\n    @px.setter\n    def px(self, val):\n        self._data['px'] = full_array(len(self), val)      \n\n    @property\n    def py(self):\n        \"\"\"\n        py coordinate in [eV/c]\n        \"\"\"\n        return self._data['py']\n    @py.setter\n    def py(self, val):\n        self._data['py'] = full_array(len(self), val)    \n\n    @property\n    def pz(self):\n        \"\"\"\n        pz coordinate in [eV/c]\n        \"\"\"\n        return self._data['pz']\n    @pz.setter\n    def pz(self, val):\n        self._data['pz'] = full_array(len(self), val)    \n\n    @property\n    def t(self):\n        \"\"\"\n        t coordinate in [s]\n        \"\"\"\n        return self._data['t']\n    @t.setter\n    def t(self, val):\n        self._data['t'] = full_array(len(self), val)       \n\n    @property\n    def status(self):\n        \"\"\"\n        status coordinate in [1]\n        \"\"\"\n        return self._data['status']\n    @status.setter\n    def status(self, val):\n        self._data['status'] = full_array(len(self), val)  \n\n    @property\n    def weight(self):\n        \"\"\"\n        weight coordinate in [C]\n        \"\"\"\n        return self._data['weight']\n    @weight.setter\n    def weight(self, val):\n        self._data['weight'] = full_array(len(self), val)            \n\n    @property\n    def id(self):\n        \"\"\"\n        id integer \n        \"\"\"\n        if 'id' not in self._data:\n            self.assign_id()      \n\n        return self._data['id']\n    @id.setter\n    def id(self, val):\n        self._data['id'] = full_array(len(self), val)            \n\n\n    @property\n    def species(self):\n        \"\"\"\n        species string\n        \"\"\"\n        return self._data['species']\n    @species.setter\n    def species(self, val):\n        self._data['species'] = val\n\n    @property\n    def data(self):\n        \"\"\"\n        Internal data dict\n        \"\"\"\n        return self._data        \n\n    #-------------------------------------------------\n    # Derived data\n\n    def assign_id(self):\n        \"\"\"\n        Assigns unique ids, integers from 1 to n_particle\n\n        \"\"\"\n        if 'id' not in self._settable_array_keys: \n            self._settable_array_keys.append('id')\n        self.id = np.arange(1, self['n_particle']+1)             \n\n    @property\n    def n_particle(self):\n        \"\"\"Total number of particles. Same as len \"\"\"\n        return len(self)\n\n    @property\n    def n_alive(self):\n        \"\"\"Number of alive particles, defined by status == 1\"\"\"\n        return len(np.where(self.status==1)[0])\n\n    @property\n    def n_dead(self):\n        \"\"\"Number of alive particles, defined by status != 1\"\"\"\n        return self.n_particle - self.n_alive\n\n\n    def units(self, key):\n        \"\"\"Returns the units of any key\"\"\"\n        return pg_units(key)\n\n    @property\n    def mass(self):\n        \"\"\"Rest mass in eV\"\"\"\n        return mass_of(self.species)\n\n    @property\n    def species_charge(self):\n        \"\"\"Species charge in C\"\"\"\n        return charge_of(self.species)\n\n    @property\n    def charge(self):\n        \"\"\"Total charge in C\"\"\"\n        return np.sum(self.weight)\n    @charge.setter\n    def charge(self, val):\n        \"\"\"Rescale weight array so that it sum to this value\"\"\"\n        assert val >0, 'charge must be >0. This is used to weight the particles.'\n        self.weight *= val/self.charge\n\n\n    # Relativistic properties\n    @property\n    def p(self):\n        \"\"\"Total momemtum in eV/c\"\"\"\n        return np.sqrt(self.px**2 + self.py**2 + self.pz**2) \n    @property\n    def energy(self):\n        \"\"\"Total energy in eV\"\"\"\n        return np.sqrt(self.px**2 + self.py**2 + self.pz**2 + self.mass**2) \n    @property\n    def kinetic_energy(self):\n        \"\"\"Kinetic energy in eV\"\"\"\n        return self.energy - self.mass\n\n    # Slopes. Note that these are relative to pz\n    @property\n    def xp(self):\n        \"\"\"x slope px/pz (dimensionless)\"\"\"\n        return self.px/self.pz  \n    @property\n    def yp(self):\n        \"\"\"y slope py/pz (dimensionless)\"\"\"\n        return self.py/self.pz    \n\n    @property\n    def higher_order_energy(self):\n        \"\"\"\n        Fits a quadratic (order=2) to the Energy vs. time, and returns the energy with this subtracted. \n        \"\"\" \n        return self.higher_order_energy_calc(order=2)\n\n    @property\n    def higher_order_energy_spread(self):\n        \"\"\"\n        Legacy syntax to compute the standard deviation of higher_order_energy.\n        \"\"\"\n        return self.std('higher_order_energy')\n\n    def higher_order_energy_calc(self, order=2):\n        \"\"\"\n        Fits a polynmial with order `order` to the Energy vs. time, , and returns the energy with this subtracted. \n        \"\"\"\n        #order=2\n        if self.std('z') < 1e-12:\n            # must be at a screen. Use t\n            t = self.t\n        else:\n            # All particles at the same time. Use z to calc t\n            t = self.z/c_light\n        energy = self.energy\n\n        best_fit_coeffs = np.polynomial.polynomial.polyfit(t, energy, order)\n        best_fit = np.polynomial.polynomial.polyval(t, best_fit_coeffs)\n        return energy - best_fit\n\n    # Polar coordinates. Note that these are centered at x=0, y=0, and not an averge center. \n    @property\n    def r(self):\n        \"\"\"Radius in the xy plane: r = sqrt(x^2 + y^2) in m\"\"\"\n        return np.hypot(self.x, self.y)\n    @property    \n    def theta(self):\n        \"\"\"Angle in xy plane: theta = arctan2(y, x) in radians\"\"\"\n        return np.arctan2(self.y, self.x)\n    @property\n    def pr(self):\n        \"\"\"\n        Momentum in the radial direction in eV/c\n        r_hat = cos(theta) xhat + sin(theta) yhat\n        pr = p dot r_hat\n        \"\"\"\n        theta = self.theta\n        return self.px * np.cos(theta)  + self.py * np.sin(theta)   \n    @property    \n    def ptheta(self):\n        \"\"\"     \n        Momentum in the polar theta direction. \n        theta_hat = -sin(theta) xhat + cos(theta) yhat\n        ptheta = p dot theta_hat\n        Note that Lz = r*ptheta\n        \"\"\"\n        theta = self.theta\n        return -self.px * np.sin(theta)  + self.py * np.cos(theta)  \n    @property    \n    def Lz(self):\n        \"\"\"\n        Angular momentum around the z axis in m*eV/c\n        Lz = x * py - y * px\n        \"\"\"\n        return self.x*self.py - self.y*self.px\n\n\n    # Relativistic quantities\n    @property\n    def gamma(self):\n        \"\"\"Relativistic gamma\"\"\"\n        return self.energy/self.mass\n\n    @gamma.setter\n    def gamma(self, val):\n        beta_x = self.beta_x\n        beta_y = self.beta_y\n        beta_z = self.beta_z\n        beta = self.beta\n        gamma_new = full_array(len(self), val)\n        energy_new = gamma_new * self.mass\n        beta_new = np.sqrt(gamma_new**2 - 1)/gamma_new\n        self._data['px'] = energy_new * beta_new * beta_x / beta\n        self._data['py'] = energy_new * beta_new * beta_y / beta\n        self._data['pz'] = energy_new * beta_new * beta_z / beta\n\n    @property\n    def beta(self):\n        \"\"\"Relativistic beta\"\"\"\n        return self.p/self.energy\n    @property\n    def beta_x(self):\n        \"\"\"Relativistic beta, x component\"\"\"\n        return self.px/self.energy\n\n    @beta_x.setter\n    def beta_x(self, val):\n        self._data['px'] = full_array(len(self), val)*self.energy    \n\n    @property\n    def beta_y(self):\n        \"\"\"Relativistic beta, y component\"\"\"\n        return self.py/self.energy\n\n    @beta_y.setter\n    def beta_y(self, val):\n        self._data['py'] = full_array(len(self), val)*self.energy    \n\n    @property\n    def beta_z(self):\n        \"\"\"Relativistic beta, z component\"\"\"\n        return self.pz/self.energy\n\n    @beta_z.setter\n    def beta_z(self, val):\n        self._data['pz'] = full_array(len(self), val)*self.energy\n\n    # Normalized coordinates for x and y\n    @property \n    def x_bar(self):\n        \"\"\"Normalized x in units of sqrt(m)\"\"\"\n        return normalized_particle_coordinate(self, 'x')\n    @property     \n    def px_bar(self):\n        \"\"\"Normalized px in units of sqrt(m)\"\"\"\n        return normalized_particle_coordinate(self, 'px')    \n    @property\n    def Jx(self):\n        \"\"\"Normalized amplitude J in the x-px plane\"\"\"\n        return particle_amplitude(self, 'x')\n\n    @property \n    def y_bar(self):\n        \"\"\"Normalized y in units of sqrt(m)\"\"\"\n        return normalized_particle_coordinate(self, 'y')\n    @property     \n    def py_bar(self):\n        \"\"\"Normalized py in units of sqrt(m)\"\"\"\n        return normalized_particle_coordinate(self, 'py')\n    @property\n    def Jy(self):\n        \"\"\"Normalized amplitude J in the y-py plane\"\"\"\n        return particle_amplitude(self, 'y')    \n\n    def delta(self, key):\n        \"\"\"Attribute (array) relative to its mean\"\"\"\n        return self[key] - self.avg(key)\n\n\n    # Statistical property functions\n\n    def min(self, key):\n        \"\"\"Minimum of any key\"\"\"\n        return np.min(self[key]) # was: getattr(self, key)\n    def max(self, key):\n        \"\"\"Maximum of any key\"\"\"\n        return np.max(self[key]) \n    def ptp(self, key):\n        \"\"\"Peak-to-Peak = max - min of any key\"\"\"\n        return np.ptp(self[key])     \n\n    def avg(self, key):\n        \"\"\"Statistical average\"\"\"\n        dat = self[key]  # equivalent to self.key for accessing properties above\n        if np.isscalar(dat): \n            return dat\n        return np.average(dat, weights=self.weight)\n    def std(self, key):\n        \"\"\"Standard deviation (actually sample)\"\"\"\n        dat = self[key]\n        if np.isscalar(dat):\n            return 0\n        avg_dat = self.avg(key)\n        return np.sqrt(np.average( (dat - avg_dat)**2, weights=self.weight))\n    def cov(self, *keys):\n        \"\"\"\n        Covariance matrix from any properties\n\n        Example: \n        P = ParticleGroup(h5)\n        P.cov('x', 'px', 'y', 'py')\n\n        \"\"\"\n        dats = np.array([ self[key] for key in keys ])\n        return np.cov(dats, aweights=self.weight)\n\n    def histogramdd(self, *keys, bins=10, range=None):\n        \"\"\"\n        Wrapper for numpy.histogramdd, but accepts property names as keys.\n\n        Computes the multidimensional histogram of keys. Internally uses weights. \n\n        Example:\n            P.histogramdd('x', 'y', bins=50)\n        Returns:\n            np.array with shape 50x50, edge list \n\n        \"\"\"\n        H, edges = np.histogramdd(np.array([self[k] for k in list(keys)]).T, weights=self.weight, bins=bins, range=range)\n\n        return H, edges\n\n\n    # Beam statistics\n    @property\n    def norm_emit_x(self):\n        \"\"\"Normalized emittance in the x plane\"\"\"\n        return norm_emit_calc(self, planes=['x'])\n    @property\n    def norm_emit_y(self):       \n        \"\"\"Normalized emittance in the x plane\"\"\"\n        return norm_emit_calc(self, planes=['y'])\n    @property\n    def norm_emit_4d(self):       \n        \"\"\"Normalized emittance in the xy planes (4D)\"\"\"\n        return norm_emit_calc(self, planes=['x', 'y'])    \n\n    def twiss(self, plane='x', fraction=1, p0c=None):\n        \"\"\"\n        Returns Twiss and Dispersion dict.\n\n        plane can be:\n\n        `'x'`, `'y'`, or `'xy'`\n\n        Optionally a fraction of the particles, based on amplitiude, can be specified.\n        \"\"\"\n        d = {}\n        for p in plane:\n            d.update(particle_twiss_dispersion(self, plane=p, fraction=fraction, p0c=p0c))\n        return d\n\n    def twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False):\n        \"\"\"\n        Returns a ParticleGroup with requested Twiss parameters.\n\n        See: statistics.matched_particles\n        \"\"\"\n\n        return matched_particles(self, beta=beta, alpha=alpha, plane=plane, inplace=inplace)\n\n\n    @property\n    def in_z_coordinates(self):\n        \"\"\"\n        Returns True if all particles have the same z coordinate\n        \"\"\" \n        # Check that z are all the same\n        return len(np.unique(self.z)) == 1           \n\n    @property\n    def in_t_coordinates(self):\n        \"\"\"\n        Returns True if all particles have the same t coordinate\n        \"\"\" \n        # Check that t are all the same\n        return len(np.unique(self.t)) == 1        \n\n\n\n    @property\n    def average_current(self):\n        \"\"\"\n        Simple average `current = charge / dt` in [A], with `dt =  (max_t - min_t)`\n        If particles are in $t$ coordinates, will try` dt = (max_z - min_z)*c_light*beta_z`\n        \"\"\"\n        dt = self.t.ptp()  # ptp 'peak to peak' is max - min\n        if dt == 0:\n            # must be in t coordinates. Calc with \n            dt = self.z.ptp() / (self.avg('beta_z')*c_light)\n        return self.charge / dt\n\n    def bunching(self, wavelength):\n        \"\"\"\n        Calculate the normalized bunching parameter, which is the magnitude of the \n        complex sum of weighted exponentials at a given point.\n\n        The formula for bunching is given by:\n\n        $$\n        B(z, \\lambda) = \\frac{\\left|\\sum w_i e^{i k z_i}\\right|}{\\sum w_i}\n        $$\n\n        where:\n        - \\( z \\) is the position array,\n        - \\( \\lambda \\) is the wavelength,\n        - \\( k = \\frac{2\\pi}{\\lambda} \\) is the wave number,\n        - \\( w_i \\) are the weights.\n\n        Parameters\n        ----------\n        wavelength : float\n            Wavelength of the wave.\n\n\n        Returns\n        -------\n        complex\n            The normalized bunching parameter.\n\n        Raises\n        ------\n        ValueError\n            If `wavelength` is not a positive number.\n        \"\"\"        \n\n        if self.in_z_coordinates:\n            # Approximate z\n            z = self.t * self.avg('beta_z')*c_light\n        else:\n            z = self.z\n\n        return statistics.bunching(z, wavelength, weight=self.weight)\n\n    def __getitem__(self, key):\n        \"\"\"\n        Returns a property or statistical quantity that can be computed:\n\n        - `P['x']` returns the x array\n        - `P['sigmx_x']` returns the std(x) scalar\n        - `P['norm_emit_x']` returns the norm_emit_x scalar\n\n        Parts can also be given. Example: `P[0:10]` returns a new ParticleGroup with the first 10 elements.\n        \"\"\"\n\n        # Allow for non-string operations: \n        if not isinstance(key, str):\n            return particle_parts(self, key)\n\n        if key.startswith('cov_'):\n            subkeys = key[4:].split('__')\n            assert len(subkeys) == 2, f'Too many properties in covariance request: {key}'\n            return self.cov(*subkeys)[0,1]\n        elif key.startswith('delta_'):\n            return self.delta(key[6:])\n        elif key.startswith('sigma_'):\n            return self.std(key[6:])\n        elif key.startswith('mean_'):\n            return self.avg(key[5:])\n        elif key.startswith('min_'):\n            return self.min(key[4:])\n        elif key.startswith('max_'):\n            return self.max(key[4:])     \n        elif key.startswith('ptp_'):\n            return self.ptp(key[4:])   \n        elif 'bunching' in key:\n            wavelength = parse_bunching_str(key)\n            bunching = self.bunching(wavelength) # complex\n\n            # abs or arg (angle):\n            if 'phase_' in key:\n                return np.angle(bunching)\n            else:\n                return np.abs(bunching)\n\n        else:\n            return getattr(self, key) \n\n    def where(self, x):\n        return self[np.where(x)]\n\n    # TODO: should the user be allowed to do this?\n    #def __setitem__(self, key, value):    \n    #    assert key in self._settable_keyes, 'Error: you cannot set:'+str(key)\n    #    \n    #    if key in self._settable_array_keys:\n    #        assert len(value) == self.n_particle\n    #        self.__dict__[key] = value\n    #    elif key == \n    #        print()\n\n    # Simple 'tracking'     \n    def drift(self, delta_t):\n        \"\"\"\n        Drifts particles by time delta_t\n        \"\"\"\n        self.x = self.x + self.beta_x * c_light * delta_t\n        self.y = self.y + self.beta_y * c_light * delta_t\n        self.z = self.z + self.beta_z * c_light * delta_t\n        self.t = self.t + delta_t\n\n    def drift_to_z(self, z=None):\n\n        if z is None:\n            z = self.avg('z')\n        dt = (z - self.z) / (self.beta_z * c_light)\n        self.drift(dt)\n        # Fix z to be exactly this value\n        self.z = np.full(self.n_particle, z)\n\n\n    def drift_to_t(self, t=None):\n        \"\"\"\n        Drifts all particles to the same t\n\n        If no z is given, particles will be drifted to the average t\n        \"\"\"\n        if t is None:\n            t = self.avg('t')\n        dt = t - self.t\n        self.drift(dt)\n        # Fix t to be exactly this value\n        self.t = np.full(self.n_particle, t)\n\n\n    # -------        \n    # dict methods\n\n    # Do not do this, it breaks deepcopy\n    #def __dict__(self):\n    #    return self.data\n\n\n    @functools.wraps(bmad.particlegroup_to_bmad)\n    def to_bmad(self, p0c=None, tref=None):\n        return bmad.particlegroup_to_bmad(self, p0c=p0c, tref=tref)\n\n\n    @classmethod\n    @functools.wraps(bmad.bmad_to_particlegroup_data)\n    def from_bmad(cls, bmad_dict):\n        \"\"\"\n        Convert Bmad particle data as a dict \n        to ParticleGroup data.\n\n        See: ParticleGroup.to_bmad or particlegroup_to_bmad\n\n        Parameters\n        ----------\n        bmad_data: dict\n            Dict with keys:\n            'x'\n            'px'\n            'y'\n            'py'\n            'z'\n            'pz', \n            'charge'\n            'species',\n            'tref'\n            'state'\n\n        Returns\n        -------\n        ParticleGroup\n        \"\"\"        \n        data = bmad.bmad_to_particlegroup_data(bmad_dict)\n        return cls(data=data)\n\n    # -------\n    # Writers\n\n    @functools.wraps(write_astra)    \n    def write_astra(self, filePath, verbose=False, probe=False):\n        write_astra(self, filePath, verbose=verbose, probe=probe)\n\n    def write_bmad(self, filePath, p0c=None, t_ref=0, verbose=False):\n        bmad.write_bmad(self, filePath, p0c=p0c, t_ref=t_ref, verbose=verbose)        \n\n    def write_elegant(self, filePath, verbose=False):\n        write_elegant(self, filePath, verbose=verbose)            \n\n    def write_genesis2_beam_file(self, filePath, n_slice=None, verbose=False):\n        # Get beam columns \n        beam_columns = genesis2_beam_data(self, n_slice=n_slice)\n        # Actually write the file\n        write_genesis2_beam_file(filePath, beam_columns, verbose=verbose)  \n\n    @functools.wraps(write_genesis4_beam)          \n    def write_genesis4_beam(self, filePath, n_slice=None, return_input_str=False, verbose=False):\n        return write_genesis4_beam(self, filePath, n_slice=n_slice, return_input_str=return_input_str, verbose=verbose)\n\n    def write_genesis4_distribution(self, filePath, verbose=False):\n        write_genesis4_distribution(self, filePath, verbose=verbose)\n\n    def write_gpt(self, filePath, asci2gdf_bin=None, verbose=False):\n        write_gpt(self, filePath, asci2gdf_bin=asci2gdf_bin, verbose=verbose)    \n\n    def write_impact(self, filePath, cathode_kinetic_energy_ref=None, include_header=True, verbose=False):\n        return write_impact(self, filePath, cathode_kinetic_energy_ref=cathode_kinetic_energy_ref,\n                            include_header=include_header, verbose=verbose)          \n\n    def write_litrack(self, filePath, p0c=None, verbose=False):        \n        return write_litrack(self, outfile=filePath, p0c=p0c, verbose=verbose)      \n\n    def write_lucretia(self, filePath, ele_name='BEGINNING', t_ref=0, stop_ix=None, verbose=False):       \n        return write_lucretia(self, filePath, ele_name=ele_name, t_ref=t_ref, stop_ix=stop_ix)\n\n    def write_simion(self, filePath, color=0, flip_z_to_x=True, verbose=False):\n        return write_simion(self, filePath, verbose=verbose, color=color, flip_z_to_x=flip_z_to_x)\n\n\n    def write_opal(self, filePath, verbose=False, dist_type='emitted'):\n        return write_opal(self, filePath, verbose=verbose, dist_type=dist_type)\n\n\n    # openPMD    \n    def write(self, h5, name=None):\n        \"\"\"\n        Writes to an open h5 handle, or new file if h5 is a str.\n\n        \"\"\"\n        if isinstance(h5, str):\n            fname = os.path.expandvars(h5)\n            g = File(fname, 'w')\n            pmd_init(g, basePath='/', particlesPath='.' )\n        else:\n            g = h5\n\n        write_pmd_bunch(g, self, name=name)        \n\n\n    # Plotting\n    # --------\n    def plot(self, key1='x', key2=None,\n             bins=None,\n             *,\n             xlim=None,\n             ylim=None,\n             return_figure=False, \n             tex=True, nice=True,\n             **kwargs):\n        \"\"\"\n        1d or 2d density plot. \n\n        If one key is given, this will plot the density of that key.\n        Example:\n            .plot('x')\n\n        If two keys arg given, this will plot a 2d marginal plot.\n        Example:\n            .plot('x', 'px')\n\n\n        Parameters\n        ----------\n        particle_group: ParticleGroup\n            The object to plot\n\n        key1: str, default = 't'\n            Key to bin on the x-axis\n\n        key2: str, default = None\n            Key to bin on the y-axis. \n\n        bins: int, default = None\n           Number of bins. If None, this will use a heuristic: bins = sqrt(n_particle/4)\n\n        xlim: tuple, default = None\n            Manual setting of the x-axis limits. Note that these are in raw, unscaled units. \n\n        ylim: tuple, default = None\n            Manual setting of the y-axis limits. Note that these are in raw, unscaled units. \n\n        tex: bool, default = True\n            Use TEX for labels   \n\n        nice: bool, default = True\n            Scale to nice units\n\n        return_figure: bool, default = False\n            If true, return a matplotlib.figure.Figure object\n\n        **kwargs\n            Any additional kwargs to send to the the plot in: plt.subplots(**kwargs)\n\n\n        Returns\n        -------\n        None or fig: matplotlib.figure.Figure\n            This only returns a figure object if return_figure=T, otherwise returns None\n\n        \"\"\"\n\n        if not key2:\n            fig = density_plot(self, key=key1,\n                               bins=bins,\n                               xlim=xlim,\n                               tex=tex,\n                               nice=nice,\n                               **kwargs)\n        else:\n            fig = marginal_plot(self, key1=key1, key2=key2,\n                                bins=bins,\n                                xlim=xlim,\n                                ylim=ylim,\n                                tex=tex,\n                                nice=nice,\n                                **kwargs)\n\n        if return_figure:\n            return fig\n\n\n\n\n    def slice_statistics(self, *keys,\n                   n_slice=100,\n                   slice_key=None):\n        \"\"\"\n        Slice statistics\n\n        \"\"\"      \n\n        if slice_key is None:\n            if self.in_t_coordinates:\n                slice_key = 'z'\n\n            else:\n                slice_key = 't'  \n\n        if slice_key in ('t', 'delta_t'):\n            density_name = 'current'\n        else:\n            density_name = 'density'\n\n        keys = set(keys)\n        keys.add('mean_'+slice_key)\n        keys.add('ptp_'+slice_key)\n        keys.add('charge')\n        slice_dat = slice_statistics(self, n_slice=n_slice, slice_key=slice_key,\n                                keys=keys)\n\n        slice_dat[density_name] = slice_dat['charge']/ slice_dat['ptp_'+slice_key]\n\n        return slice_dat\n\n    def slice_plot(self, *keys,\n                   n_slice=100,\n                   slice_key=None,\n                   tex=True,\n                   nice=True,\n                   return_figure=False,\n                   xlim=None,\n                   ylim=None,\n                   **kwargs):   \n\n        fig = slice_plot(self, *keys,\n                         n_slice=n_slice,\n                         slice_key=slice_key,\n                         tex=tex,\n                         nice=nice,\n                         xlim=xlim,\n                         ylim=ylim,\n                         **kwargs)\n\n        if return_figure:\n            return fig        \n\n\n    # New constructors\n    def split(self, n_chunks = 100, key='z'):\n        return split_particles(self, n_chunks=n_chunks, key=key)\n\n    def copy(self):\n        \"\"\"Returns a deep copy\"\"\"\n        return deepcopy(self)    \n\n    @functools.wraps(resample_particles)\n    def resample(self, n=0, equal_weights=False):\n        data = resample_particles(self, n, equal_weights=equal_weights)\n        return ParticleGroup(data=data)\n\n    # Internal sorting\n    def _sort(self, key):\n        \"\"\"Sorts internal arrays by key\"\"\"\n        ixlist = np.argsort(self[key])\n        for k in self._settable_array_keys:\n            self._data[k] = self[k][ixlist]    \n\n    # Operator overloading    \n    def __add__(self, other):\n        \"\"\"\n        Overloads the + operator to join particle groups.\n        Simply calls join_particle_groups\n        \"\"\"\n        return join_particle_groups(self, other)\n\n    # \n    def __contains__(self, item):\n        \"\"\"Checks internal data\"\"\"\n        return True if item in self._data else False    \n\n    def __eq__(self, other):\n        \"\"\"Check equality of internal data\"\"\"\n        if isinstance(other, ParticleGroup):\n            for key in ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', 'id']:\n                if not np.allclose(self[key], other[key]):\n                    return False\n            return True\n\n        return NotImplemented    \n\n    def __len__(self):\n        return len(self[self._settable_array_keys[0]])\n\n    def __str__(self):\n        s = f'ParticleGroup with {self.n_particle} particles with total charge {self.charge} C'\n        return s\n\n    def __repr__(self):\n        memloc = hex(id(self))\n        return f'<ParticleGroup with {self.n_particle} particles at {memloc}>'\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.Jx","title":"Jx property","text":"

Normalized amplitude J in the x-px plane

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.Jy","title":"Jy property","text":"

Normalized amplitude J in the y-py plane

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.Lz","title":"Lz property","text":"

Angular momentum around the z axis in m*eV/c Lz = x * py - y * px

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.average_current","title":"average_current property","text":"

Simple average current = charge / dt in [A], with dt = (max_t - min_t) If particles are in \\(t\\) coordinates, will trydt = (max_z - min_z)*c_light*beta_z

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.beta","title":"beta property","text":"

Relativistic beta

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.beta_x","title":"beta_x property writable","text":"

Relativistic beta, x component

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.beta_y","title":"beta_y property writable","text":"

Relativistic beta, y component

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.beta_z","title":"beta_z property writable","text":"

Relativistic beta, z component

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.charge","title":"charge property writable","text":"

Total charge in C

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.data","title":"data property","text":"

Internal data dict

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.energy","title":"energy property","text":"

Total energy in eV

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.gamma","title":"gamma property writable","text":"

Relativistic gamma

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.higher_order_energy","title":"higher_order_energy property","text":"

Fits a quadratic (order=2) to the Energy vs. time, and returns the energy with this subtracted.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.higher_order_energy_spread","title":"higher_order_energy_spread property","text":"

Legacy syntax to compute the standard deviation of higher_order_energy.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.id","title":"id property writable","text":"

id integer

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.in_t_coordinates","title":"in_t_coordinates property","text":"

Returns True if all particles have the same t coordinate

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.in_z_coordinates","title":"in_z_coordinates property","text":"

Returns True if all particles have the same z coordinate

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.kinetic_energy","title":"kinetic_energy property","text":"

Kinetic energy in eV

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.mass","title":"mass property","text":"

Rest mass in eV

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.n_alive","title":"n_alive property","text":"

Number of alive particles, defined by status == 1

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.n_dead","title":"n_dead property","text":"

Number of alive particles, defined by status != 1

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.n_particle","title":"n_particle property","text":"

Total number of particles. Same as len

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.norm_emit_4d","title":"norm_emit_4d property","text":"

Normalized emittance in the xy planes (4D)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.norm_emit_x","title":"norm_emit_x property","text":"

Normalized emittance in the x plane

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.norm_emit_y","title":"norm_emit_y property","text":"

Normalized emittance in the x plane

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.p","title":"p property","text":"

Total momemtum in eV/c

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.pr","title":"pr property","text":"

Momentum in the radial direction in eV/c r_hat = cos(theta) xhat + sin(theta) yhat pr = p dot r_hat

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.ptheta","title":"ptheta property","text":"

Momentum in the polar theta direction. theta_hat = -sin(theta) xhat + cos(theta) yhat ptheta = p dot theta_hat Note that Lz = r*ptheta

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.px","title":"px property writable","text":"

px coordinate in [eV/c]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.px_bar","title":"px_bar property","text":"

Normalized px in units of sqrt(m)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.py","title":"py property writable","text":"

py coordinate in [eV/c]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.py_bar","title":"py_bar property","text":"

Normalized py in units of sqrt(m)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.pz","title":"pz property writable","text":"

pz coordinate in [eV/c]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.r","title":"r property","text":"

Radius in the xy plane: r = sqrt(x^2 + y^2) in m

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.species","title":"species property writable","text":"

species string

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.species_charge","title":"species_charge property","text":"

Species charge in C

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.status","title":"status property writable","text":"

status coordinate in [1]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.t","title":"t property writable","text":"

t coordinate in [s]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.theta","title":"theta property","text":"

Angle in xy plane: theta = arctan2(y, x) in radians

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.weight","title":"weight property writable","text":"

weight coordinate in [C]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.x","title":"x property writable","text":"

x coordinate in [m]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.x_bar","title":"x_bar property","text":"

Normalized x in units of sqrt(m)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.xp","title":"xp property","text":"

x slope px/pz (dimensionless)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.y","title":"y property writable","text":"

y coordinate in [m]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.y_bar","title":"y_bar property","text":"

Normalized y in units of sqrt(m)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.yp","title":"yp property","text":"

y slope py/pz (dimensionless)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.z","title":"z property writable","text":"

z coordinate in [m]

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.__add__","title":"__add__(other)","text":"

Overloads the + operator to join particle groups. Simply calls join_particle_groups

Source code in pmd_beamphysics/particles.py
def __add__(self, other):\n    \"\"\"\n    Overloads the + operator to join particle groups.\n    Simply calls join_particle_groups\n    \"\"\"\n    return join_particle_groups(self, other)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.__contains__","title":"__contains__(item)","text":"

Checks internal data

Source code in pmd_beamphysics/particles.py
def __contains__(self, item):\n    \"\"\"Checks internal data\"\"\"\n    return True if item in self._data else False    \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.__eq__","title":"__eq__(other)","text":"

Check equality of internal data

Source code in pmd_beamphysics/particles.py
def __eq__(self, other):\n    \"\"\"Check equality of internal data\"\"\"\n    if isinstance(other, ParticleGroup):\n        for key in ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', 'id']:\n            if not np.allclose(self[key], other[key]):\n                return False\n        return True\n\n    return NotImplemented    \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.__getitem__","title":"__getitem__(key)","text":"

Returns a property or statistical quantity that can be computed:

  • P['x'] returns the x array
  • P['sigmx_x'] returns the std(x) scalar
  • P['norm_emit_x'] returns the norm_emit_x scalar

Parts can also be given. Example: P[0:10] returns a new ParticleGroup with the first 10 elements.

Source code in pmd_beamphysics/particles.py
def __getitem__(self, key):\n    \"\"\"\n    Returns a property or statistical quantity that can be computed:\n\n    - `P['x']` returns the x array\n    - `P['sigmx_x']` returns the std(x) scalar\n    - `P['norm_emit_x']` returns the norm_emit_x scalar\n\n    Parts can also be given. Example: `P[0:10]` returns a new ParticleGroup with the first 10 elements.\n    \"\"\"\n\n    # Allow for non-string operations: \n    if not isinstance(key, str):\n        return particle_parts(self, key)\n\n    if key.startswith('cov_'):\n        subkeys = key[4:].split('__')\n        assert len(subkeys) == 2, f'Too many properties in covariance request: {key}'\n        return self.cov(*subkeys)[0,1]\n    elif key.startswith('delta_'):\n        return self.delta(key[6:])\n    elif key.startswith('sigma_'):\n        return self.std(key[6:])\n    elif key.startswith('mean_'):\n        return self.avg(key[5:])\n    elif key.startswith('min_'):\n        return self.min(key[4:])\n    elif key.startswith('max_'):\n        return self.max(key[4:])     \n    elif key.startswith('ptp_'):\n        return self.ptp(key[4:])   \n    elif 'bunching' in key:\n        wavelength = parse_bunching_str(key)\n        bunching = self.bunching(wavelength) # complex\n\n        # abs or arg (angle):\n        if 'phase_' in key:\n            return np.angle(bunching)\n        else:\n            return np.abs(bunching)\n\n    else:\n        return getattr(self, key) \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.assign_id","title":"assign_id()","text":"

Assigns unique ids, integers from 1 to n_particle

Source code in pmd_beamphysics/particles.py
def assign_id(self):\n    \"\"\"\n    Assigns unique ids, integers from 1 to n_particle\n\n    \"\"\"\n    if 'id' not in self._settable_array_keys: \n        self._settable_array_keys.append('id')\n    self.id = np.arange(1, self['n_particle']+1)             \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.avg","title":"avg(key)","text":"

Statistical average

Source code in pmd_beamphysics/particles.py
def avg(self, key):\n    \"\"\"Statistical average\"\"\"\n    dat = self[key]  # equivalent to self.key for accessing properties above\n    if np.isscalar(dat): \n        return dat\n    return np.average(dat, weights=self.weight)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.bunching","title":"bunching(wavelength)","text":"

Calculate the normalized bunching parameter, which is the magnitude of the complex sum of weighted exponentials at a given point.

The formula for bunching is given by:

\\[ B(z, \\lambda) = \frac{\\left|\\sum w_i e^{i k z_i} ight|}{\\sum w_i} \\]

where: - \\( z \\) is the position array, - \\( \\lambda \\) is the wavelength, - \\( k = \frac{2\\pi}{\\lambda} \\) is the wave number, - \\( w_i \\) are the weights.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.bunching--parameters","title":"Parameters","text":"

wavelength : float Wavelength of the wave.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.bunching--returns","title":"Returns","text":"

complex The normalized bunching parameter.

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.bunching--raises","title":"Raises","text":"

ValueError If wavelength is not a positive number.

Source code in pmd_beamphysics/particles.py
def bunching(self, wavelength):\n    \"\"\"\n    Calculate the normalized bunching parameter, which is the magnitude of the \n    complex sum of weighted exponentials at a given point.\n\n    The formula for bunching is given by:\n\n    $$\n    B(z, \\lambda) = \\frac{\\left|\\sum w_i e^{i k z_i}\\right|}{\\sum w_i}\n    $$\n\n    where:\n    - \\( z \\) is the position array,\n    - \\( \\lambda \\) is the wavelength,\n    - \\( k = \\frac{2\\pi}{\\lambda} \\) is the wave number,\n    - \\( w_i \\) are the weights.\n\n    Parameters\n    ----------\n    wavelength : float\n        Wavelength of the wave.\n\n\n    Returns\n    -------\n    complex\n        The normalized bunching parameter.\n\n    Raises\n    ------\n    ValueError\n        If `wavelength` is not a positive number.\n    \"\"\"        \n\n    if self.in_z_coordinates:\n        # Approximate z\n        z = self.t * self.avg('beta_z')*c_light\n    else:\n        z = self.z\n\n    return statistics.bunching(z, wavelength, weight=self.weight)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.copy","title":"copy()","text":"

Returns a deep copy

Source code in pmd_beamphysics/particles.py
def copy(self):\n    \"\"\"Returns a deep copy\"\"\"\n    return deepcopy(self)    \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.cov","title":"cov(*keys)","text":"

Covariance matrix from any properties

Example: P = ParticleGroup(h5) P.cov('x', 'px', 'y', 'py')

Source code in pmd_beamphysics/particles.py
def cov(self, *keys):\n    \"\"\"\n    Covariance matrix from any properties\n\n    Example: \n    P = ParticleGroup(h5)\n    P.cov('x', 'px', 'y', 'py')\n\n    \"\"\"\n    dats = np.array([ self[key] for key in keys ])\n    return np.cov(dats, aweights=self.weight)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.delta","title":"delta(key)","text":"

Attribute (array) relative to its mean

Source code in pmd_beamphysics/particles.py
def delta(self, key):\n    \"\"\"Attribute (array) relative to its mean\"\"\"\n    return self[key] - self.avg(key)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.drift","title":"drift(delta_t)","text":"

Drifts particles by time delta_t

Source code in pmd_beamphysics/particles.py
def drift(self, delta_t):\n    \"\"\"\n    Drifts particles by time delta_t\n    \"\"\"\n    self.x = self.x + self.beta_x * c_light * delta_t\n    self.y = self.y + self.beta_y * c_light * delta_t\n    self.z = self.z + self.beta_z * c_light * delta_t\n    self.t = self.t + delta_t\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.drift_to_t","title":"drift_to_t(t=None)","text":"

Drifts all particles to the same t

If no z is given, particles will be drifted to the average t

Source code in pmd_beamphysics/particles.py
def drift_to_t(self, t=None):\n    \"\"\"\n    Drifts all particles to the same t\n\n    If no z is given, particles will be drifted to the average t\n    \"\"\"\n    if t is None:\n        t = self.avg('t')\n    dt = t - self.t\n    self.drift(dt)\n    # Fix t to be exactly this value\n    self.t = np.full(self.n_particle, t)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.from_bmad","title":"from_bmad(bmad_dict) classmethod","text":"

Convert Bmad particle data as a dict to ParticleGroup data.

See: ParticleGroup.to_bmad or particlegroup_to_bmad

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.from_bmad--parameters","title":"Parameters","text":"

bmad_data: dict Dict with keys: 'x' 'px' 'y' 'py' 'z' 'pz', 'charge' 'species', 'tref' 'state'

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.from_bmad--returns","title":"Returns","text":"

ParticleGroup

Source code in pmd_beamphysics/particles.py
@classmethod\n@functools.wraps(bmad.bmad_to_particlegroup_data)\ndef from_bmad(cls, bmad_dict):\n    \"\"\"\n    Convert Bmad particle data as a dict \n    to ParticleGroup data.\n\n    See: ParticleGroup.to_bmad or particlegroup_to_bmad\n\n    Parameters\n    ----------\n    bmad_data: dict\n        Dict with keys:\n        'x'\n        'px'\n        'y'\n        'py'\n        'z'\n        'pz', \n        'charge'\n        'species',\n        'tref'\n        'state'\n\n    Returns\n    -------\n    ParticleGroup\n    \"\"\"        \n    data = bmad.bmad_to_particlegroup_data(bmad_dict)\n    return cls(data=data)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.higher_order_energy_calc","title":"higher_order_energy_calc(order=2)","text":"

Fits a polynmial with order order to the Energy vs. time, , and returns the energy with this subtracted.

Source code in pmd_beamphysics/particles.py
def higher_order_energy_calc(self, order=2):\n    \"\"\"\n    Fits a polynmial with order `order` to the Energy vs. time, , and returns the energy with this subtracted. \n    \"\"\"\n    #order=2\n    if self.std('z') < 1e-12:\n        # must be at a screen. Use t\n        t = self.t\n    else:\n        # All particles at the same time. Use z to calc t\n        t = self.z/c_light\n    energy = self.energy\n\n    best_fit_coeffs = np.polynomial.polynomial.polyfit(t, energy, order)\n    best_fit = np.polynomial.polynomial.polyval(t, best_fit_coeffs)\n    return energy - best_fit\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.histogramdd","title":"histogramdd(*keys, bins=10, range=None)","text":"

Wrapper for numpy.histogramdd, but accepts property names as keys.

Computes the multidimensional histogram of keys. Internally uses weights.

Example

P.histogramdd('x', 'y', bins=50)

Returns: np.array with shape 50x50, edge list

Source code in pmd_beamphysics/particles.py
def histogramdd(self, *keys, bins=10, range=None):\n    \"\"\"\n    Wrapper for numpy.histogramdd, but accepts property names as keys.\n\n    Computes the multidimensional histogram of keys. Internally uses weights. \n\n    Example:\n        P.histogramdd('x', 'y', bins=50)\n    Returns:\n        np.array with shape 50x50, edge list \n\n    \"\"\"\n    H, edges = np.histogramdd(np.array([self[k] for k in list(keys)]).T, weights=self.weight, bins=bins, range=range)\n\n    return H, edges\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.max","title":"max(key)","text":"

Maximum of any key

Source code in pmd_beamphysics/particles.py
def max(self, key):\n    \"\"\"Maximum of any key\"\"\"\n    return np.max(self[key]) \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.min","title":"min(key)","text":"

Minimum of any key

Source code in pmd_beamphysics/particles.py
def min(self, key):\n    \"\"\"Minimum of any key\"\"\"\n    return np.min(self[key]) # was: getattr(self, key)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.plot","title":"plot(key1='x', key2=None, bins=None, *, xlim=None, ylim=None, return_figure=False, tex=True, nice=True, **kwargs)","text":"

1d or 2d density plot.

If one key is given, this will plot the density of that key. Example: .plot('x')

If two keys arg given, this will plot a 2d marginal plot. Example: .plot('x', 'px')

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.plot--parameters","title":"Parameters","text":"

particle_group: ParticleGroup The object to plot

str, default = 't'

Key to bin on the x-axis

str, default = None

Key to bin on the y-axis.

int, default = None

Number of bins. If None, this will use a heuristic: bins = sqrt(n_particle/4)

tuple, default = None

Manual setting of the x-axis limits. Note that these are in raw, unscaled units.

tuple, default = None

Manual setting of the y-axis limits. Note that these are in raw, unscaled units.

bool, default = True

Use TEX for labels

bool, default = True

Scale to nice units

bool, default = False

If true, return a matplotlib.figure.Figure object

kwargs Any additional kwargs to send to the the plot in: plt.subplots(kwargs)

"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.plot--returns","title":"Returns","text":"

None or fig: matplotlib.figure.Figure This only returns a figure object if return_figure=T, otherwise returns None

Source code in pmd_beamphysics/particles.py
def plot(self, key1='x', key2=None,\n         bins=None,\n         *,\n         xlim=None,\n         ylim=None,\n         return_figure=False, \n         tex=True, nice=True,\n         **kwargs):\n    \"\"\"\n    1d or 2d density plot. \n\n    If one key is given, this will plot the density of that key.\n    Example:\n        .plot('x')\n\n    If two keys arg given, this will plot a 2d marginal plot.\n    Example:\n        .plot('x', 'px')\n\n\n    Parameters\n    ----------\n    particle_group: ParticleGroup\n        The object to plot\n\n    key1: str, default = 't'\n        Key to bin on the x-axis\n\n    key2: str, default = None\n        Key to bin on the y-axis. \n\n    bins: int, default = None\n       Number of bins. If None, this will use a heuristic: bins = sqrt(n_particle/4)\n\n    xlim: tuple, default = None\n        Manual setting of the x-axis limits. Note that these are in raw, unscaled units. \n\n    ylim: tuple, default = None\n        Manual setting of the y-axis limits. Note that these are in raw, unscaled units. \n\n    tex: bool, default = True\n        Use TEX for labels   \n\n    nice: bool, default = True\n        Scale to nice units\n\n    return_figure: bool, default = False\n        If true, return a matplotlib.figure.Figure object\n\n    **kwargs\n        Any additional kwargs to send to the the plot in: plt.subplots(**kwargs)\n\n\n    Returns\n    -------\n    None or fig: matplotlib.figure.Figure\n        This only returns a figure object if return_figure=T, otherwise returns None\n\n    \"\"\"\n\n    if not key2:\n        fig = density_plot(self, key=key1,\n                           bins=bins,\n                           xlim=xlim,\n                           tex=tex,\n                           nice=nice,\n                           **kwargs)\n    else:\n        fig = marginal_plot(self, key1=key1, key2=key2,\n                            bins=bins,\n                            xlim=xlim,\n                            ylim=ylim,\n                            tex=tex,\n                            nice=nice,\n                            **kwargs)\n\n    if return_figure:\n        return fig\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.ptp","title":"ptp(key)","text":"

Peak-to-Peak = max - min of any key

Source code in pmd_beamphysics/particles.py
def ptp(self, key):\n    \"\"\"Peak-to-Peak = max - min of any key\"\"\"\n    return np.ptp(self[key])     \n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.slice_statistics","title":"slice_statistics(*keys, n_slice=100, slice_key=None)","text":"

Slice statistics

Source code in pmd_beamphysics/particles.py
def slice_statistics(self, *keys,\n               n_slice=100,\n               slice_key=None):\n    \"\"\"\n    Slice statistics\n\n    \"\"\"      \n\n    if slice_key is None:\n        if self.in_t_coordinates:\n            slice_key = 'z'\n\n        else:\n            slice_key = 't'  \n\n    if slice_key in ('t', 'delta_t'):\n        density_name = 'current'\n    else:\n        density_name = 'density'\n\n    keys = set(keys)\n    keys.add('mean_'+slice_key)\n    keys.add('ptp_'+slice_key)\n    keys.add('charge')\n    slice_dat = slice_statistics(self, n_slice=n_slice, slice_key=slice_key,\n                            keys=keys)\n\n    slice_dat[density_name] = slice_dat['charge']/ slice_dat['ptp_'+slice_key]\n\n    return slice_dat\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.std","title":"std(key)","text":"

Standard deviation (actually sample)

Source code in pmd_beamphysics/particles.py
def std(self, key):\n    \"\"\"Standard deviation (actually sample)\"\"\"\n    dat = self[key]\n    if np.isscalar(dat):\n        return 0\n    avg_dat = self.avg(key)\n    return np.sqrt(np.average( (dat - avg_dat)**2, weights=self.weight))\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.twiss","title":"twiss(plane='x', fraction=1, p0c=None)","text":"

Returns Twiss and Dispersion dict.

plane can be:

'x', 'y', or 'xy'

Optionally a fraction of the particles, based on amplitiude, can be specified.

Source code in pmd_beamphysics/particles.py
def twiss(self, plane='x', fraction=1, p0c=None):\n    \"\"\"\n    Returns Twiss and Dispersion dict.\n\n    plane can be:\n\n    `'x'`, `'y'`, or `'xy'`\n\n    Optionally a fraction of the particles, based on amplitiude, can be specified.\n    \"\"\"\n    d = {}\n    for p in plane:\n        d.update(particle_twiss_dispersion(self, plane=p, fraction=fraction, p0c=p0c))\n    return d\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.twiss_match","title":"twiss_match(beta=None, alpha=None, plane='x', p0c=None, inplace=False)","text":"

Returns a ParticleGroup with requested Twiss parameters.

See: statistics.matched_particles

Source code in pmd_beamphysics/particles.py
def twiss_match(self, beta=None, alpha=None, plane='x', p0c=None, inplace=False):\n    \"\"\"\n    Returns a ParticleGroup with requested Twiss parameters.\n\n    See: statistics.matched_particles\n    \"\"\"\n\n    return matched_particles(self, beta=beta, alpha=alpha, plane=plane, inplace=inplace)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.units","title":"units(key)","text":"

Returns the units of any key

Source code in pmd_beamphysics/particles.py
def units(self, key):\n    \"\"\"Returns the units of any key\"\"\"\n    return pg_units(key)\n
"},{"location":"api/particles/#pmd_beamphysics.ParticleGroup.write","title":"write(h5, name=None)","text":"

Writes to an open h5 handle, or new file if h5 is a str.

Source code in pmd_beamphysics/particles.py
def write(self, h5, name=None):\n    \"\"\"\n    Writes to an open h5 handle, or new file if h5 is a str.\n\n    \"\"\"\n    if isinstance(h5, str):\n        fname = os.path.expandvars(h5)\n        g = File(fname, 'w')\n        pmd_init(g, basePath='/', particlesPath='.' )\n    else:\n        g = h5\n\n    write_pmd_bunch(g, self, name=name)        \n
"},{"location":"examples/bunching/","title":"Bunching","text":"In\u00a0[1]: Copied!
from pmd_beamphysics import ParticleGroup\n%config InlineBackend.figure_format = 'retina'\n
from pmd_beamphysics import ParticleGroup %config InlineBackend.figure_format = 'retina' In\u00a0[2]: Copied!
P = ParticleGroup( 'data/bmad_particles2.h5')\nP.drift_to_t()\n\nwavelength = 0.1e-6\ndz = (P.z/wavelength % 1) * wavelength\nP.z -= dz\n
P = ParticleGroup( 'data/bmad_particles2.h5') P.drift_to_t() wavelength = 0.1e-6 dz = (P.z/wavelength % 1) * wavelength P.z -= dz In\u00a0[3]: Copied!
P.plot('z')\n
P.plot('z') In\u00a0[4]: Copied!
b = P.bunching(wavelength)\nb\n
b = P.bunching(wavelength) b Out[4]:
(1-3.2133096564432656e-16j)
In\u00a0[5]: Copied!
P['bunching_0.1e-6']\n
P['bunching_0.1e-6'] Out[5]:
1.0
In\u00a0[6]: Copied!
P['bunching_phase_0.1e-6']\n
P['bunching_phase_0.1e-6'] Out[6]:
-3.2133096564432656e-16
In\u00a0[7]: Copied!
P['bunching_0.1_um']\n
P['bunching_0.1_um'] Out[7]:
1.0
In\u00a0[8]: Copied!
import numpy as np\n\nimport matplotlib.pyplot as plt\n
import numpy as np import matplotlib.pyplot as plt In\u00a0[9]: Copied!
wavelengths = wavelength * np.linspace(0.9, 1.1, 200)\n
wavelengths = wavelength * np.linspace(0.9, 1.1, 200) In\u00a0[10]: Copied!
plt.plot(wavelengths*1e6, np.abs(list(map(P.bunching, wavelengths))))\nplt.xlabel('wavelength (\u00b5m)')\nplt.ylabel('bunching')\n
plt.plot(wavelengths*1e6, np.abs(list(map(P.bunching, wavelengths)))) plt.xlabel('wavelength (\u00b5m)') plt.ylabel('bunching') Out[10]:
Text(0, 0.5, 'bunching')
In\u00a0[11]: Copied!
P.slice_plot('bunching_0.1_um')\n
P.slice_plot('bunching_0.1_um') In\u00a0[12]: Copied!
P.slice_plot('bunching_phase_0.1_um')\n
P.slice_plot('bunching_phase_0.1_um') In\u00a0[13]: Copied!
P.in_z_coordinates\n
P.in_z_coordinates Out[13]:
False
In\u00a0[14]: Copied!
P.units('bunching_0.1_um')\n
P.units('bunching_0.1_um') Out[14]:
pmd_unit('', 1, (0, 0, 0, 0, 0, 0, 0))
In\u00a0[15]: Copied!
P.units('bunching_phase_0.1_um')\n
P.units('bunching_phase_0.1_um') Out[15]:
pmd_unit('rad', 1, (0, 0, 0, 0, 0, 0, 0))
In\u00a0[16]: Copied!
from pmd_beamphysics.statistics import bunching\n
from pmd_beamphysics.statistics import bunching In\u00a0[17]: Copied!
?bunching\n
?bunching"},{"location":"examples/bunching/#bunching","title":"Bunching\u00b6","text":"

Bunching at some wavelength $\\lambda$ for a list of particles $z$ is given by the weighted sum of complex phasors:

$$B(z, \\lambda) \\equiv \\frac{\\sum_j w_j e^{i k z_j}}{\\sum w_j} $$

where $k = 2\\pi/\\lambda$ and $w_j$ are the weights of the particles.

See for example D. Ratner's disseratation.

"},{"location":"examples/bunching/#add-bunching-to-particles","title":"Add bunching to particles\u00b6","text":"

This uses a simple method to add perfect bunching at 0.1 \u00b5m

"},{"location":"examples/bunching/#calculate-bunching","title":"Calculate bunching\u00b6","text":"

All of these methods will calculate the bunching. The first returns a complex number bunching

The string attributes return real numbers, magnitude and argument (phase):

  • 'bunching_ returns np.abs(bunching)
  • 'bunching_phase_ returns np.angle(bunching)
"},{"location":"examples/bunching/#simple-plot","title":"Simple plot\u00b6","text":""},{"location":"examples/bunching/#units","title":"Units\u00b6","text":"

Bunching is dimensionless

"},{"location":"examples/bunching/#bunching-function","title":"Bunching function\u00b6","text":"

This is the function that is used.

"},{"location":"examples/labels/","title":"TeX Labels","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n
# Useful for debugging %load_ext autoreload %autoreload 2 In\u00a0[2]: Copied!
%pylab --no-import-all inline\n%config InlineBackend.figure_format = 'retina'\n
%pylab --no-import-all inline %config InlineBackend.figure_format = 'retina'
%pylab is deprecated, use %matplotlib inline and import the required libraries.\nPopulating the interactive namespace from numpy and matplotlib\n
In\u00a0[3]: Copied!
from pmd_beamphysics.labels import texlabel, TEXLABEL, mathlabel\nfrom pmd_beamphysics.units import pg_units\n
from pmd_beamphysics.labels import texlabel, TEXLABEL, mathlabel from pmd_beamphysics.units import pg_units

This is basic function

In\u00a0[4]: Copied!
?texlabel\n
?texlabel

Example:

In\u00a0[5]: Copied!
texlabel('norm_emit_x')\n
texlabel('norm_emit_x') Out[5]:
'\\\\epsilon_{n, x}'

Example with two parts:

In\u00a0[6]: Copied!
texlabel('cov_x__px')\n
texlabel('cov_x__px') Out[6]:
'\\\\left<x, p_x\\\\right>'

Returns None if a label cannot be formed:

In\u00a0[7]: Copied!
texlabel('garbage') is None\n
texlabel('garbage') is None Out[7]:
True
In\u00a0[8]: Copied!
examples = ['cov_x__px', 'sigma_y', 'mean_Jx']\nexamples += list(TEXLABEL)\n
examples = ['cov_x__px', 'sigma_y', 'mean_Jx'] examples += list(TEXLABEL) In\u00a0[9]: Copied!
for key in examples:\n    tex = texlabel(key)\n    s = fr'${tex}$'\n    print(key ,'->', tex)\n    fig, ax = plt.subplots(figsize=(1,1))\n    plt.axis('off')\n    \n    ax.text(0.5, 0.5, s, fontsize=32)\n    plt.show()\n
for key in examples: tex = texlabel(key) s = fr'${tex}$' print(key ,'->', tex) fig, ax = plt.subplots(figsize=(1,1)) plt.axis('off') ax.text(0.5, 0.5, s, fontsize=32) plt.show()
cov_x__px -> \\left<x, p_x\\right>\n
sigma_y -> \\sigma_{ y }\n
mean_Jx -> \\left<J_x\\right>\n
t -> t\n
energy -> E\n
kinetic_energy -> E_{kinetic}\n
Ex -> E_x\n
Ey -> E_y\n
Ez -> E_z\n
Bx -> B_x\n
By -> B_y\n
Bz -> B_z\n
Etheta -> E_{\\theta}\n
Btheta -> B_{\\theta}\n
px -> p_x\n
py -> p_y\n
pz -> p_z\n
p -> p\n
pr -> p_r\n
ptheta -> p_{\\theta}\n
x -> x\n
y -> y\n
z -> z\n
r -> r\n
Jx -> J_x\n
Jy -> J_y\n
beta -> \\beta\n
beta_x -> \\beta_x\n
beta_y -> \\beta_y\n
beta_z -> \\beta_z\n
gamma -> \\gamma\n
theta -> \\theta\n
charge -> Q\n
twiss_alpha_x -> Twiss\\ \\alpha_x\n
twiss_beta_x -> Twiss\\ \\beta_x\n
twiss_gamma_x -> Twiss\\ \\gamma_x\n
twiss_eta_x -> Twiss\\ \\eta_x\n
twiss_etap_x -> Twiss\\ \\eta'_x\n
twiss_emit_x -> Twiss\\ \\epsilon_{x}\n
twiss_norm_emit_x -> Twiss\\ \\epsilon_{n, x}\n
twiss_alpha_y -> Twiss\\ \\alpha_y\n
twiss_beta_y -> Twiss\\ \\beta_y\n
twiss_gamma_y -> Twiss\\ \\gamma_y\n
twiss_eta_y -> Twiss\\ \\eta_y\n
twiss_etap_y -> Twiss\\ \\eta'_y\n
twiss_emit_y -> Twiss\\ \\epsilon_{y}\n
twiss_norm_emit_y -> Twiss\\ \\epsilon_{n, y}\n
average_current -> I_{av}\n
norm_emit_x -> \\epsilon_{n, x}\n
norm_emit_y -> \\epsilon_{n, y}\n
norm_emit_4d -> \\epsilon_{4D}\n
Lz -> L_z\n
xp -> x'\n
yp -> y'\n
x_bar -> \\overline{x}\n
px_bar -> \\overline{p_x}\n
y_bar -> \\overline{y}\n
py_bar -> \\overline{p_y}\n
In\u00a0[10]: Copied!
?mathlabel\n
?mathlabel In\u00a0[11]: Copied!
mathlabel('beta_x', units=pg_units('beta_x'))\n
mathlabel('beta_x', units=pg_units('beta_x')) Out[11]:
'$\\\\beta_x$'

Multiple keys. Note that units are not checked for consistency!

In\u00a0[12]: Copied!
mathlabel('sigma_x', 'sigma_y', units='\u00b5m')\n
mathlabel('sigma_x', 'sigma_y', units='\u00b5m') Out[12]:
'$\\\\sigma_{ x }, \\\\sigma_{ y }~(\\\\mathrm{ \u00b5m } )$'
In\u00a0[13]: Copied!
for key in examples:\n    \n    label = mathlabel(key, units=pg_units(key))\n    print(key ,'->', label)\n    fig, ax = plt.subplots(figsize=(1,1))\n    plt.axis('off')\n    \n    ax.text(0.5, 0.5, label, fontsize=32)\n    plt.show()\n
for key in examples: label = mathlabel(key, units=pg_units(key)) print(key ,'->', label) fig, ax = plt.subplots(figsize=(1,1)) plt.axis('off') ax.text(0.5, 0.5, label, fontsize=32) plt.show()
cov_x__px -> $\\left<x, p_x\\right>~(\\mathrm{ m*eV/c } )$\n
sigma_y -> $\\sigma_{ y }~(\\mathrm{ m } )$\n
mean_Jx -> $\\left<J_x\\right>~(\\mathrm{ m } )$\n
t -> $t~(\\mathrm{ s } )$\n
energy -> $E~(\\mathrm{ eV } )$\n
kinetic_energy -> $E_{kinetic}~(\\mathrm{ eV } )$\n
Ex -> $E_x~(\\mathrm{ V/m } )$\n
Ey -> $E_y~(\\mathrm{ V/m } )$\n
Ez -> $E_z~(\\mathrm{ V/m } )$\n
Bx -> $B_x~(\\mathrm{ T } )$\n
By -> $B_y~(\\mathrm{ T } )$\n
Bz -> $B_z~(\\mathrm{ T } )$\n
Etheta -> $E_{\\theta}~(\\mathrm{ V/m } )$\n
Btheta -> $B_{\\theta}~(\\mathrm{ T } )$\n
px -> $p_x~(\\mathrm{ eV/c } )$\n
py -> $p_y~(\\mathrm{ eV/c } )$\n
pz -> $p_z~(\\mathrm{ eV/c } )$\n
p -> $p~(\\mathrm{ eV/c } )$\n
pr -> $p_r~(\\mathrm{ eV/c } )$\n
ptheta -> $p_{\\theta}~(\\mathrm{ eV/c } )$\n
x -> $x~(\\mathrm{ m } )$\n
y -> $y~(\\mathrm{ m } )$\n
z -> $z~(\\mathrm{ m } )$\n
r -> $r~(\\mathrm{ m } )$\n
Jx -> $J_x~(\\mathrm{ m } )$\n
Jy -> $J_y~(\\mathrm{ m } )$\n
beta -> $\\beta$\n
beta_x -> $\\beta_x$\n
beta_y -> $\\beta_y$\n
beta_z -> $\\beta_z$\n
gamma -> $\\gamma$\n
theta -> $\\theta~(\\mathrm{ rad } )$\n
charge -> $Q~(\\mathrm{ C } )$\n
twiss_alpha_x -> $Twiss\\ \\alpha_x$\n
twiss_beta_x -> $Twiss\\ \\beta_x~(\\mathrm{ m } )$\n
twiss_gamma_x -> $Twiss\\ \\gamma_x~(\\mathrm{ /m } )$\n
twiss_eta_x -> $Twiss\\ \\eta_x~(\\mathrm{ m } )$\n
twiss_etap_x -> $Twiss\\ \\eta'_x$\n
twiss_emit_x -> $Twiss\\ \\epsilon_{x}~(\\mathrm{ m } )$\n
twiss_norm_emit_x -> $Twiss\\ \\epsilon_{n, x}~(\\mathrm{ m } )$\n
twiss_alpha_y -> $Twiss\\ \\alpha_y$\n
twiss_beta_y -> $Twiss\\ \\beta_y~(\\mathrm{ m } )$\n
twiss_gamma_y -> $Twiss\\ \\gamma_y~(\\mathrm{ /m } )$\n
twiss_eta_y -> $Twiss\\ \\eta_y~(\\mathrm{ m } )$\n
twiss_etap_y -> $Twiss\\ \\eta'_y$\n
twiss_emit_y -> $Twiss\\ \\epsilon_{y}~(\\mathrm{ m } )$\n
twiss_norm_emit_y -> $Twiss\\ \\epsilon_{n, y}~(\\mathrm{ m } )$\n
average_current -> $I_{av}~(\\mathrm{ A } )$\n
norm_emit_x -> $\\epsilon_{n, x}~(\\mathrm{ m } )$\n
norm_emit_y -> $\\epsilon_{n, y}~(\\mathrm{ m } )$\n
norm_emit_4d -> $\\epsilon_{4D}~(\\mathrm{ (m)^2 } )$\n
Lz -> $L_z~(\\mathrm{ m*eV/c } )$\n
xp -> $x'~(\\mathrm{ rad } )$\n
yp -> $y'~(\\mathrm{ rad } )$\n
x_bar -> $\\overline{x}~(\\mathrm{ \\sqrt{ m } } )$\n
px_bar -> $\\overline{p_x}~(\\mathrm{ \\sqrt{ m } } )$\n
y_bar -> $\\overline{y}~(\\mathrm{ \\sqrt{ m } } )$\n
py_bar -> $\\overline{p_y}~(\\mathrm{ \\sqrt{ m } } )$\n
"},{"location":"examples/labels/#tex-labels","title":"TeX Labels\u00b6","text":"

TeX labels for most attributes can be retrieved.

"},{"location":"examples/labels/#math-label-with-unit","title":"Math label with unit\u00b6","text":"

mathlabel provides the complete string with optional units

"},{"location":"examples/normalized_coordinates/","title":"Simple Normalized Coordinates","text":"In\u00a0[1]: Copied!
from pmd_beamphysics import ParticleGroup\nfrom pmd_beamphysics.statistics import A_mat_calc, twiss_calc, normalized_particle_coordinate\nimport numpy as np\n\n\nimport matplotlib.pyplot as plt\nimport matplotlib\n%matplotlib inline\n%config InlineBackend.figure_format = 'retina'\nmatplotlib.rcParams['figure.figsize'] = (4,4)\n
from pmd_beamphysics import ParticleGroup from pmd_beamphysics.statistics import A_mat_calc, twiss_calc, normalized_particle_coordinate import numpy as np import matplotlib.pyplot as plt import matplotlib %matplotlib inline %config InlineBackend.figure_format = 'retina' matplotlib.rcParams['figure.figsize'] = (4,4) In\u00a0[2]: Copied!
help(A_mat_calc)\n
help(A_mat_calc)
Help on function A_mat_calc in module pmd_beamphysics.statistics:\n\nA_mat_calc(beta, alpha, inverse=False)\n    Returns the 1D normal form matrix from twiss parameters beta and alpha\n    \n        A =   sqrt(beta)         0 \n             -alpha/sqrt(beta)   1/sqrt(beta) \n    \n    If inverse, the inverse will be returned:\n    \n        A^-1 =  1/sqrt(beta)     0 \n                alpha/sqrt(beta) sqrt(beta)     \n    \n    This corresponds to the linear normal form decomposition:\n    \n        M = A . Rot(theta) . A^-1\n    \n    with a clockwise rotation matrix:\n    \n        Rot(theta) =  cos(theta) sin(theta)\n                     -sin(theta) cos(theta)\n    \n    In the Bmad manual, G_q (Bmad) = A (here) in the Linear Optics chapter.\n    \n    A^-1 can be used to form normalized coordinates: \n        x_bar, px_bar   = A^-1 . (x, px)\n\n

Make phase space circle. This will represent some normalized coordinates:

In\u00a0[3]: Copied!
theta = np.linspace(0, np.pi*2, 100)\nzvec0 = np.array([np.cos(theta), np.sin(theta)])\nplt.scatter(*zvec0)\n
theta = np.linspace(0, np.pi*2, 100) zvec0 = np.array([np.cos(theta), np.sin(theta)]) plt.scatter(*zvec0) Out[3]:
<matplotlib.collections.PathCollection at 0x7f9690b69670>

Make a 'beam' in 'lab coordinates':

In\u00a0[4]: Copied!
MYMAT = np.array([[10, 0],[-3, 5]])\nzvec = np.matmul(MYMAT , zvec0)\nplt.scatter(*zvec)\n
MYMAT = np.array([[10, 0],[-3, 5]]) zvec = np.matmul(MYMAT , zvec0) plt.scatter(*zvec) Out[4]:
<matplotlib.collections.PathCollection at 0x7f9687adf370>

With a beam, $\\alpha$ and $\\beta$ can be determined from moments of the covariance matrix.

In\u00a0[5]: Copied!
help(twiss_calc)\n
help(twiss_calc)
Help on function twiss_calc in module pmd_beamphysics.statistics:\n\ntwiss_calc(sigma_mat2)\n    Calculate Twiss parameters from the 2D sigma matrix (covariance matrix):\n    sigma_mat = <x,x>   <x, p>\n                <p, x>  <p, p>\n    \n    This is a simple calculation. Makes no assumptions about units. \n    \n    alpha = -<x, p>/emit\n    beta  =  <x, x>/emit\n    gamma =  <p, p>/emit\n    emit = det(sigma_mat)\n\n

Calculate a sigma matrix, get the determinant:

In\u00a0[6]: Copied!
sigma_mat2 = np.cov(*zvec)\nnp.linalg.det(sigma_mat2)\n
sigma_mat2 = np.cov(*zvec) np.linalg.det(sigma_mat2) Out[6]:
637.5000000000003

Get some twiss:

In\u00a0[7]: Copied!
twiss = twiss_calc(sigma_mat2)\ntwiss\n
twiss = twiss_calc(sigma_mat2) twiss Out[7]:
{'alpha': 0.6059702963017245,\n 'beta': 2.0199009876724157,\n 'gamma': 0.6768648603788545,\n 'emit': 25.248762345905202}

Analyzing matrices:

In\u00a0[8]: Copied!
A = A_mat_calc(twiss['beta'], twiss['alpha'])\nA_inv = A_mat_calc(twiss['beta'], twiss['alpha'], inverse=True)\n
A = A_mat_calc(twiss['beta'], twiss['alpha']) A_inv = A_mat_calc(twiss['beta'], twiss['alpha'], inverse=True)

A_inv turns this back into a circle:

In\u00a0[9]: Copied!
zvec2 = np.matmul(A_inv, zvec)\nplt.scatter(*zvec2)\n
zvec2 = np.matmul(A_inv, zvec) plt.scatter(*zvec2) Out[9]:
<matplotlib.collections.PathCollection at 0x7f9687a56370>
In\u00a0[10]: Copied!
twiss_calc(np.cov(*zvec2))\n
twiss_calc(np.cov(*zvec2)) Out[10]:
{'alpha': 1.4672219335410167e-16,\n 'beta': 1.0,\n 'gamma': 1.0000000000000002,\n 'emit': 25.24876234590519}
In\u00a0[11]: Copied!
matplotlib.rcParams['figure.figsize'] = (13,8) # Reset plot\n
matplotlib.rcParams['figure.figsize'] = (13,8) # Reset plot In\u00a0[12]: Copied!
help(normalized_particle_coordinate)\n
help(normalized_particle_coordinate)
Help on function normalized_particle_coordinate in module pmd_beamphysics.statistics:\n\nnormalized_particle_coordinate(particle_group, key, twiss=None, mass_normalize=True)\n    Returns a single normalized coordinate array from a ParticleGroup\n    \n    Position or momentum is determined by the key. \n    If the key starts with 'p', it is a momentum, else it is a position,\n    and the\n    \n    Intended use is for key to be one of:\n        x, px, y py\n        \n    and the corresponding normalized coordinates are named with suffix _bar, i.e.:\n        x_bar, px_bar, y_bar, py_bar\n    \n    If mass_normalize (default=True), the momentum will be divided by the mass, so that the units are sqrt(m).\n    \n    These are related to action-angle coordinates\n        J: amplitude\n        phi: phase\n        \n        x_bar =  sqrt(2 J) cos(phi)\n        px_bar = sqrt(2 J) sin(phi)\n        \n    So therefore:\n        J = (x_bar^2 + px_bar^2)/2\n        phi = arctan(px_bar/x_bar)\n    and:        \n        <J> = norm_emit_x\n     \n     Note that the center may need to be subtracted in this case.\n\n

Get some example particles, with a typical transverse phase space plot:

In\u00a0[13]: Copied!
P = ParticleGroup('data/bmad_particles2.h5')\nP.plot('x', 'px')\n
P = ParticleGroup('data/bmad_particles2.h5') P.plot('x', 'px')

If no twiss is given, then the analyzing matrix is computed from the beam itself:

In\u00a0[14]: Copied!
normalized_particle_coordinate(P, 'x', twiss=None)\n
normalized_particle_coordinate(P, 'x', twiss=None) Out[14]:
array([-4.83384095e-04,  9.99855846e-04,  7.35820860e-05, ...,\n       -7.48265408e-05,  4.77803205e-05, -4.18053319e-04])

This is equivelent:

In\u00a0[15]: Copied!
normalized_particle_coordinate(P, 'x', twiss=twiss_calc(P.cov('x', 'px')), mass_normalize=False)/np.sqrt(P.mass)\n
normalized_particle_coordinate(P, 'x', twiss=twiss_calc(P.cov('x', 'px')), mass_normalize=False)/np.sqrt(P.mass) Out[15]:
array([-4.83384095e-04,  9.99855846e-04,  7.35820860e-05, ...,\n       -7.48265408e-05,  4.77803205e-05, -4.18053319e-04])

And is given as a property:

In\u00a0[16]: Copied!
P.x_bar\n
P.x_bar Out[16]:
array([-4.83384095e-04,  9.99855846e-04,  7.35820860e-05, ...,\n       -7.48265408e-05,  4.77803205e-05, -4.18053319e-04])

The amplitude is defined as:

In\u00a0[17]: Copied!
(P.x_bar**2 + P.px_bar**2)/2\n
(P.x_bar**2 + P.px_bar**2)/2 Out[17]:
array([1.16831464e-07, 5.85751290e-07, 3.26876598e-07, ...,\n       3.25916449e-07, 2.21097254e-07, 2.73364174e-07])

This is also given as a property:

In\u00a0[18]: Copied!
P.Jx\n
P.Jx Out[18]:
array([1.16831464e-07, 5.85751290e-07, 3.26876598e-07, ...,\n       3.25916449e-07, 2.21097254e-07, 2.73364174e-07])

Note the mass normalization is the same:

In\u00a0[19]: Copied!
P.Jx.mean(), P['mean_Jx'], P['norm_emit_x']\n
P.Jx.mean(), P['mean_Jx'], P['norm_emit_x'] Out[19]:
(4.883790126887025e-07, 4.883790126887027e-07, 4.881047612307434e-07)

This is now nice and roundish:

In\u00a0[20]: Copied!
P.plot('x_bar', 'px_bar')\n
P.plot('x_bar', 'px_bar')

Jy also works. This gives some sense of where the emittance is larger.

In\u00a0[21]: Copied!
P.plot('t', 'Jy')\n
P.plot('t', 'Jy')

Sort by Jx:

In\u00a0[22]: Copied!
P = P[np.argsort(P.Jx)]\n
P = P[np.argsort(P.Jx)]

Now particles are ordered:

In\u00a0[23]: Copied!
plt.plot(P.Jx)\n
plt.plot(P.Jx) Out[23]:
[<matplotlib.lines.Line2D at 0x7f968742f4c0>]

This can be used to calculate the 95% emittance:

In\u00a0[24]: Copied!
P[0:int(0.95*len(P))]['norm_emit_x']\n
P[0:int(0.95*len(P))]['norm_emit_x'] Out[24]:
3.7399940158442355e-07
In\u00a0[25]: Copied!
def twiss_match(x, p, beta0=1, alpha0=0, beta1=1, alpha1=0):\n    \"\"\"\n    Simple Twiss matching. \n    \n    Takes positions x and momenta p, and transforms them according to \n    initial Twiss parameters:\n        beta0, alpha0 \n    into final  Twiss parameters:\n        beta1, alpha1\n        \n    This is simply the matrix ransformation:    \n        xnew  = (   sqrt(beta1/beta0)                  0                 ) . ( x )\n        pnew    (  (alpha0-alpha1)/sqrt(beta0*beta1)   sqrt(beta0/beta1) )   ( p ) \n        \n\n    Returns new x, p\n    \n    \"\"\"\n    m11 = np.sqrt(beta1/beta0)\n    m21 = (alpha0-alpha1)/np.sqrt(beta0*beta1)\n    \n    xnew = x * m11\n    pnew = x * m21 + p / m11\n    \n    return xnew, pnew\n
def twiss_match(x, p, beta0=1, alpha0=0, beta1=1, alpha1=0): \"\"\" Simple Twiss matching. Takes positions x and momenta p, and transforms them according to initial Twiss parameters: beta0, alpha0 into final Twiss parameters: beta1, alpha1 This is simply the matrix ransformation: xnew = ( sqrt(beta1/beta0) 0 ) . ( x ) pnew ( (alpha0-alpha1)/sqrt(beta0*beta1) sqrt(beta0/beta1) ) ( p ) Returns new x, p \"\"\" m11 = np.sqrt(beta1/beta0) m21 = (alpha0-alpha1)/np.sqrt(beta0*beta1) xnew = x * m11 pnew = x * m21 + p / m11 return xnew, pnew

Get some Twiss:

In\u00a0[26]: Copied!
T0 = twiss_calc(P.cov('x', 'xp'))\nT0\n
T0 = twiss_calc(P.cov('x', 'xp')) T0 Out[26]:
{'alpha': -0.7756418199427733,\n 'beta': 9.761411505651415,\n 'gamma': 0.16407670467707158,\n 'emit': 3.127519925999214e-11}

Make a copy and maniplulate:

In\u00a0[27]: Copied!
P2 = P.copy()\nP2.x, P2.px = twiss_match(P.x, P.px/P['mean_p'], beta0=T0['beta'], alpha0=T0['alpha'], beta1=9, alpha1=-2)\nP2.px *= P['mean_p']\n
P2 = P.copy() P2.x, P2.px = twiss_match(P.x, P.px/P['mean_p'], beta0=T0['beta'], alpha0=T0['alpha'], beta1=9, alpha1=-2) P2.px *= P['mean_p'] In\u00a0[28]: Copied!
twiss_calc(P2.cov('x', 'xp'))\n
twiss_calc(P2.cov('x', 'xp')) Out[28]:
{'alpha': -1.9995993293102663,\n 'beta': 9.00102737307016,\n 'gamma': 0.5553141070021174,\n 'emit': 3.12716295233219e-11}

This is a dedicated routine:

In\u00a0[29]: Copied!
def matched_particles(particle_group, beta=None, alpha=None, plane='x', p0c=None, inplace=False):\n    \"\"\"\n    Perfoms simple Twiss 'matching' by applying a linear transformation to\n        x, px if plane == 'x', or x, py if plane == 'y'\n    \n    Returns a new ParticleGroup\n    \n    If inplace, a copy will not be made, and changes will be done in place. \n    \n    \"\"\"\n    \n    assert plane in ('x', 'y'), f'Invalid plane: {plane}'\n    \n    if inplace:\n        P = particle_group\n    else:\n        P = particle_group.copy()\n    \n    if not p0c:\n        p0c = P['mean_p']\n    \n\n    # Use Bmad-style coordinates.\n    # Get plane. \n    if plane == 'x':\n        x = P.x\n        p = P.px/p0c\n    else:\n        x = P.y\n        p = P.py/p0c\n        \n    # Get current Twiss\n    tx = twiss_calc(np.cov(x, p, aweights=P.weight))\n    \n    # If not specified, just fill in the current value.\n    if alpha is None:\n        alpha = tx['alpha']\n    if beta is None:\n        beta = tx['beta']\n    \n    # New coordinates\n    xnew, pnew = twiss_match(x, p, beta0=tx['beta'], alpha0=tx['alpha'], beta1=beta, alpha1=alpha)\n    \n    # Set\n    if plane == 'x':\n        P.x = xnew\n        P.px = pnew*p0c\n    else:\n        P.y = xnew\n        P.py = pnew*p0c\n        \n    return P\n    \n    \n# Check    \nP3 = matched_particles(P, beta=None, alpha=-4, plane='y')\nP.twiss(plane='y'), P3.twiss(plane='y')\n
def matched_particles(particle_group, beta=None, alpha=None, plane='x', p0c=None, inplace=False): \"\"\" Perfoms simple Twiss 'matching' by applying a linear transformation to x, px if plane == 'x', or x, py if plane == 'y' Returns a new ParticleGroup If inplace, a copy will not be made, and changes will be done in place. \"\"\" assert plane in ('x', 'y'), f'Invalid plane: {plane}' if inplace: P = particle_group else: P = particle_group.copy() if not p0c: p0c = P['mean_p'] # Use Bmad-style coordinates. # Get plane. if plane == 'x': x = P.x p = P.px/p0c else: x = P.y p = P.py/p0c # Get current Twiss tx = twiss_calc(np.cov(x, p, aweights=P.weight)) # If not specified, just fill in the current value. if alpha is None: alpha = tx['alpha'] if beta is None: beta = tx['beta'] # New coordinates xnew, pnew = twiss_match(x, p, beta0=tx['beta'], alpha0=tx['alpha'], beta1=beta, alpha1=alpha) # Set if plane == 'x': P.x = xnew P.px = pnew*p0c else: P.y = xnew P.py = pnew*p0c return P # Check P3 = matched_particles(P, beta=None, alpha=-4, plane='y') P.twiss(plane='y'), P3.twiss(plane='y') Out[29]:
({'alpha_y': 0.9058122605337396,\n  'beta_y': 14.683316714787708,\n  'gamma_y': 0.12398396674913406,\n  'emit_y': 3.214301173354259e-11,\n  'eta_y': -0.0001221701145704683,\n  'etap_y': -3.1414468851691344e-07,\n  'norm_emit_y': 5.016420878150227e-07},\n {'alpha_y': -3.9999679865267384,\n  'beta_y': 14.683316715010363,\n  'gamma_y': 1.1577591237176261,\n  'emit_y': 3.214301173290241e-11,\n  'eta_y': -0.00012217012301264445,\n  'etap_y': -4.113188244396527e-05,\n  'norm_emit_y': 5.016420878133589e-07})

These functions are in statistics:

In\u00a0[30]: Copied!
from pmd_beamphysics.statistics import twiss_match, matched_particles\n
from pmd_beamphysics.statistics import twiss_match, matched_particles"},{"location":"examples/normalized_coordinates/#simple-normalized-coordinates","title":"Simple Normalized Coordinates\u00b6","text":"

1D normalized coordinates originate from the normal form decomposition, where the transfer matrix that propagates phase space coordinates $(x, p)$ is decomposed as

$M = A \\cdot R(\\theta) \\cdot A^{-1}$

And the matrix $A$ can be parameterized as

A = $\\begin{pmatrix}\\sqrt{\\beta} & 0\\\\-\\alpha/\\sqrt{\\beta} & 1/\\sqrt{\\beta}\\end{pmatrix}$

"},{"location":"examples/normalized_coordinates/#twiss-parameters","title":"Twiss parameters\u00b6","text":"

Effective Twiss parameters can be calculated from the second order moments of the particles.

This does not change the phase space area.

"},{"location":"examples/normalized_coordinates/#x_bar-px_bar-jx-etc","title":"x_bar, px_bar, Jx, etc.\u00b6","text":"

These are essentially action-angle coordinates, calculated by using the an analyzing twiss dict

"},{"location":"examples/normalized_coordinates/#simple-matching","title":"Simple 'matching'\u00b6","text":"

Often a beam needs to be 'matched' for tracking in some program.

This is a 'faked' tranformation that ultimately would need to be realized by a focusing system.

"},{"location":"examples/particle_examples/","title":"openPMD beamphysics examples","text":"In\u00a0[1]: Copied!
# Nicer plotting\nimport matplotlib\nimport matplotlib.pyplot as plt\n%config InlineBackend.figure_format = 'retina'\nmatplotlib.rcParams['figure.figsize'] = (8,4)\n
# Nicer plotting import matplotlib import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' matplotlib.rcParams['figure.figsize'] = (8,4) In\u00a0[2]: Copied!
from pmd_beamphysics import ParticleGroup\n
from pmd_beamphysics import ParticleGroup In\u00a0[3]: Copied!
P = ParticleGroup( 'data/bmad_particles2.h5')\nP\n
P = ParticleGroup( 'data/bmad_particles2.h5') P Out[3]:
<ParticleGroup with 100000 particles at 0x7fb40c3bf100>
In\u00a0[4]: Copied!
P.energy\n
P.energy Out[4]:
array([8.00032916e+09, 7.97408124e+09, 7.97338447e+09, ...,\n       7.97531701e+09, 7.97163591e+09, 7.97170403e+09])
In\u00a0[5]: Copied!
P['mean_energy'], P.units('mean_energy')\n
P['mean_energy'], P.units('mean_energy') Out[5]:
(7974939710.08345, pmd_unit('eV', 1.602176634e-19, (2, 1, -2, 0, 0, 0, 0)))
In\u00a0[6]: Copied!
P.where(P.x < P['mean_x'])\n
P.where(P.x < P['mean_x']) Out[6]:
<ParticleGroup with 50082 particles at 0x7fb454ebfc40>
In\u00a0[7]: Copied!
a = P.plot('x', 'px', figsize=(8,8))\n
a = P.plot('x', 'px', figsize=(8,8)) In\u00a0[8]: Copied!
P.write_elegant('elegant_particles.txt', verbose=True)\n
P.write_elegant('elegant_particles.txt', verbose=True)
writing 100000 particles to elegant_particles.txt\n

x positions, in meters

In\u00a0[9]: Copied!
P.x\n
P.x Out[9]:
array([-1.20890504e-05,  2.50055966e-05,  1.84022924e-06, ...,\n       -1.87135206e-06,  1.19494768e-06, -1.04551798e-05])

relativistic gamma, calculated on the fly

In\u00a0[10]: Copied!
P.gamma\n
P.gamma Out[10]:
array([15656.25362205, 15604.88772858, 15603.52416601, ...,\n       15607.30607107, 15600.10233296, 15600.23563914])

Both are allowed

In\u00a0[11]: Copied!
len(P), P['n_particle']\n
len(P), P['n_particle'] Out[11]:
(100000, 100000)

Statistics on any of these. Note that these properly use the .weight array.

In\u00a0[12]: Copied!
P.avg('gamma'), P.std('p')\n
P.avg('gamma'), P.std('p') Out[12]:
(15606.56770446094, 7440511.955100455)

Covariance matrix of any list of keys

In\u00a0[13]: Copied!
P.cov('x', 'px', 'y', 'kinetic_energy')\n
P.cov('x', 'px', 'y', 'kinetic_energy') Out[13]:
array([[ 3.05290090e-10,  1.93582323e-01,  2.14462846e-12,\n        -3.94841065e+00],\n       [ 1.93582323e-01,  3.26525376e+08, -2.44058325e-05,\n        -5.36815277e+08],\n       [ 2.14462846e-12, -2.44058325e-05,  4.71979014e-10,\n        -8.48100957e-01],\n       [-3.94841065e+00, -5.36815277e+08, -8.48100957e-01,\n         5.53617715e+13]])

These can all be accessed with brackets. sigma_ and mean_ are also allowed

In\u00a0[14]: Copied!
P['sigma_x'], P['sigma_energy'], P['min_y'], P['norm_emit_x'], P['norm_emit_4d']\n
P['sigma_x'], P['sigma_energy'], P['min_y'], P['norm_emit_x'], P['norm_emit_4d'] Out[14]:
(1.7472465109340715e-05,\n 7440511.939853717,\n -0.00017677380499644412,\n 4.881047612307434e-07,\n 2.4484888474798633e-13)

Covariance has a special syntax, items separated by __

In\u00a0[15]: Copied!
P['cov_x__kinetic_energy']\n
P['cov_x__kinetic_energy'] Out[15]:
-3.9484106461190627

n-dimensional histogram. This is a wrapper for numpy.histogramdd

In\u00a0[16]: Copied!
H, edges = P.histogramdd('t', 'delta_pz', bins=(5,10))\nH.shape, edges\n
H, edges = P.histogramdd('t', 'delta_pz', bins=(5,10)) H.shape, edges Out[16]:
((5, 10),\n [array([5.16387938e-06, 5.16387943e-06, 5.16387948e-06, 5.16387953e-06,\n         5.16387958e-06, 5.16387963e-06]),\n  array([-24476455.61834908, -17729298.92490654, -10982142.231464  ,\n          -4234985.53802147,   2512171.15542107,   9259327.8488636 ,\n          16006484.54230614,  22753641.23574867,  29500797.92919121,\n          36247954.62263375,  42995111.31607628])])
In\u00a0[17]: Copied!
ss = P.slice_statistics('norm_emit_x')\nss.keys()\n
ss = P.slice_statistics('norm_emit_x') ss.keys() Out[17]:
dict_keys(['charge', 'mean_t', 'norm_emit_x', 'ptp_t', 'current'])

Multiple keys can also be accepted:

In\u00a0[18]: Copied!
ss = P.slice_statistics('norm_emit_x', 'norm_emit_y', 'twiss')\nss.keys()\n
ss = P.slice_statistics('norm_emit_x', 'norm_emit_y', 'twiss') ss.keys() Out[18]:
dict_keys(['charge', 'mean_t', 'ptp_t', 'norm_emit_y', 'twiss', 'norm_emit_x', 'twiss_alpha_x', 'twiss_beta_x', 'twiss_gamma_x', 'twiss_emit_x', 'twiss_eta_x', 'twiss_etap_x', 'twiss_norm_emit_x', 'twiss_alpha_y', 'twiss_beta_y', 'twiss_gamma_y', 'twiss_emit_y', 'twiss_eta_y', 'twiss_etap_y', 'twiss_norm_emit_y', 'current'])

Note that for a slice key X, the method will also calculate mean_X, ptp_X, as charge so that a density calculated from these. In the special case of X=t, the density will be labeled as current according to common convention.

In\u00a0[19]: Copied!
P.twiss('x')\n
P.twiss('x') Out[19]:
{'alpha_x': -0.7764646310859605,\n 'beta_x': 9.758458404204259,\n 'gamma_x': 0.16425722762079686,\n 'emit_x': 3.1255806600595395e-11,\n 'eta_x': -0.0005687740085942673,\n 'etap_x': -9.69649743612097e-06,\n 'norm_emit_x': 4.877958608683612e-07}

95% emittance calculation, x and y

In\u00a0[20]: Copied!
P.twiss('xy', fraction=0.95)\n
P.twiss('xy', fraction=0.95) Out[20]:
{'alpha_x': -0.765323995145385,\n 'beta_x': 9.233496626510156,\n 'gamma_x': 0.1717356795249758,\n 'emit_x': 2.3954681527227138e-11,\n 'eta_x': -0.0004717155629444416,\n 'etap_x': -1.6006449526750024e-05,\n 'norm_emit_x': 3.738408555668476e-07,\n 'alpha_y': 0.9776071851091841,\n 'beta_y': 14.39339077533324,\n 'gamma_y': 0.13587596132863441,\n 'emit_y': 2.342927040318514e-11,\n 'eta_y': -4.3316354305934096e-05,\n 'etap_y': -6.000905618300805e-07,\n 'norm_emit_y': 3.656364435837357e-07}

This makes new particles:

In\u00a0[21]: Copied!
P2 = P.twiss_match(beta=30, alpha=-3, plane = 'x')\nP2.twiss('x')\n
P2 = P.twiss_match(beta=30, alpha=-3, plane = 'x') P2.twiss('x') Out[21]:
{'alpha_x': -2.9996935644621994,\n 'beta_x': 29.99130803172276,\n 'gamma_x': 0.3333686370097817,\n 'emit_x': 3.1255806601163326e-11,\n 'eta_x': -0.000997118623912468,\n 'etap_x': -7.944656701598246e-05,\n 'norm_emit_x': 4.877958608785158e-07}
In\u00a0[22]: Copied!
P.resample()\n
P.resample() Out[22]:
<ParticleGroup with 100000 particles at 0x7fb40bcc9dc0>

With n > 0, particles will be subsampled. Note that this also works for differently weighed particles.

In\u00a0[23]: Copied!
P.resample(1000)\n
P.resample(1000) Out[23]:
<ParticleGroup with 1000 particles at 0x7fb40bcde850>
In\u00a0[24]: Copied!
P.resample(1000).plot('x', 'px', bins=100)\n
P.resample(1000).plot('x', 'px', bins=100) In\u00a0[25]: Copied!
P.units('x'), P.units('energy'), P.units('norm_emit_x'), P.units('cov_x__kinetic_energy'), P.units('norm_emit_4d')\n
P.units('x'), P.units('energy'), P.units('norm_emit_x'), P.units('cov_x__kinetic_energy'), P.units('norm_emit_4d') Out[25]:
(pmd_unit('m', 1, (1, 0, 0, 0, 0, 0, 0)),\n pmd_unit('eV', 1.602176634e-19, (2, 1, -2, 0, 0, 0, 0)),\n pmd_unit('m', 1, (1, 0, 0, 0, 0, 0, 0)),\n pmd_unit('m*eV', 1.602176634e-19, (3, 1, -2, 0, 0, 0, 0)),\n pmd_unit('(m)^2', 1, (2, 0, 0, 0, 0, 0, 0)))
In\u00a0[26]: Copied!
P.units('mean_energy')\n
P.units('mean_energy') Out[26]:
pmd_unit('eV', 1.602176634e-19, (2, 1, -2, 0, 0, 0, 0))
In\u00a0[27]: Copied!
str(P.units('cov_x__kinetic_energy'))\n
str(P.units('cov_x__kinetic_energy')) Out[27]:
'm*eV'
In\u00a0[28]: Copied!
P.std('z'), P.std('t')\n
P.std('z'), P.std('t') Out[28]:
(0.0, 2.4466662184814374e-14)

Get the central time:

In\u00a0[29]: Copied!
t0 = P.avg('t')\nt0\n
t0 = P.avg('t') t0 Out[29]:
5.163879459127423e-06

Drift all particles to this time. This operates in-place:

In\u00a0[30]: Copied!
P.drift_to_t(t0)\n
P.drift_to_t(t0)

Now these are at different z, and the same t:

In\u00a0[31]: Copied!
P.std('z'), P.avg('t'), set(P.t)\n
P.std('z'), P.avg('t'), set(P.t) Out[31]:
(7.334920780350132e-06, 5.163879459127425e-06, {5.163879459127423e-06})
In\u00a0[32]: Copied!
P.status[0:10] = 0\nP.status, P.n_alive, P.n_dead\n
P.status[0:10] = 0 P.status, P.n_alive, P.n_dead Out[32]:
(array([0, 0, 0, ..., 1, 1, 1], dtype=int32), 99990, 10)

There is a .where convenience routine to make selections easier:

In\u00a0[33]: Copied!
P0 = P.where(P.status==0)\nP1 = P.where(P.status==1)\nlen(P0), P0.charge, P1.charge\n
P0 = P.where(P.status==0) P1 = P.where(P.status==1) len(P0), P0.charge, P1.charge Out[33]:
(10, 2.4999999999999994e-14, 2.4997499999999996e-10)

Copy is a deep copy:

In\u00a0[34]: Copied!
P2 = P1.copy()\n
P2 = P1.copy()

Charge can also be set. This will re-scale the weight array:

In\u00a0[35]: Copied!
P2.charge = 9.8765e-12\nP1.weight[0:2], P2.weight[0:2], P2.charge\n
P2.charge = 9.8765e-12 P1.weight[0:2], P2.weight[0:2], P2.charge Out[35]:
(array([2.5e-15, 2.5e-15]),\n array([9.87748775e-17, 9.87748775e-17]),\n 9.876499999999997e-12)

Some codes provide ids for particles. If not, you can assign an id.

In\u00a0[36]: Copied!
'id' in P2\n
'id' in P2 Out[36]:
False

This will assign an id if none exists.

In\u00a0[37]: Copied!
P2.id, 'id' in P2\n
P2.id, 'id' in P2 Out[37]:
(array([    1,     2,     3, ..., 99988, 99989, 99990]), True)
In\u00a0[38]: Copied!
import h5py\nimport numpy as np\n
import h5py import numpy as np In\u00a0[39]: Copied!
newh5file = 'particles.h5'\n\nwith h5py.File(newh5file, 'w') as h5:\n    P.write(h5)\n    \nwith h5py.File(newh5file, 'r') as h5:\n    P2 = ParticleGroup(h5)\n
newh5file = 'particles.h5' with h5py.File(newh5file, 'w') as h5: P.write(h5) with h5py.File(newh5file, 'r') as h5: P2 = ParticleGroup(h5)

Check if all are the same:

In\u00a0[40]: Copied!
for key in ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', 'id']:\n    same = np.all(P[key] == P2[key])\n    print(key, same)\n
for key in ['x', 'px', 'y', 'py', 'z', 'pz', 't', 'status', 'weight', 'id']: same = np.all(P[key] == P2[key]) print(key, same)
x True\npx True\ny True\npy True\nz True\npz True\nt True\nstatus True\nweight True\nid True\n

This does the same check:

In\u00a0[41]: Copied!
P2 == P\n
P2 == P Out[41]:
True

Write Astra-style particles

In\u00a0[42]: Copied!
P.write_astra('astra.dat')\n
P.write_astra('astra.dat') In\u00a0[43]: Copied!
!head astra.dat\n
!head astra.dat
  5.358867254236e-07  -2.266596025469e-08   2.743173452837e-13   5.432293116193e+02   1.634894200076e+01   7.974939693676e+09   5.163879459127e+03   0.000000000000e+00    1   -1\r\n -1.208904511904e-05   2.743402818288e-05  -5.473095269153e-06  -7.699432808273e+03   1.073320266862e+04   2.538945179648e+07  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  2.500557812617e-05   1.840484451196e-06  -6.071970122807e-06   2.432464253407e+04  -2.207080331882e+03  -8.584659148073e+05  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  1.840224855502e-06   2.319774542484e-05  -1.983837488548e-06   1.761897150333e+04  -4.269379756219e+03  -1.555244938371e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  1.282953228685e-05   2.807375273984e-06   7.759268653990e-06   1.898440718152e+04  -5.303751910566e+03  -2.614389107333e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  3.362374691900e-06   4.796982704111e-06  -1.006752695161e-06   1.012222041635e+04   1.266876546973e+04  -1.818436067077e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n -1.441736363766e-05   7.420644576948e-06   1.697647381418e-06  -8.598453241915e+03  -9.693787540264e+02  -4.437182519667e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n -1.715615894267e-05  -2.489925517264e-05   8.689767207313e-06  -1.817734573652e+04   1.917720905016e+04  -4.674564930124e+05  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  5.700269218746e-06   4.764024833770e-05   2.167342605243e-05   8.662840216364e+03  -7.175141639811e+03  -3.156898311773e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n  9.426699454873e-07  -5.785285707147e-06   6.353982932653e-06  -1.498628620111e+04  -1.222058411806e+03  -3.802046580825e+06  -1.818989403546e-12   2.500000000000e-06    1   -1\r\n

Optionally, a string can be given:

In\u00a0[44]: Copied!
P.write('particles.h5')\n
P.write('particles.h5') In\u00a0[45]: Copied!
P.plot('x')\n
P.plot('x') In\u00a0[46]: Copied!
P.slice_plot('norm_emit_x', 'norm_emit_y', ylim=(0, 1e-6))\n
P.slice_plot('norm_emit_x', 'norm_emit_y', ylim=(0, 1e-6)) In\u00a0[47]: Copied!
P.plot('z', 'x')\n
P.plot('z', 'x')

Any other key that returbs an arrat can be sliced on

In\u00a0[48]: Copied!
P.slice_plot('sigma_x', slice_key = 'Jx')\n
P.slice_plot('sigma_x', slice_key = 'Jx') In\u00a0[49]: Copied!
P.plot('x', 'px')\n
P.plot('x', 'px')

Optionally the figure object can be returned, and the plot further modified.

In\u00a0[50]: Copied!
fig = P.plot('x', return_figure=True)\nax = fig.axes[0]\nax.set_title('Density Plot')\nax.set_xlim(-50, 50)\n
fig = P.plot('x', return_figure=True) ax = fig.axes[0] ax.set_title('Density Plot') ax.set_xlim(-50, 50) Out[50]:
(-50.0, 50.0)
In\u00a0[51]: Copied!
import copy\n\nfig, ax = plt.subplots()\nax.set_aspect('equal')\nxkey = 'x'\nykey = 'y'\ndatx = P[xkey]\ndaty = P[ykey]\nax.set_xlabel(f'{xkey} ({P.units(xkey)})')\nax.set_ylabel(f'{ykey} ({P.units(ykey)})')\n\ncmap = copy.copy(plt.get_cmap('viridis'))\ncmap.set_under('white')\nax.hexbin(datx, daty, gridsize=40, cmap=cmap, vmin=1e-15)\n
import copy fig, ax = plt.subplots() ax.set_aspect('equal') xkey = 'x' ykey = 'y' datx = P[xkey] daty = P[ykey] ax.set_xlabel(f'{xkey} ({P.units(xkey)})') ax.set_ylabel(f'{ykey} ({P.units(ykey)})') cmap = copy.copy(plt.get_cmap('viridis')) cmap.set_under('white') ax.hexbin(datx, daty, gridsize=40, cmap=cmap, vmin=1e-15) Out[51]:
<matplotlib.collections.PolyCollection at 0x7fb400a25d00>
In\u00a0[52]: Copied!
P.plot('delta_z', 'delta_p', figsize=(8,6))\n
P.plot('delta_z', 'delta_p', figsize=(8,6)) In\u00a0[53]: Copied!
H, edges = P.histogramdd('delta_z', 'delta_p', bins=(150, 150))\nextent = [edges[0].min(),edges[0].max(),edges[1].min(),edges[1].max() ]\n\nplt.imshow(H.T, origin='lower', extent = extent, aspect='auto', vmin=1e-15, cmap=cmap)\n
H, edges = P.histogramdd('delta_z', 'delta_p', bins=(150, 150)) extent = [edges[0].min(),edges[0].max(),edges[1].min(),edges[1].max() ] plt.imshow(H.T, origin='lower', extent = extent, aspect='auto', vmin=1e-15, cmap=cmap) Out[53]:
<matplotlib.image.AxesImage at 0x7fb40c09e8b0>
In\u00a0[54]: Copied!
from pmd_beamphysics import particle_paths\nfrom pmd_beamphysics.readers import all_components, component_str\n\nH5FILE = 'data/astra_particles.h5'\nh5 = h5py.File(H5FILE, 'r')\n
from pmd_beamphysics import particle_paths from pmd_beamphysics.readers import all_components, component_str H5FILE = 'data/astra_particles.h5' h5 = h5py.File(H5FILE, 'r')

Get the valid paths

In\u00a0[55]: Copied!
ppaths = particle_paths(h5)\nppaths\n
ppaths = particle_paths(h5) ppaths Out[55]:
['/screen/0/./', '/screen/1/./']

Search for all valid components in a single path

In\u00a0[56]: Copied!
ph5 = h5[ppaths[0]]\nall_components(ph5 )\n
ph5 = h5[ppaths[0]] all_components(ph5 ) Out[56]:
['momentum/x',\n 'momentum/y',\n 'momentum/z',\n 'momentumOffset/z',\n 'particleStatus',\n 'position/x',\n 'position/y',\n 'position/z',\n 'positionOffset/z',\n 'time',\n 'timeOffset',\n 'weight']

Get some info

In\u00a0[57]: Copied!
for component in all_components(ph5):\n    info = component_str(ph5, component)\n    print(info)\n
for component in all_components(ph5): info = component_str(ph5, component) print(info)
momentum/x [998 items] is a momentum with units: kg*m/s\nmomentum/y [998 items] is a momentum with units: kg*m/s\nmomentum/z [998 items] is a momentum with units: kg*m/s\nmomentumOffset/z [constant 4.660805218675275e-22 with shape 998] is a momentum with units: kg*m/s\nparticleStatus [998 items]\nposition/x [998 items] is a length with units: m\nposition/y [998 items] is a length with units: m\nposition/z [998 items] is a length with units: m\npositionOffset/z [constant 0.50013 with shape 998] is a length with units: m\ntime [998 items] is a time with units: s\ntimeOffset [constant 2.0826e-09 with shape 998] is a time with units: s\nweight [998 items] is a charge with units: C\n
In\u00a0[58]: Copied!
import os\n\nos.remove('astra.dat')\nos.remove(newh5file)\nos.remove('elegant_particles.txt')\n
import os os.remove('astra.dat') os.remove(newh5file) os.remove('elegant_particles.txt')"},{"location":"examples/particle_examples/#openpmd-beamphysics-examples","title":"openPMD beamphysics examples\u00b6","text":""},{"location":"examples/particle_examples/#basic-usage","title":"Basic Usage\u00b6","text":""},{"location":"examples/particle_examples/#particlegroup-class","title":"ParticleGroup class\u00b6","text":""},{"location":"examples/particle_examples/#basic-statistics","title":"Basic Statistics\u00b6","text":""},{"location":"examples/particle_examples/#slice-statistics","title":"Slice statistics\u00b6","text":"

ParticleGroup can be sliced along one dimension into chunks of an equal number of particles. Here are the routines to create the raw data.

"},{"location":"examples/particle_examples/#advanced-statisics","title":"Advanced statisics\u00b6","text":"

Twiss and Dispersion can be calculated.

These are the projected Twiss parameters.

TODO: normal mode twiss.

"},{"location":"examples/particle_examples/#resampling","title":"Resampling\u00b6","text":"

Particles can be resampled to either scramble the ordering of the particle arrays or subsample.

With no argument or n=0, the same number of particles will be returned:

"},{"location":"examples/particle_examples/#units","title":"Units\u00b6","text":"

Units can be retrieved from any computable quantitiy. These are returned as a pmd_unit type.

"},{"location":"examples/particle_examples/#z-vs-t","title":"z vs t\u00b6","text":"

These particles are from Bmad, at the same z and different times

"},{"location":"examples/particle_examples/#status-weight-id-copy","title":"status, weight, id, copy\u00b6","text":"

status == 1 is alive, otherwise dead. Set the first ten particles to a different status.

n_alive, n_dead count these

"},{"location":"examples/particle_examples/#writing","title":"Writing\u00b6","text":""},{"location":"examples/particle_examples/#plot","title":"Plot\u00b6","text":"

Some plotting is included for convenience. See plot_examples.ipynb for better plotting.

"},{"location":"examples/particle_examples/#1d-density-plot","title":"1D density plot\u00b6","text":""},{"location":"examples/particle_examples/#slice-statistic-plot","title":"Slice statistic plot\u00b6","text":""},{"location":"examples/particle_examples/#2d-density-plot","title":"2D density plot\u00b6","text":""},{"location":"examples/particle_examples/#manual-plotting","title":"Manual plotting\u00b6","text":""},{"location":"examples/particle_examples/#manual-binning-and-plotting","title":"Manual binning and plotting\u00b6","text":""},{"location":"examples/particle_examples/#multiple-particlegroup-in-an-hdf5-file","title":"Multiple ParticleGroup in an HDF5 file\u00b6","text":"

This example has two particlegroups. This also shows how to examine the components, without loading the full data.

"},{"location":"examples/particle_examples/#cleanup","title":"Cleanup\u00b6","text":""},{"location":"examples/plot_examples/","title":"Plot examples","text":"In\u00a0[1]: Copied!
from pmd_beamphysics import ParticleGroup, particle_paths\nimport matplotlib\nimport matplotlib.pyplot as plt\n\n%config InlineBackend.figure_format = 'retina'\nmatplotlib.rcParams['figure.figsize'] = (8,6)\n\nfrom h5py import File\nimport os\n
from pmd_beamphysics import ParticleGroup, particle_paths import matplotlib import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' matplotlib.rcParams['figure.figsize'] = (8,6) from h5py import File import os In\u00a0[2]: Copied!
# Open a file, fine the particle paths from the root attributes\n# Pick one:\nH5FILE = 'data/bmad_particles2.h5'\n#H5FILE = 'data/distgen_particles.h5'\n#H5FILE = 'data/astra_particles.h5'\n\n# Load\nh5 = File(H5FILE, 'r')\nppaths = particle_paths(h5)\nph5 = h5[ppaths[0]]\n\nP = ParticleGroup(ph5)\nppaths\n
# Open a file, fine the particle paths from the root attributes # Pick one: H5FILE = 'data/bmad_particles2.h5' #H5FILE = 'data/distgen_particles.h5' #H5FILE = 'data/astra_particles.h5' # Load h5 = File(H5FILE, 'r') ppaths = particle_paths(h5) ph5 = h5[ppaths[0]] P = ParticleGroup(ph5) ppaths Out[2]:
['/data/00001/particles/']
In\u00a0[3]: Copied!
P.plot('t')\n
P.plot('t') In\u00a0[4]: Copied!
P.t = P.t - P['mean_t']\nP.slice_plot('sigma_x', 'sigma_y', xlim=(None, 80e-15), ylim=(0, 30e-6))\n
P.t = P.t - P['mean_t'] P.slice_plot('sigma_x', 'sigma_y', xlim=(None, 80e-15), ylim=(0, 30e-6)) In\u00a0[5]: Copied!
P.plot('t', 'energy', xlim = (-200e-15, 200e-15), ylim = (7.9e9, 8.1e9))\n
P.plot('t', 'energy', xlim = (-200e-15, 200e-15), ylim = (7.9e9, 8.1e9)) In\u00a0[6]: Copied!
from pmd_beamphysics.plot import density_and_slice_plot\n
from pmd_beamphysics.plot import density_and_slice_plot In\u00a0[7]: Copied!
P.species\n
P.species Out[7]:
'electron'
In\u00a0[8]: Copied!
density_and_slice_plot(P, 't', 'energy', stat_keys = ['sigma_x', 'sigma_y'], n_slice = 200, bins=200)\n
density_and_slice_plot(P, 't', 'energy', stat_keys = ['sigma_x', 'sigma_y'], n_slice = 200, bins=200)"},{"location":"examples/plot_examples/#plot-examples","title":"Plot examples\u00b6","text":""},{"location":"examples/plot_examples/#density-plots","title":"Density plots\u00b6","text":""},{"location":"examples/plot_examples/#slice-statistics-plots","title":"Slice statistics Plots\u00b6","text":""},{"location":"examples/plot_examples/#marginal-plots","title":"Marginal plots\u00b6","text":""},{"location":"examples/plot_examples/#combined-density-and-slice-plot","title":"Combined density and slice plot\u00b6","text":""},{"location":"examples/read_examples/","title":"Read examples","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n
# Useful for debugging %load_ext autoreload %autoreload 2 In\u00a0[2]: Copied!
from pmd_beamphysics import ParticleGroup\nfrom h5py import File\n
from pmd_beamphysics import ParticleGroup from h5py import File In\u00a0[3]: Copied!
from pmd_beamphysics.interfaces.elegant import elegant_h5_to_data\n
from pmd_beamphysics.interfaces.elegant import elegant_h5_to_data

This will convert to a data dict

In\u00a0[4]: Copied!
data = elegant_h5_to_data('data/elegant_raw.h5')\ndata\n
data = elegant_h5_to_data('data/elegant_raw.h5') data Out[4]:
{'x': array([-1.12390181e-04, -7.20268439e-05, -1.14543953e-04, ...,\n         1.05737263e-04, -7.24786314e-05,  5.78264247e-05]),\n 'y': array([-1.23229455e-04, -1.08820261e-04, -9.27723036e-05, ...,\n         8.55980575e-05,  8.58175111e-05,  9.07622257e-05]),\n 'z': array([0, 0, 0, ..., 0, 0, 0]),\n 'px': array([ -3083.62425792,   2225.50915788,   2725.71118129, ...,\n        -15364.10017428,  15821.42533783,  -4733.37386475]),\n 'py': array([ 83545.5978262 ,  83389.57148069,  68275.02698773, ...,\n        -70430.7889418 , -70600.14619666, -69547.74375481]),\n 'pz': array([8.00396787e+09, 8.00007258e+09, 8.00139350e+09, ...,\n        7.99685994e+09, 7.99660958e+09, 7.99818064e+09]),\n 't': array([5.11804566e-06, 5.11804568e-06, 5.11804567e-06, ...,\n        5.11804569e-06, 5.11804569e-06, 5.11804569e-06]),\n 'status': array([1, 1, 1, ..., 1, 1, 1]),\n 'species': 'electron',\n 'weight': array([2.e-17, 2.e-17, 2.e-17, ..., 2.e-17, 2.e-17, 2.e-17]),\n 'id': array([    1,     2,     3, ..., 49998, 49999, 50000], dtype=int32)}

Create ParticleGroup and plot:

In\u00a0[5]: Copied!
P=ParticleGroup(data=data)\nP.plot('delta_t', 'delta_pz')\n
P=ParticleGroup(data=data) P.plot('delta_t', 'delta_pz') In\u00a0[6]: Copied!
from pmd_beamphysics.interfaces.genesis import genesis4_par_to_data\n
from pmd_beamphysics.interfaces.genesis import genesis4_par_to_data In\u00a0[7]: Copied!
P = ParticleGroup(data=genesis4_par_to_data('data/genesis4.par.h5'))\nP\n
P = ParticleGroup(data=genesis4_par_to_data('data/genesis4.par.h5')) P Out[7]:
<ParticleGroup with 14336 particles at 0x7fdb41b34820>
In\u00a0[8]: Copied!
P.plot('z', 'pz')\n
P.plot('z', 'pz')"},{"location":"examples/read_examples/#read-examples","title":"Read examples\u00b6","text":""},{"location":"examples/read_examples/#elegant-hdf5-format","title":"elegant hdf5 format\u00b6","text":""},{"location":"examples/read_examples/#genesis4-hdf5-format","title":"Genesis4 HDF5 format\u00b6","text":""},{"location":"examples/units/","title":"Units","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n
# Useful for debugging %load_ext autoreload %autoreload 2 In\u00a0[2]: Copied!
from pmd_beamphysics import particle_paths\nfrom pmd_beamphysics.units import pmd_unit, dimension_name, sqrt_unit, known_unit, multiply_units\nfrom h5py import File\nimport numpy as np\n
from pmd_beamphysics import particle_paths from pmd_beamphysics.units import pmd_unit, dimension_name, sqrt_unit, known_unit, multiply_units from h5py import File import numpy as np

This is the basic class:

In\u00a0[3]: Copied!
?pmd_unit\n
?pmd_unit

Get a known units. These can be multiplied and divided:

In\u00a0[4]: Copied!
u1 = known_unit['J']\nu2 = known_unit['m']\nu1, u2, u1/u2, u1*u2\n
u1 = known_unit['J'] u2 = known_unit['m'] u1, u2, u1/u2, u1*u2 Out[4]:
(pmd_unit('J', 1, (2, 1, -2, 0, 0, 0, 0)),\n pmd_unit('m', 1, (1, 0, 0, 0, 0, 0, 0)),\n pmd_unit('J/m', 1.0, (1, 1, -2, 0, 0, 0, 0)),\n pmd_unit('J*m', 1, (3, 1, -2, 0, 0, 0, 0)))

Special function for sqrt:

In\u00a0[5]: Copied!
sqrt_unit(u1)\n
sqrt_unit(u1) Out[5]:
pmd_unit('\\sqrt{ J }', 1.0, (1.0, 0.5, -1.0, 0.0, 0.0, 0.0, 0.0))
In\u00a0[6]: Copied!
# Pick one:\n#H5FILE = 'data/bmad_particles.h5'\nH5FILE = 'data/distgen_particles.h5'\n#H5FILE = 'data/astra_particles.h5'\nh5 = File(H5FILE, 'r')\n\nppaths = particle_paths(h5)\nprint(ppaths)\n
# Pick one: #H5FILE = 'data/bmad_particles.h5' H5FILE = 'data/distgen_particles.h5' #H5FILE = 'data/astra_particles.h5' h5 = File(H5FILE, 'r') ppaths = particle_paths(h5) print(ppaths)
['//']\n

This points to a single particle group:

In\u00a0[7]: Copied!
ph5 = h5[ppaths[0]]\nlist(ph5)\n
ph5 = h5[ppaths[0]] list(ph5) Out[7]:
['momentum', 'particleStatus', 'position', 'time', 'weight']

Each component should have a dimension and a conversion factor to SI:

In\u00a0[8]: Copied!
d = dict(ph5['momentum/x'].attrs)\nd\n
d = dict(ph5['momentum/x'].attrs) d Out[8]:
{'unitDimension': array([ 1,  1, -1,  0,  0,  0,  0]),\n 'unitSI': 5.344285992678308e-28,\n 'unitSymbol': 'eV/c'}
In\u00a0[9]: Copied!
tuple(d['unitDimension'])\n
tuple(d['unitDimension']) Out[9]:
(1, 1, -1, 0, 0, 0, 0)

This will extract the name of this dimension:

In\u00a0[10]: Copied!
dimension_name(d['unitDimension'])\n
dimension_name(d['unitDimension']) Out[10]:
'momentum'
In\u00a0[11]: Copied!
from pmd_beamphysics.units import nice_array\n
from pmd_beamphysics.units import nice_array

This will scale the array, and return the appropriate SI prefix:

In\u00a0[12]: Copied!
x = 1e-4\nunit = 'm'\nnice_array(x)\n
x = 1e-4 unit = 'm' nice_array(x) Out[12]:
(100.00000000000001, 1e-06, '\u00b5')
In\u00a0[13]: Copied!
nice_array([-0.01, 0.01])\n
nice_array([-0.01, 0.01]) Out[13]:
(array([-10.,  10.]), 0.001, 'm')
In\u00a0[14]: Copied!
from pmd_beamphysics.units import nice_scale_prefix\n
from pmd_beamphysics.units import nice_scale_prefix In\u00a0[15]: Copied!
nice_scale_prefix(0.009)\n
nice_scale_prefix(0.009) Out[15]:
(0.001, 'm')
In\u00a0[16]: Copied!
try:\n    u1/1\nexcept:\n    print('you cannot do this')\n
try: u1/1 except: print('you cannot do this')
you cannot do this\n
"},{"location":"examples/units/#units","title":"Units\u00b6","text":"

This package provides unit conversion tools

"},{"location":"examples/units/#openpmd-hdf5-units","title":"openPMD HDF5 units\u00b6","text":"

Open a file, find the particle paths from the root attributes

"},{"location":"examples/units/#nice-arrays","title":"Nice arrays\u00b6","text":""},{"location":"examples/units/#limitations","title":"Limitations\u00b6","text":"

This is a simple class for use with this package. So even simple things like the example below will fail.

For more advanced units, use a package like Pint: https://pint.readthedocs.io/

"},{"location":"examples/write_examples/","title":"Write examples","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n
# Useful for debugging %load_ext autoreload %autoreload 2 In\u00a0[2]: Copied!
from pmd_beamphysics import ParticleGroup, particle_paths, pmd_init\nfrom h5py import File\nimport os\n
from pmd_beamphysics import ParticleGroup, particle_paths, pmd_init from h5py import File import os In\u00a0[3]: Copied!
# Pick one:\n\n#H5File = 'data/bmad_particles2.h5'\nH5FILE = 'data/distgen_particles.h5'\n#H5FILE = 'data/astra_particles.h5'\n\nP = ParticleGroup(H5FILE)\n
# Pick one: #H5File = 'data/bmad_particles2.h5' H5FILE = 'data/distgen_particles.h5' #H5FILE = 'data/astra_particles.h5' P = ParticleGroup(H5FILE)

The regular write routine writes in a proper openPMD format

In\u00a0[4]: Copied!
P.write('openpmd_particles.h5')\n
P.write('openpmd_particles.h5')

An open h5 hande can also be used, but it needs to be properly initialized

In\u00a0[5]: Copied!
with File('openpmd_particles.h5', 'w') as h5:\n    pmd_init(h5, basePath='/', particlesPath='/' )\n    P.write(h5)\n
with File('openpmd_particles.h5', 'w') as h5: pmd_init(h5, basePath='/', particlesPath='/' ) P.write(h5)

This can be read in by another ParticleGroup

In\u00a0[6]: Copied!
P2 = ParticleGroup('openpmd_particles.h5')\n
P2 = ParticleGroup('openpmd_particles.h5')

Check that they are the same:

In\u00a0[7]: Copied!
P2 == P\n
P2 == P Out[7]:
True
In\u00a0[8]: Copied!
P.write_astra('astra_particles.txt')\n
P.write_astra('astra_particles.txt') In\u00a0[9]: Copied!
!head astra_particles.txt\n
!head astra_particles.txt
 -1.289814080985e-05   1.712192978919e-05   0.000000000000e+00  -9.245284702413e-01  -3.316650265292e+00   2.210558337183e+02   1.819664001274e-05   0.000000000000e+00    1    5\r\n -1.184861337727e-03  -2.101371437059e-03   0.000000000000e+00  -3.047044656318e+02  -3.039342419008e+02  -1.005645891013e+02  -9.745607002167e-04   1.000000000000e-06    1    5\r\n -5.181307340245e-04  -2.178353405029e-03   0.000000000000e+00   5.525456229648e+02   2.416723877028e+02  -6.554342847563e+01   1.280843753434e-03   1.000000000000e-06    1    5\r\n -1.773501610902e-03   2.864979597813e-03   0.000000000000e+00  -2.226004747820e+02   9.450238076106e+00  -1.055085411491e+02   3.835366744569e-04   1.000000000000e-06    1    5\r\n  1.686555815999e-03  -2.401048305081e-04   0.000000000000e+00  -1.891692499417e+02   4.859547751754e+01   3.339263495319e+02   1.902998338336e-03   1.000000000000e-06    1    5\r\n -7.779454935491e-04  -6.800063114796e-04   0.000000000000e+00   6.716138938638e+01  -2.064173000222e+02  -1.405963302134e+02   1.779005092730e-04   1.000000000000e-06    1    5\r\n -2.593702199590e-03  -2.301030494125e-03   0.000000000000e+00  -1.455653402031e+01   2.074634953296e+02  -1.397453142110e+02   1.368567098305e-03   1.000000000000e-06    1    5\r\n  1.997801509161e-03   2.648416193086e-03   0.000000000000e+00  -2.124047726665e+00  -6.792723569247e+01   6.931081537770e+01   2.616497721112e-04   1.000000000000e-06    1    5\r\n  1.999741847023e-03  -6.945690451493e-04   0.000000000000e+00  -9.991142908925e+01  -9.189412445573e+01   2.259539675809e+02  -8.681109991004e-04   1.000000000000e-06    1    5\r\n -7.033822974359e-04  -5.677746866954e-04   0.000000000000e+00   7.520962264129e+02   3.125940718167e+02  -1.451032665210e+02   4.315191990002e-04   1.000000000000e-06    1    5\r\n

Check the readback:

In\u00a0[10]: Copied!
from pmd_beamphysics.interfaces.astra import parse_astra_phase_file\nimport numpy as np\nP1 = ParticleGroup(data=parse_astra_phase_file('astra_particles.txt'))\nfor k in ['x', 'px', 'y', 'py', 'z', 'pz']:\n    assert np.allclose(P[k], P1[k])\n
from pmd_beamphysics.interfaces.astra import parse_astra_phase_file import numpy as np P1 = ParticleGroup(data=parse_astra_phase_file('astra_particles.txt')) for k in ['x', 'px', 'y', 'py', 'z', 'pz']: assert np.allclose(P[k], P1[k]) In\u00a0[11]: Copied!
P.write_bmad('bmad_particles.txt')\n
P.write_bmad('bmad_particles.txt') In\u00a0[12]: Copied!
!head bmad_particles.txt\n
!head bmad_particles.txt
!ASCII::3\r\n0 ! ix_ele, not used\r\n1 ! n_bunch\r\n10000 ! n_particle\r\nBEGIN_BUNCH\r\nelectron \r\n1.0000000000000003e-11  ! bunch_charge\r\n0 ! z_center\r\n0 ! t_center\r\n -1.184861337727e-03  -3.047044656318e+02  -2.101371437059e-03  -3.039342419008e+02  -9.563640602039e-13   1.204912446170e+02   1.000000000000e-15  1\r\n
In\u00a0[13]: Copied!
P.to_bmad()\n
P.to_bmad() Out[13]:
{'x': array([-0.00118486, -0.00051813, -0.0017735 , ..., -0.00052658,\n         0.00252813,  0.00113815]),\n 'y': array([-0.00210137, -0.00217835,  0.00286498, ..., -0.00161154,\n         0.00160049, -0.0010351 ]),\n 'px': array([-0.69011879,  1.25144906, -0.50416317, ...,  0.60249121,\n         0.505233  ,  0.74928061]),\n 'py': array([-0.68837433,  0.54735875,  0.02140365, ..., -0.07882388,\n         0.29433475, -0.25918357]),\n 'z': array([ 2.55529374e-07, -4.68009162e-07, -5.64739756e-08, ...,\n        -9.69805227e-09,  4.20165276e-07,  2.70278113e-07]),\n 'pz': array([ 0.01222356,  0.41059669, -0.4315584 , ..., -0.3093279 ,\n         0.02463904,  0.08781142]),\n 'charge': array([1.e-15, 1.e-15, 1.e-15, ..., 1.e-15, 1.e-15, 1.e-15]),\n 'species': 'electron',\n 'p0c': 441.52466167250947,\n 'tref': 1.8196640012738955e-14,\n 'state': array([1, 1, 1, ..., 1, 1, 1])}

Check that the conversion preserves information. Note that == uses np.allclose, because there is roundoff error in the conversion.

In\u00a0[14]: Copied!
assert P == P.from_bmad(P.to_bmad())\n
assert P == P.from_bmad(P.to_bmad()) In\u00a0[15]: Copied!
P.write_elegant('elegant_particles.txt', verbose=True)\n
P.write_elegant('elegant_particles.txt', verbose=True)
writing 10000 particles to elegant_particles.txt\n
In\u00a0[16]: Copied!
!head -n 20 elegant_particles.txt\n
!head -n 20 elegant_particles.txt
SDDS1\r\n! \r\n! Created using the openPMD-beamphysics Python package\r\n! https://github.com/ChristopherMayes/openPMD-beamphysics\r\n! species: electron\r\n!\r\n&parameter name=Charge, type=double, units=C, description=\"total charge in Coulombs\" &end\r\n&column name=t,  type=double, units=s, description=\"time in seconds\" &end\r\n&column name=x,  type=double, units=m, description=\"x in meters\" &end\r\n&column name=xp, type=double, description=\"px/pz\" &end\r\n&column name=y,  type=double, units=m, description=\"y in meters\" &end\r\n&column name=yp, type=double, description=\"py/pz\" &end\r\n&column name=p,  type=double, units=\"m$be$nc\", description=\"relativistic gamma*beta\" &end\r\n&data mode=ascii &end\r\n1.0000000000000003e-11\r\n10000\r\n -9.563640602039e-13  -1.184861337727e-03  -2.528851507845e+00  -2.101371437059e-03  -2.522459145201e+00   8.746038816275e-04\r\n  1.299040393447e-12  -5.181307340245e-04   3.553064606663e+00  -2.178353405029e-03   1.554039289185e+00   1.218815083118e-03\r\n  4.017333144697e-13  -1.773501610902e-03  -1.926488019169e+00   2.864979597813e-03   8.178675472159e-02   4.911575370556e-04\r\n  1.921194978349e-12   1.686555815999e-03  -3.408564376497e-01  -2.401048305081e-04   8.756222989528e-02   1.151365621035e-03\r\n
In\u00a0[17]: Copied!
P.write_genesis2_beam_file('genesis2.beam', n_slice=50, verbose=True)\n
P.write_genesis2_beam_file('genesis2.beam', n_slice=50, verbose=True)
Beam written: genesis2.beam\n
In\u00a0[18]: Copied!
!head genesis2.beam\n
!head genesis2.beam
? VERSION=1.0\r\n? SIZE=50\r\n? COLUMNS TPOS CURPEAK GAMMA0 DELGAM EMITX EMITY RXBEAM RYBEAM XBEAM YBEAM PXBEAM PYBEAM ALPHAX ALPHAY\r\n-1.96236040e-12 2.75359318e+00 1.00000042e+00 3.87022121e-07 1.19639887e-06 9.26536586e-07 2.04948080e-03 1.83621527e-03 -2.06231144e-04 5.85738124e-05 1.38209180e-05 4.17553934e-05 -1.01973407e-02 7.54079832e-04\r\n-1.88646733e-12 2.46723994e+00 1.00000043e+00 3.39783124e-07 1.04698472e-06 1.03470799e-06 2.05064183e-03 1.92139881e-03 -2.53107201e-05 -1.13678787e-04 -2.85190907e-05 -2.90105675e-05 -2.32400675e-02 -2.55583997e-03\r\n-1.80734757e-12 2.61898031e+00 1.00000043e+00 3.65257967e-07 9.78086023e-07 1.03038032e-06 1.97065976e-03 1.88550433e-03 -7.56576638e-05 1.77044304e-04 -4.48907172e-05 6.98821769e-05 5.38708456e-03 4.70844009e-03\r\n-1.72583745e-12 2.30361285e+00 1.00000043e+00 3.43343193e-07 9.42099457e-07 1.07137205e-06 1.90156644e-03 1.92054974e-03 4.82559113e-05 -6.43105052e-05 1.66595205e-05 1.33986604e-05 9.16890850e-02 8.20635086e-03\r\n-1.64047654e-12 2.48779835e+00 1.00000043e+00 3.76740497e-07 1.12901501e-06 1.01496464e-06 2.07085748e-03 1.84284581e-03 -2.63780641e-04 2.16425678e-05 9.07228824e-06 -2.65046022e-05 1.91567484e-03 3.63234933e-03\r\n-1.55820367e-12 2.33273410e+00 1.00000041e+00 3.38657590e-07 9.79796547e-07 1.04648255e-06 1.84685458e-03 1.99189484e-03 3.36520774e-05 1.02123362e-04 3.01402301e-06 6.13937003e-05 -7.84493116e-02 2.84788841e-02\r\n-1.47800120e-12 2.62363489e+00 1.00000044e+00 3.34570582e-07 1.12580877e-06 1.08628803e-06 1.97095399e-03 2.02149469e-03 -1.94415715e-04 -1.05666747e-04 -1.70675102e-05 -5.61809635e-06 -1.81522208e-01 -5.08353286e-03\r\n
In\u00a0[19]: Copied!
input_str = P.write_genesis4_beam('genesis4_beam.h5', n_slice=123, verbose=True, return_input_str=True)\n
input_str = P.write_genesis4_beam('genesis4_beam.h5', n_slice=123, verbose=True, return_input_str=True)
Genesis4 beam file written: genesis4_beam.h5\n

This string is optionally returned for use in the main Genesis4 input file:

In\u00a0[20]: Copied!
print(input_str)\n
print(input_str)
&profile_file\n  label = current\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/current\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = gamma\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/gamma\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = delgam\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/delgam\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = ex\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/ex\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = ey\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/ey\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = xcenter\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/xcenter\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = ycenter\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/ycenter\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = pxcenter\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/pxcenter\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = pycenter\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/pycenter\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = alphax\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/alphax\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = alphay\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/alphay\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = betax\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/betax\n  isTime = T\n  reverse = T\n&end\n&profile_file\n  label = betay\n  xdata = genesis4_beam.h5/t\n  ydata = genesis4_beam.h5/betay\n  isTime = T\n  reverse = T\n&end\n&beam\n  current = @current\n  gamma = @gamma\n  delgam = @delgam\n  ex = @ex\n  ey = @ey\n  xcenter = @xcenter\n  ycenter = @ycenter\n  pxcenter = @pxcenter\n  pycenter = @pycenter\n  alphax = @alphax\n  alphay = @alphay\n  betax = @betax\n  betay = @betay\n&end\n

These are the datasets written:

In\u00a0[21]: Copied!
with File('genesis4_beam.h5', 'r') as h5:\n    for g in h5:\n        print(g, len(h5[g]), h5[g].attrs['unitSymbol'])\n
with File('genesis4_beam.h5', 'r') as h5: for g in h5: print(g, len(h5[g]), h5[g].attrs['unitSymbol'])
alphax 123 \nalphay 123 \nbetax 123 m\nbetay 123 m\ncurrent 123 A\ndelgam 123 \nex 123 m\ney 123 m\ngamma 123 \npxcenter 123 \npycenter 123 \nt 123 s\nxcenter 123 m\nycenter 123 m\n
In\u00a0[22]: Copied!
P.write_genesis4_distribution('genesis4_distribution.h5', verbose=True)\n
P.write_genesis4_distribution('genesis4_distribution.h5', verbose=True)
Datasets x, xp, y, yp, t, p written to: genesis4_distribution.h5\n

This is what is written:

In\u00a0[23]: Copied!
with File('genesis4_distribution.h5', 'r') as h5:\n    for g in h5:\n        print(g, len(h5[g]))\n
with File('genesis4_distribution.h5', 'r') as h5: for g in h5: print(g, len(h5[g]))
p 10000\nt 10000\nx 10000\nxp 10000\ny 10000\nyp 10000\n
In\u00a0[24]: Copied!
P.write_gpt('gpt_particles.txt', verbose=True)\n
P.write_gpt('gpt_particles.txt', verbose=True)
writing 10000 particles to gpt_particles.txt\nASCII particles written. Convert to GDF using: asci2df -o particles.gdf gpt_particles.txt\n
In\u00a0[25]: Copied!
if os.path.exists(os.path.expandvars('$ASCI2GDF_BIN')):\n    P.write_gpt('gpt_particles.gdf', verbose=True, asci2gdf_bin='$ASCI2GDF_BIN')\n
if os.path.exists(os.path.expandvars('$ASCI2GDF_BIN')): P.write_gpt('gpt_particles.gdf', verbose=True, asci2gdf_bin='$ASCI2GDF_BIN') In\u00a0[26]: Copied!
#!head gpt_particles.txt\n
#!head gpt_particles.txt In\u00a0[27]: Copied!
P.drift_to_t(P['mean_t'])\n
P.drift_to_t(P['mean_t'])

This will return settings for Impact-T to use:

In\u00a0[28]: Copied!
P.write_impact('impact_particles.txt')\n
P.write_impact('impact_particles.txt') Out[28]:
{'input_particle_file': 'impact_particles.txt',\n 'Np': 10000,\n 'Tini': 1.8196640012738955e-14,\n 'Flagimg': 0}
In\u00a0[29]: Copied!
!head impact_particles.txt\n
!head impact_particles.txt
10000\r\n-1.185035553808772143e-03 -5.962917646539026830e-04 -2.101545212762252063e-03 -5.947844744118675406e-04 6.889138464881934423e-08 2.357954837617718942e-04\r\n-5.185459410277813361e-04 1.081304810831444467e-03 -2.178535008255722601e-03 4.729410651485857963e-04 -1.168588385748075652e-07 3.043301854978374224e-04\r\n-1.773451522909312962e-03 -4.356182625855454594e-04 2.864977471386669170e-03 1.849365458795189412e-05 -2.599963881922582336e-08 2.261204109504710640e-04\r\n1.686767013783966630e-03 -3.701949875665073173e-04 -2.401590848712557098e-04 9.509897724357652376e-05 -6.196091998406064887e-07 1.086073040365844247e-03\r\n-7.779525032181127363e-04 1.314315604491483489e-04 -6.799847675988795461e-04 -4.039485795854574836e-04 -8.397600117458311948e-09 1.574553206124528761e-04\r\n-2.593690512006350136e-03 -2.848642647956871966e-05 -2.301197068594195913e-03 4.059959327304890142e-04 -6.528501143099030074e-08 1.591207173856017771e-04\r\n1.997801835211931738e-03 -4.156657712632428962e-06 2.648426620219643604e-03 -1.329302842842789139e-04 -4.457257401140207729e-08 5.682333576145901953e-04\r\n1.999690961886589624e-03 -1.955217894072916647e-04 -6.946158470527933476e-04 -1.798323156157682705e-04 2.276631907326255499e-07 8.747763597149907193e-04\r\n-7.035727003852429310e-04 1.471815600429043306e-03 -5.678538239535645561e-04 6.117313388152665482e-04 -1.922838101944327160e-08 1.486354662711140203e-04\r\n
In\u00a0[30]: Copied!
P.drift_to_z()\n
P.drift_to_z() In\u00a0[31]: Copied!
P.write_litrack('litrack.zd', verbose=True)\n
P.write_litrack('litrack.zd', verbose=True)
Using mean_p as the reference momentum: 441.52466167250947 eV/c\nwriting 10000 LiTrack particles to litrack.zd\n
Out[31]:
'litrack.zd'
In\u00a0[32]: Copied!
!head -n 20 litrack.zd\n
!head -n 20 litrack.zd
% LiTrack particles\r\n% \r\n% Created using the openPMD-beamphysics Python package\r\n% https://github.com/ChristopherMayes/openPMD-beamphysics\r\n%\r\n% species: electron\r\n% n_particle: 10000\r\n% total charge: 1.0000000000000003e-11 (C)\r\n% reference momentum p0: 441.52466167250947 (eV/c)\r\n%\r\n% Columns: ct, delta = p/p0 -1\r\n% Units: mm, percent\r\n -2.910140500388e-01   1.222356070583e+00\r\n  3.861082943987e-01   4.105966931910e+01\r\n  1.159491741202e-01  -4.315583986424e+01\r\n  5.750254785583e-01   3.325339997689e+01\r\n  5.234406211768e-02  -4.756793241308e+01\r\n  4.093643740668e-01  -4.942448528425e+01\r\n  8.211012905157e-02  -3.245820052454e+01\r\n -2.559578717432e-01   5.807578013130e+00\r\n
In\u00a0[33]: Copied!
P.write_lucretia('lucretia.mat', ele_name='BEGINNING', t_ref=0, stop_ix=None, verbose=True)\n
P.write_lucretia('lucretia.mat', ele_name='BEGINNING', t_ref=0, stop_ix=None, verbose=True)
writing 10000 particles in the Lucretia format to lucretia.mat\n

Read back:

In\u00a0[34]: Copied!
from pmd_beamphysics.interfaces.lucretia import lucretia_to_data, list_element_names\n\nParticleGroup(data=lucretia_to_data('lucretia.mat', verbose=True))\n
from pmd_beamphysics.interfaces.lucretia import lucretia_to_data, list_element_names ParticleGroup(data=lucretia_to_data('lucretia.mat', verbose=True))
1 elements found in the file!\n10000 particles detected, 0 found dead!\n
Out[34]:
<ParticleGroup with 10000 particles at 0x7fd27409a850>

Helper function to list the available elements:

In\u00a0[35]: Copied!
list_element_names('lucretia.mat')\n
list_element_names('lucretia.mat') Out[35]:
['BEGINNING']
In\u00a0[36]: Copied!
P.drift_to_t()\n\nP.write_opal('opal_injected.txt', dist_type='injected')\n
P.drift_to_t() P.write_opal('opal_injected.txt', dist_type='injected') In\u00a0[37]: Copied!
!head opal_injected.txt\n
!head opal_injected.txt
10000\r\n -1.185024568260e-03  -5.962917646539e-04  -2.101534254983e-03  -5.947844744119e-04   6.454729863911e-08   2.357954837618e-04\r\n -5.185658620175e-04   1.081304810831e-03  -2.178543721298e-03   4.729410651486e-04  -1.224655448770e-07   3.043301854978e-04\r\n -1.773443497464e-03  -4.356182625855e-04   2.864977130676e-03   1.849365458795e-05  -3.016548099423e-08   2.261204109505e-04\r\n  1.686773833925e-03  -3.701949875665e-04  -2.401608368896e-04   9.509897724358e-05  -6.396180367454e-07   1.086073040366e-03\r\n -7.779549245968e-04   1.314315604491e-04  -6.799773256079e-04  -4.039485795855e-04  -1.129841753741e-08   1.574553206125e-04\r\n -2.593689987198e-03  -2.848642647957e-05  -2.301204548304e-03   4.059959327305e-04  -6.821651066751e-08   1.591207173856e-04\r\n  1.997801911791e-03  -4.156657712632e-06   2.648429069209e-03  -1.329302842843e-04  -5.504120158406e-08   5.682333576146e-04\r\n  1.999694564006e-03  -1.955217894073e-04  -6.946125339825e-04  -1.798323156158e-04   2.115470906675e-07   8.747763597150e-04\r\n -7.035998157808e-04   1.471815600429e-03  -5.678650939369e-04   6.117313388153e-04  -2.196670602435e-08   1.486354662711e-04\r\n

Emitted particles must be at the same z:

In\u00a0[38]: Copied!
P.drift_to_z(P['mean_z'])\nP.write_opal('opal_emitted.txt', dist_type='emitted')\n
P.drift_to_z(P['mean_z']) P.write_opal('opal_emitted.txt', dist_type='emitted') In\u00a0[39]: Copied!
!head opal_emitted.txt\n
!head opal_emitted.txt
10000\r\n -1.184838617375e-03  -5.962917646539e-04  -2.101348774139e-03  -5.947844744119e-04  -1.083461177889e-12   2.357954837618e-04\r\n -5.181626563723e-04   1.081304810831e-03  -2.178367367225e-03   4.729410651486e-04   1.200565320731e-12   3.043301854978e-04\r\n -1.773484302458e-03  -4.356182625855e-04   2.864978863003e-03   1.849365458795e-05   2.691980940714e-13   2.261204109505e-04\r\n  1.686558878409e-03  -3.701949875665e-04  -2.401056172069e-04   9.509897724358e-05   1.893601130019e-12   1.086073040366e-03\r\n -7.779529930790e-04   1.314315604491e-04  -6.799832620349e-04  -4.039485795855e-04   5.764311716727e-15   1.574553206125e-04\r\n -2.593700591157e-03  -2.848642647957e-05  -2.301053417928e-03   4.059959327305e-04   1.198422972634e-12   1.591207173856e-04\r\n  1.997801574883e-03  -4.156657712632e-06   2.648418294875e-03  -1.329302842843e-04   2.271058970013e-13   5.682333576146e-04\r\n  1.999743855144e-03  -1.955217894073e-04  -6.945671981683e-04  -1.798323156158e-04  -8.841733180528e-13   8.747763597150e-04\r\n -7.034712631509e-04   1.471815600429e-03  -5.678116635531e-04   6.117313388153e-04   2.480886363183e-13   1.486354662711e-04\r\n
In\u00a0[40]: Copied!
P.write_simion('simion_particles.ion')\n
P.write_simion('simion_particles.ion') In\u00a0[41]: Copied!
for file in [\n    'astra_particles.txt',\n    'bmad_particles.txt',\n    'elegant_particles.txt',\n    'gpt_particles.txt',\n    'impact_particles.txt',\n    'opal_injected.txt',\n    'opal_emitted.txt',\n    'openpmd_particles.h5',\n    'genesis4_beam.h5',\n    'genesis4_distribution.h5',\n    'genesis2.beam',\n    'litrack.zd',\n    'gpt_particles.gdf',\n    'lucretia.mat',\n    'simion_particles.ion'\n    ]:\n    if os.path.exists(file):\n        os.remove(file)\n
for file in [ 'astra_particles.txt', 'bmad_particles.txt', 'elegant_particles.txt', 'gpt_particles.txt', 'impact_particles.txt', 'opal_injected.txt', 'opal_emitted.txt', 'openpmd_particles.h5', 'genesis4_beam.h5', 'genesis4_distribution.h5', 'genesis2.beam', 'litrack.zd', 'gpt_particles.gdf', 'lucretia.mat', 'simion_particles.ion' ]: if os.path.exists(file): os.remove(file)"},{"location":"examples/write_examples/#write-examples","title":"Write examples\u00b6","text":""},{"location":"examples/write_examples/#openpmd","title":"openPMD\u00b6","text":""},{"location":"examples/write_examples/#astra","title":"Astra\u00b6","text":""},{"location":"examples/write_examples/#bmad-ascii","title":"Bmad ASCII\u00b6","text":""},{"location":"examples/write_examples/#bmad-dict","title":"Bmad dict\u00b6","text":""},{"location":"examples/write_examples/#elegant","title":"elegant\u00b6","text":""},{"location":"examples/write_examples/#genesis-13-v2","title":"Genesis 1.3 v2\u00b6","text":""},{"location":"examples/write_examples/#genesis-13-v4","title":"Genesis 1.3 v4\u00b6","text":""},{"location":"examples/write_examples/#beam-file-slice-statistics","title":"beam file (slice statistics)\u00b6","text":""},{"location":"examples/write_examples/#distribution-file-particles","title":"Distribution file (particles)\u00b6","text":""},{"location":"examples/write_examples/#gpt-ascii","title":"GPT ASCII\u00b6","text":""},{"location":"examples/write_examples/#impact-t","title":"Impact-T\u00b6","text":"

Impact-T particles must all be a the same time:

"},{"location":"examples/write_examples/#litrack","title":"LiTrack\u00b6","text":"

LiTrack particles must be at the same z:

"},{"location":"examples/write_examples/#lucretia","title":"Lucretia\u00b6","text":""},{"location":"examples/write_examples/#opal","title":"OPAL\u00b6","text":"

Injected particled must be at the same time:

"},{"location":"examples/write_examples/#simion","title":"SIMION\u00b6","text":"

Write SIMION input files (*.ion)

"},{"location":"examples/write_examples/#cleanup","title":"Cleanup\u00b6","text":""},{"location":"examples/fields/field_conversion/","title":"FieldMesh Conversion","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n\n# Nicer plotting\nimport matplotlib.pyplot as plt\n%config InlineBackend.figure_format = 'retina'\n\nimport numpy as np\n
# Useful for debugging %load_ext autoreload %autoreload 2 # Nicer plotting import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' import numpy as np In\u00a0[2]: Copied!
from pmd_beamphysics import FieldMesh\n
from pmd_beamphysics import FieldMesh In\u00a0[3]: Copied!
FM1 = FieldMesh('../data/rfgun.h5')\nFM1.plot('re_E', aspect='equal', figsize=(12,4))\n
FM1 = FieldMesh('../data/rfgun.h5') FM1.plot('re_E', aspect='equal', figsize=(12,4)) In\u00a0[4]: Copied!
FM3D = FieldMesh.from_ansys_ascii_3d(efile='../data/ansys_rfgun_2856MHz_E.dat',\n                              hfile='../data/ansys_rfgun_2856MHz_H.dat',\n                              frequency=2856e6)\n\n\n\nFM3D\n
FM3D = FieldMesh.from_ansys_ascii_3d(efile='../data/ansys_rfgun_2856MHz_E.dat', hfile='../data/ansys_rfgun_2856MHz_H.dat', frequency=2856e6) FM3D Out[4]:
<FieldMesh with rectangular geometry and (3, 3, 457) shape at 0x7fd1446beee0>

This will convert to 2D cylindrical:

In\u00a0[5]: Copied!
FM2 = FM3D.to_cylindrical()\nFM2\n
FM2 = FM3D.to_cylindrical() FM2 Out[5]:
<FieldMesh with cylindrical geometry and (2, 1, 457) shape at 0x7fd1446bed00>

Spacing is different:

In\u00a0[6]: Copied!
FM1.dr, FM2.dr\n
FM1.dr, FM2.dr Out[6]:
(0.00025, 0.001)

Set a scale to rotate and normalize Ez to be mostly real

In\u00a0[7]: Copied!
E0 = FM2.components['electricField/z'][0,0,0]\nFM2.scale = -np.exp(-1j * np.angle(E0)) / np.abs(E0) # - sign to agree with FM1\n\nFM2.Ez[0,0,0]\n
E0 = FM2.components['electricField/z'][0,0,0] FM2.scale = -np.exp(-1j * np.angle(E0)) / np.abs(E0) # - sign to agree with FM1 FM2.Ez[0,0,0] Out[7]:
(-1-1.890985933883628e-16j)
In\u00a0[8]: Copied!
z1 = FM1.coord_vec('z')\nz2 = FM2.coord_vec('z')\n\n\nfig, ax = plt.subplots()\nax.plot(z1, np.real(FM1.Ez[0,0,:]), label='Superfish')\nax.plot(z2, np.real(FM2.Ez[0,0,:]), label='ANSYS')\nax.set_xlabel(r'$z$ (m)')\nax.set_ylabel(r'$E_z$ (V/m)')\nplt.legend()\n
z1 = FM1.coord_vec('z') z2 = FM2.coord_vec('z') fig, ax = plt.subplots() ax.plot(z1, np.real(FM1.Ez[0,0,:]), label='Superfish') ax.plot(z2, np.real(FM2.Ez[0,0,:]), label='ANSYS') ax.set_xlabel(r'$z$ (m)') ax.set_ylabel(r'$E_z$ (V/m)') plt.legend() Out[8]:
<matplotlib.legend.Legend at 0x7fd144695eb0>
In\u00a0[9]: Copied!
fig, ax = plt.subplots()\nax.plot(z1, np.imag(FM1.Btheta[4,0,:]), label='superfish')\nax.plot(z2, np.imag(FM2.Btheta[1,0,:]), label='ansys')\n\nax.set_title(fr'$r$ = {FM2.dr*1000} mm')\nax.set_xlabel(r'$z$ (m)')\nax.set_ylabel(r'$B_\\theta$ (T)')\nplt.legend()\n
fig, ax = plt.subplots() ax.plot(z1, np.imag(FM1.Btheta[4,0,:]), label='superfish') ax.plot(z2, np.imag(FM2.Btheta[1,0,:]), label='ansys') ax.set_title(fr'$r$ = {FM2.dr*1000} mm') ax.set_xlabel(r'$z$ (m)') ax.set_ylabel(r'$B_\\theta$ (T)') plt.legend() Out[9]:
<matplotlib.legend.Legend at 0x7fd14454feb0>

The magnetic field is out of phase, so use the im_ syntax:

In\u00a0[10]: Copied!
FM2.plot('im_Btheta', aspect='equal', figsize=(12,4))\n
FM2.plot('im_Btheta', aspect='equal', figsize=(12,4))

Max on-axis field:

In\u00a0[11]: Copied!
np.abs(FM2.Ez[0,0,:]).max()\n
np.abs(FM2.Ez[0,0,:]).max() Out[11]:
1.0153992993439902
In\u00a0[12]: Copied!
def check_oscillation(FM, label=''):\n\n    c_light = 299792458.\n    \n    dr = FM.dr\n    omega = FM.frequency*2*np.pi\n    \n    # Check the first off-axis grid points\n    z0 = FM.z\n    Ez0 = np.real(FM.Ez[0,0,:])\n    B1 = -np.imag(FM.Btheta[1,0,:])\n    \n    plt.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$')\n    plt.plot(z0, B1*2/dr *c_light**2/omega, '--', label=r'$-\\frac{r}{2}\\frac{\\omega}{c^2} \\Im\\left(B_\\theta\\right)$')\n    plt.ylabel('field (V/m)')\n    plt.xlabel('z (m)')\n    plt.legend()\n    plt.title(fr'Complex field oscillation{label}')\n
def check_oscillation(FM, label=''): c_light = 299792458. dr = FM.dr omega = FM.frequency*2*np.pi # Check the first off-axis grid points z0 = FM.z Ez0 = np.real(FM.Ez[0,0,:]) B1 = -np.imag(FM.Btheta[1,0,:]) plt.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$') plt.plot(z0, B1*2/dr *c_light**2/omega, '--', label=r'$-\\frac{r}{2}\\frac{\\omega}{c^2} \\Im\\left(B_\\theta\\right)$') plt.ylabel('field (V/m)') plt.xlabel('z (m)') plt.legend() plt.title(fr'Complex field oscillation{label}') In\u00a0[13]: Copied!
check_oscillation(FM1, ', Superfish')\n
check_oscillation(FM1, ', Superfish') In\u00a0[14]: Copied!
check_oscillation(FM2, ', ANSYS')\n
check_oscillation(FM2, ', ANSYS')"},{"location":"examples/fields/field_conversion/#fieldmesh-conversion","title":"FieldMesh Conversion\u00b6","text":""},{"location":"examples/fields/field_conversion/#2d-cylindrically-symmetric-rf-gun-fieldmesh","title":"2D cylindrically symmetric RF gun FieldMesh\u00b6","text":"

This data was originally generated using Superfish.

"},{"location":"examples/fields/field_conversion/#3d-rectangular-field-from-ansys","title":"3D rectangular field from ANSYS\u00b6","text":""},{"location":"examples/fields/field_conversion/#verify-the-oscillation","title":"Verify the oscillation\u00b6","text":"

Complex fields oscillate as $e^{-i\\omega t}$. For TM fields, the spatial components $E_z$ and $B_\\theta$ near the axis

$\\Re E_{z} = -\\frac{r}{2}\\frac{\\omega}{c^2} \\Im B_\\theta$

"},{"location":"examples/fields/field_examples/","title":"FieldMesh Examples","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n\n# Nicer plotting\nimport matplotlib.pyplot as plt\n%config InlineBackend.figure_format = 'retina'\n\nimport numpy as np\n
# Useful for debugging %load_ext autoreload %autoreload 2 # Nicer plotting import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' import numpy as np In\u00a0[2]: Copied!
from pmd_beamphysics import FieldMesh, tools\n
from pmd_beamphysics import FieldMesh, tools In\u00a0[3]: Copied!
FM = FieldMesh('../data/solenoid.h5')\nFM\n
FM = FieldMesh('../data/solenoid.h5') FM Out[3]:
<FieldMesh with cylindrical geometry and (101, 1, 201) shape at 0x7fd5dc32f760>

Built-in plotting:

In\u00a0[4]: Copied!
FM.plot('B', aspect='equal')\n
FM.plot('B', aspect='equal')

On-axis field plotting

In\u00a0[5]: Copied!
FM.plot_onaxis()\n
FM.plot_onaxis() In\u00a0[6]: Copied!
FM.attrs, FM.components.keys()\n
FM.attrs, FM.components.keys() Out[6]:
({'eleAnchorPt': 'beginning',\n  'gridGeometry': 'cylindrical',\n  'axisLabels': array(['r', 'theta', 'z'], dtype='<U5'),\n  'gridLowerBound': array([0, 1, 0]),\n  'gridOriginOffset': array([ 0. ,  0. , -0.1]),\n  'gridSpacing': array([0.001, 0.   , 0.001]),\n  'gridSize': array([101,   1, 201]),\n  'harmonic': 0,\n  'fundamentalFrequency': 0,\n  'RFphase': 0,\n  'fieldScale': 1.0},\n dict_keys(['magneticField/z', 'magneticField/r']))
In\u00a0[7]: Copied!
FM.shape\n
FM.shape Out[7]:
(101, 1, 201)
In\u00a0[8]: Copied!
FM.frequency\n
FM.frequency Out[8]:
0

Coordinate vectors: .r, .theta, .z, etc.

In\u00a0[9]: Copied!
FM.r, FM.dr\n
FM.r, FM.dr Out[9]:
(array([0.   , 0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007, 0.008,\n        0.009, 0.01 , 0.011, 0.012, 0.013, 0.014, 0.015, 0.016, 0.017,\n        0.018, 0.019, 0.02 , 0.021, 0.022, 0.023, 0.024, 0.025, 0.026,\n        0.027, 0.028, 0.029, 0.03 , 0.031, 0.032, 0.033, 0.034, 0.035,\n        0.036, 0.037, 0.038, 0.039, 0.04 , 0.041, 0.042, 0.043, 0.044,\n        0.045, 0.046, 0.047, 0.048, 0.049, 0.05 , 0.051, 0.052, 0.053,\n        0.054, 0.055, 0.056, 0.057, 0.058, 0.059, 0.06 , 0.061, 0.062,\n        0.063, 0.064, 0.065, 0.066, 0.067, 0.068, 0.069, 0.07 , 0.071,\n        0.072, 0.073, 0.074, 0.075, 0.076, 0.077, 0.078, 0.079, 0.08 ,\n        0.081, 0.082, 0.083, 0.084, 0.085, 0.086, 0.087, 0.088, 0.089,\n        0.09 , 0.091, 0.092, 0.093, 0.094, 0.095, 0.096, 0.097, 0.098,\n        0.099, 0.1  ]),\n 0.001)

Grid info

In\u00a0[10]: Copied!
FM.mins, FM.maxs, FM.deltas\n
FM.mins, FM.maxs, FM.deltas Out[10]:
(array([ 0. ,  0. , -0.1]),\n array([0.1, 0. , 0.1]),\n array([0.001, 0.   , 0.001]))

Convenient logicals

In\u00a0[11]: Copied!
FM.is_static, FM.is_pure_magnetic, FM.is_pure_magnetic,  FM.is_pure_electric\n
FM.is_static, FM.is_pure_magnetic, FM.is_pure_magnetic, FM.is_pure_electric Out[11]:
(True, True, True, False)
In\u00a0[12]: Copied!
FM.components\n
FM.components Out[12]:
{'magneticField/z': array([[[ 4.10454985e-03,  4.31040451e-03,  4.52986744e-03, ...,\n           4.67468517e-04,  3.93505841e-04,  3.31380794e-04]],\n \n        [[ 4.10132316e-03,  4.30698128e-03,  4.52613784e-03, ...,\n           4.63910019e-04,  3.90463457e-04,  3.28826095e-04]],\n \n        [[ 4.09178241e-03,  4.29666227e-03,  4.51500745e-03, ...,\n           4.53304832e-04,  3.81497195e-04,  3.21252672e-04]],\n \n        ...,\n \n        [[-8.55276742e-05, -9.25454620e-05, -9.97134392e-05, ...,\n          -1.67910069e-13, -1.66617291e-13, -1.69112101e-13]],\n \n        [[-8.66606075e-05, -9.34605759e-05, -1.00393739e-04, ...,\n          -1.63746446e-13, -1.62385457e-13, -1.63975660e-13]],\n \n        [[-8.76493773e-05, -9.42325632e-05, -1.00947206e-04, ...,\n          -1.59165583e-13, -1.57653026e-13, -1.58633209e-13]]]),\n 'magneticField/r': array([[[ 0.00000000e+00,  0.00000000e+00,  0.00000000e+00, ...,\n           0.00000000e+00,  0.00000000e+00,  0.00000000e+00]],\n \n        [[-9.96833640e-05, -1.06224487e-04, -1.13203127e-04, ...,\n           4.01781064e-05,  3.37384918e-05,  2.82880488e-05]],\n \n        [[-1.99034573e-04, -2.12052909e-04, -2.25955469e-04, ...,\n           7.94909429e-05,  6.67047656e-05,  5.59040132e-05]],\n \n        ...,\n \n        [[-3.28171418e-04, -3.29256623e-04, -3.30141629e-04, ...,\n           5.98175517e-14,  5.84734577e-14,  5.07218297e-14]],\n \n        [[-3.18048731e-04, -3.18985999e-04, -3.19728362e-04, ...,\n           5.74787071e-14,  5.71575012e-14,  5.06326785e-14]],\n \n        [[-3.08270029e-04, -3.09070567e-04, -3.09682173e-04, ...,\n           5.47143752e-14,  5.54052993e-14,  4.98595495e-14]]])}

Convenient access to component data

In\u00a0[13]: Copied!
FM.Bz is FM['magneticField/z']\n
FM.Bz is FM['magneticField/z'] Out[13]:
True

Setting .scale will set the underlying attribute

In\u00a0[14]: Copied!
FM.scale = 2\nFM.attrs['fieldScale'], FM.scale\n
FM.scale = 2 FM.attrs['fieldScale'], FM.scale Out[14]:
(2, 2)

Raw components accessed by their full key

In\u00a0[15]: Copied!
FM['magneticField/z']\n
FM['magneticField/z'] Out[15]:
array([[[ 4.10454985e-03,  4.31040451e-03,  4.52986744e-03, ...,\n          4.67468517e-04,  3.93505841e-04,  3.31380794e-04]],\n\n       [[ 4.10132316e-03,  4.30698128e-03,  4.52613784e-03, ...,\n          4.63910019e-04,  3.90463457e-04,  3.28826095e-04]],\n\n       [[ 4.09178241e-03,  4.29666227e-03,  4.51500745e-03, ...,\n          4.53304832e-04,  3.81497195e-04,  3.21252672e-04]],\n\n       ...,\n\n       [[-8.55276742e-05, -9.25454620e-05, -9.97134392e-05, ...,\n         -1.67910069e-13, -1.66617291e-13, -1.69112101e-13]],\n\n       [[-8.66606075e-05, -9.34605759e-05, -1.00393739e-04, ...,\n         -1.63746446e-13, -1.62385457e-13, -1.63975660e-13]],\n\n       [[-8.76493773e-05, -9.42325632e-05, -1.00947206e-04, ...,\n         -1.59165583e-13, -1.57653026e-13, -1.58633209e-13]]])

Scaled component accessed by shorter keys, e.g.

In\u00a0[16]: Copied!
FM['Bz']\n
FM['Bz'] Out[16]:
array([[[ 8.20909970e-03,  8.62080901e-03,  9.05973488e-03, ...,\n          9.34937033e-04,  7.87011682e-04,  6.62761588e-04]],\n\n       [[ 8.20264631e-03,  8.61396257e-03,  9.05227569e-03, ...,\n          9.27820038e-04,  7.80926913e-04,  6.57652189e-04]],\n\n       [[ 8.18356483e-03,  8.59332454e-03,  9.03001490e-03, ...,\n          9.06609663e-04,  7.62994390e-04,  6.42505344e-04]],\n\n       ...,\n\n       [[-1.71055348e-04, -1.85090924e-04, -1.99426878e-04, ...,\n         -3.35820138e-13, -3.33234581e-13, -3.38224202e-13]],\n\n       [[-1.73321215e-04, -1.86921152e-04, -2.00787478e-04, ...,\n         -3.27492892e-13, -3.24770914e-13, -3.27951319e-13]],\n\n       [[-1.75298755e-04, -1.88465126e-04, -2.01894411e-04, ...,\n         -3.18331166e-13, -3.15306051e-13, -3.17266417e-13]]])
In\u00a0[17]: Copied!
FM['magneticField/z'].max(), FM['Bz'].max()\n
FM['magneticField/z'].max(), FM['Bz'].max() Out[17]:
(2.150106838829148, 4.300213677658296)
In\u00a0[18]: Copied!
FM = FieldMesh('../data/rfgun.h5')\nFM.plot('re_E', aspect='equal', figsize=(12,4))\n
FM = FieldMesh('../data/rfgun.h5') FM.plot('re_E', aspect='equal', figsize=(12,4))

The magnetic field is out of phase, so use the im_ syntax:

In\u00a0[19]: Copied!
FM.plot('im_Btheta', aspect='equal', figsize=(12,4))\n
FM.plot('im_Btheta', aspect='equal', figsize=(12,4))

Max on-axis field:

In\u00a0[20]: Copied!
np.abs(FM.Ez[0,0,:]).max()\n
np.abs(FM.Ez[0,0,:]).max() Out[20]:
1.0
In\u00a0[21]: Copied!
c_light = 299792458.\n\ndr = FM.dr\nomega = FM.frequency*2*np.pi\n\n# Check the first off-axis grid points\nz0 = FM.z\nEz0 = np.real(FM.Ez[0,0,:])\nB1 = -np.imag(FM.Btheta[1,0,:])\n\nplt.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$')\nplt.plot(z0, B1*2/dr *c_light**2/omega, '--', label=r'$-\\frac{r}{2}\\frac{\\omega}{c^2} \\Im\\left(B_\\theta\\right)$')\nplt.ylabel('field (V/m)')\nplt.xlabel('z (m)')\nplt.legend()\nplt.title(r'Complex field oscillation')\n
c_light = 299792458. dr = FM.dr omega = FM.frequency*2*np.pi # Check the first off-axis grid points z0 = FM.z Ez0 = np.real(FM.Ez[0,0,:]) B1 = -np.imag(FM.Btheta[1,0,:]) plt.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$') plt.plot(z0, B1*2/dr *c_light**2/omega, '--', label=r'$-\\frac{r}{2}\\frac{\\omega}{c^2} \\Im\\left(B_\\theta\\right)$') plt.ylabel('field (V/m)') plt.xlabel('z (m)') plt.legend() plt.title(r'Complex field oscillation') Out[21]:
Text(0.5, 1.0, 'Complex field oscillation')
In\u00a0[22]: Copied!
FM.units('Bz')\n
FM.units('Bz') Out[22]:
pmd_unit('T', 1, (0, 1, -2, -1, 0, 0, 0))

This also works:

In\u00a0[23]: Copied!
FM.units('abs_Ez')\n
FM.units('abs_Ez') Out[23]:
pmd_unit('V/m', 1, (1, 1, -3, -1, 0, 0, 0))
In\u00a0[24]: Copied!
FM.write('rfgun2.h5')\n
FM.write('rfgun2.h5')

Read back and make sure the data are the same.

In\u00a0[25]: Copied!
FM2 = FieldMesh('rfgun2.h5')\n\nassert FM == FM2\n
FM2 = FieldMesh('rfgun2.h5') assert FM == FM2

Write to open HDF5 file and test reload:

In\u00a0[26]: Copied!
import h5py\nwith h5py.File('test.h5', 'w') as h5:\n    FM.write(h5, name='myfield')\n    FM2 = FieldMesh(h5=h5['myfield'])\n    assert FM == FM2\n
import h5py with h5py.File('test.h5', 'w') as h5: FM.write(h5, name='myfield') FM2 = FieldMesh(h5=h5['myfield']) assert FM == FM2 In\u00a0[27]: Copied!
FM.write_astra_1d('astra_1d.dat')\n
FM.write_astra_1d('astra_1d.dat')

Another method returns the array data with some annotation

In\u00a0[28]: Copied!
FM.to_astra_1d()\n
FM.to_astra_1d() Out[28]:
{'attrs': {'type': 'astra_1d'},\n 'data': array([[ 0.00000000e+00, -1.00000000e+00],\n        [ 2.50000000e-04, -9.99967412e-01],\n        [ 5.00000000e-04, -9.99865106e-01],\n        ...,\n        [ 1.29500000e-01,  1.45678834e-04],\n        [ 1.29750000e-01,  1.40392476e-04],\n        [ 1.30000000e-01,  1.35399670e-04]])}
In\u00a0[29]: Copied!
idata = FM.to_impact_solrf()\nidata.keys()\n
idata = FM.to_impact_solrf() idata.keys() Out[29]:
dict_keys(['line', 'rfdata', 'ele', 'fmap'])

This is an element that can be used with LUME-Impact

In\u00a0[30]: Copied!
idata['ele']\n
idata['ele'] Out[30]:
{'L': 0.13,\n 'type': 'solrf',\n 'zedge': 0,\n 'rf_field_scale': 1,\n 'rf_frequency': 2855998506.158,\n 'theta0_deg': 0.0,\n 'filename': 'rfdata666',\n 'radius': 0.15,\n 'x_offset': 0,\n 'y_offset': 0,\n 'x_rotation': 0.0,\n 'y_rotation': 0.0,\n 'z_rotation': 0.0,\n 'solenoid_field_scale': 0,\n 'name': 'solrf_666',\n 's': 0.13}

This is a line that would be used

In\u00a0[31]: Copied!
idata['line']\n
idata['line'] Out[31]:
'0.13 0 0 105 0 1 2855998506.158 0.0 666 0.15 0 0 0 0 0 0 /name:solrf_666'

Data that would be written to the rfdata999 file

In\u00a0[32]: Copied!
idata['rfdata']\n
idata['rfdata'] Out[32]:
array([ 5.90000000e+01, -1.30000000e-01,  1.30000000e-01,  2.60000000e-01,\n        2.38794165e-01, -3.04717859e-01, -3.56926831e-17, -7.49524991e-01,\n        7.29753328e-18, -2.59757413e-01, -4.68937899e-17,  1.27535902e-01,\n       -6.30755527e-17,  4.49651550e-02, -1.49234509e-16,  4.67567146e-03,\n       -1.74265243e-16,  3.32025307e-02,  6.08199268e-17, -2.74904837e-03,\n        1.81697659e-16, -1.55710320e-02, -1.45475359e-16,  2.64475111e-03,\n       -1.20376479e-16,  9.51814907e-04,  2.52133666e-16, -2.68547441e-03,\n        4.07323339e-16,  1.50824509e-03,  3.51376926e-16,  7.16574075e-04,\n        1.71392948e-16, -9.25573616e-04, -1.42917735e-16,  2.40084183e-04,\n       -1.19272642e-16,  2.61495768e-04,  1.87867359e-16, -2.41687337e-04,\n       -9.72891282e-18,  6.22859548e-05, -3.09382521e-16,  7.85163760e-05,\n       -2.59194056e-16, -8.56926328e-05, -2.56768407e-16,  2.38418521e-06,\n       -2.89351931e-16,  2.84696849e-05, -1.77696470e-16, -2.10693774e-05,\n       -8.56616099e-18,  4.74764623e-06, -1.26336157e-16,  9.74703896e-06,\n       -1.86248124e-16, -6.17509459e-06, -2.08540055e-16, -1.04844029e-06,\n       -1.86006363e-16,  3.01586958e-06,  1.90371674e-16,  1.00000000e+00,\n       -1.30000000e-01,  1.30000000e-01,  2.60000000e-01,  0.00000000e+00])

This is the fieldmap that makes that data:

In\u00a0[33]: Copied!
fmap = idata['fmap']\nfmap.keys()\n
fmap = idata['fmap'] fmap.keys() Out[33]:
dict_keys(['info', 'data', 'field'])

Additional info:

In\u00a0[34]: Copied!
fmap['info']\n
fmap['info'] Out[34]:
{'format': 'solrf',\n 'Ez_scale': 1.0,\n 'Ez_err': 7.68588630296298e-08,\n 'Bz_scale': 0.0,\n 'Bz_err': 0,\n 'zmin': -0.13,\n 'zmax': 0.13}
In\u00a0[35]: Copied!
from pmd_beamphysics.interfaces.impact import fourier_field_reconsruction\nL = z0.ptp()\nzlist = np.linspace(0, L, len(Ez0))\nfcoefs = fmap['field']['Ez']['fourier_coefficients']\nreconstructed_Ez0 = np.array([fourier_field_reconsruction(z, fcoefs, z0=-L, zlen = 2*L) for z in zlist])\n\nfig, ax = plt.subplots()\nax2 = ax.twinx()\nax.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$', color='black')\nax.plot(zlist, reconstructed_Ez0, '--',  label='reconstructed', color='red', )\nax2.plot(zlist, abs(reconstructed_Ez0/Ez0 -1), '--',  label='reconstructed', color='black' )\nax2.set_ylabel('relative error')\nax2.set_yscale('log')\nax.set_ylabel('field (V/m)')\nax.set_xlabel('z (m)')\nplt.legend()\n
from pmd_beamphysics.interfaces.impact import fourier_field_reconsruction L = z0.ptp() zlist = np.linspace(0, L, len(Ez0)) fcoefs = fmap['field']['Ez']['fourier_coefficients'] reconstructed_Ez0 = np.array([fourier_field_reconsruction(z, fcoefs, z0=-L, zlen = 2*L) for z in zlist]) fig, ax = plt.subplots() ax2 = ax.twinx() ax.plot(z0, Ez0, label=r'$\\Re \\left( E_z\\right)$', color='black') ax.plot(zlist, reconstructed_Ez0, '--', label='reconstructed', color='red', ) ax2.plot(zlist, abs(reconstructed_Ez0/Ez0 -1), '--', label='reconstructed', color='black' ) ax2.set_ylabel('relative error') ax2.set_yscale('log') ax.set_ylabel('field (V/m)') ax.set_xlabel('z (m)') plt.legend() Out[35]:
<matplotlib.legend.Legend at 0x7fd58822c910>

This function can also be used to study the reconstruction error as a function of the number of coefficients:

In\u00a0[36]: Copied!
ncoefs = np.arange(10, FM2.shape[2]//2)\nerrs = np.array([FM2.to_impact_solrf(n_coef = n, zmirror=True)['fmap']['info']['Ez_err'] for n in ncoefs])\n\nfig, ax = plt.subplots()\nax.plot(ncoefs, errs, marker='.', color='black')\nax.set_xlabel('n_coef')\nax.set_ylabel('Ez reconstruction error')\nax.set_yscale('log')\n
ncoefs = np.arange(10, FM2.shape[2]//2) errs = np.array([FM2.to_impact_solrf(n_coef = n, zmirror=True)['fmap']['info']['Ez_err'] for n in ncoefs]) fig, ax = plt.subplots() ax.plot(ncoefs, errs, marker='.', color='black') ax.set_xlabel('n_coef') ax.set_ylabel('Ez reconstruction error') ax.set_yscale('log') In\u00a0[37]: Copied!
#FM.write_gpt('solenoid.gdf', asci2gdf_bin='$ASCI2GDF_BIN', verbose=True)\nFM.write_gpt('rfgun_for_gpt.txt', verbose=True)\n
#FM.write_gpt('solenoid.gdf', asci2gdf_bin='$ASCI2GDF_BIN', verbose=True) FM.write_gpt('rfgun_for_gpt.txt', verbose=True)
ASCII field data written. Convert to GDF using: asci2df -o field.gdf rfgun_for_gpt.txt\n
Out[37]:
'rfgun_for_gpt.txt'
In\u00a0[38]: Copied!
FM.write_superfish('rfgun2.t7')\n
FM.write_superfish('rfgun2.t7') Out[38]:
'rfgun2.t7'
In\u00a0[39]: Copied!
FM3 = FieldMesh.from_superfish('rfgun2.t7')\nFM3\n
FM3 = FieldMesh.from_superfish('rfgun2.t7') FM3 Out[39]:
<FieldMesh with cylindrical geometry and (61, 1, 521) shape at 0x7fd5880a9970>
In\u00a0[40]: Copied!
help(FieldMesh.from_superfish)\n
help(FieldMesh.from_superfish)
Help on method read_superfish_t7 in module pmd_beamphysics.interfaces.superfish:\n\nread_superfish_t7(type=None, geometry='cylindrical') method of builtins.type instance\n    Parses a T7 file written by Posson/Superfish.\n    \n    Fish or Poisson T7 are automatically detected according to the second line.\n    \n    For Poisson problems, the type must be specified.\n    \n    Superfish fields oscillate as:\n        Er, Ez ~ cos(wt)\n        Hphi   ~ -sin(wt)\n      \n    For complex fields oscillating as e^-iwt\n    \n        Re(Ex*e^-iwt)   ~ cos\n        Re(-iB*e^-iwt) ~ -sin        \n    and therefore B = -i * mu_0 * H_phi is the complex magnetic field in Tesla\n    \n    \n    Parameters:\n    ----------\n    filename: str\n        T7 filename to read\n    type: str, optional\n        For Poisson files, required to be 'electric' or 'magnetic'. \n        Not used for Fish files\n    geometry: str, optional\n        field geometry, currently required to be the default: 'cylindrical'\n    \n    Returns:\n    -------\n    fieldmesh_data: dict of dicts:\n        attrs\n        components\n        \n        \n    A FieldMesh object is instantiated from this as:\n        FieldMesh(data=fieldmesh_data)\n\n

Note that writing the ASCII and conversions alter the data slightly

In\u00a0[41]: Copied!
FM == FM3\n
FM == FM3 Out[41]:
False

But the data are all close:

In\u00a0[42]: Copied!
for c in FM.components:\n    close = np.allclose(FM.components[c], FM3.components[c])\n    equal = np.all(FM.components[c] == FM3.components[c])\n    print(c, equal, close)\n
for c in FM.components: close = np.allclose(FM.components[c], FM3.components[c]) equal = np.all(FM.components[c] == FM3.components[c]) print(c, equal, close)
electricField/z False True\nelectricField/r False True\nmagneticField/theta False True\n
In\u00a0[43]: Copied!
FM3D = FieldMesh.from_ansys_ascii_3d(efile='../data/ansys_rfgun_2856MHz_E.dat',\n                              hfile='../data/ansys_rfgun_2856MHz_H.dat',\n                              frequency=2856e6)\n\n\n\nFM3D\n
FM3D = FieldMesh.from_ansys_ascii_3d(efile='../data/ansys_rfgun_2856MHz_E.dat', hfile='../data/ansys_rfgun_2856MHz_H.dat', frequency=2856e6) FM3D Out[43]:
<FieldMesh with rectangular geometry and (3, 3, 457) shape at 0x7fd58809c970>
In\u00a0[44]: Copied!
FM3D.attrs\n
FM3D.attrs Out[44]:
{'eleAnchorPt': 'beginning',\n 'gridGeometry': 'rectangular',\n 'axisLabels': ('x', 'y', 'z'),\n 'gridLowerBound': (0, 0, 0),\n 'gridOriginOffset': (-0.001, -0.001, 0.0),\n 'gridSpacing': (0.001, 0.001, 0.00025),\n 'gridSize': (3, 3, 457),\n 'harmonic': 1,\n 'fundamentalFrequency': 2856000000.0,\n 'RFphase': 0,\n 'fieldScale': 1.0}

This can then be written:

In\u00a0[45]: Copied!
FM3D.write('../data/rfgun_rectangular.h5')\n
FM3D.write('../data/rfgun_rectangular.h5')

The y=0 plane can be extracted to be used as cylindrically symmetric data:

In\u00a0[46]: Copied!
FM2D = FM3D.to_cylindrical()\nFM2D\n
FM2D = FM3D.to_cylindrical() FM2D Out[46]:
<FieldMesh with cylindrical geometry and (2, 1, 457) shape at 0x7fd58809c850>
In\u00a0[47]: Copied!
import os\nfor file in ('test.h5',\n             'astra_1d.dat',\n             'rfgun_for_gpt.txt',\n             'rfgun2.h5',\n             'rfgun2.t7'):\n    os.remove(file)\n
import os for file in ('test.h5', 'astra_1d.dat', 'rfgun_for_gpt.txt', 'rfgun2.h5', 'rfgun2.t7'): os.remove(file)"},{"location":"examples/fields/field_examples/#fieldmesh-examples","title":"FieldMesh Examples\u00b6","text":""},{"location":"examples/fields/field_examples/#internal-data","title":"Internal data\u00b6","text":"

attributes and components

"},{"location":"examples/fields/field_examples/#properties","title":"Properties\u00b6","text":"

Convenient access to these

"},{"location":"examples/fields/field_examples/#components","title":"Components\u00b6","text":""},{"location":"examples/fields/field_examples/#oscillating-fields","title":"Oscillating fields\u00b6","text":"

Oscillating fields have .harmonic > 0

"},{"location":"examples/fields/field_examples/#verify-the-oscillation","title":"Verify the oscillation\u00b6","text":"

Complex fields oscillate as $e^{-i\\omega t}$. For TM fields, the spatial components $E_z$ and $B_\\theta$ near the axis

$\\Re E_{z} = -\\frac{r}{2}\\frac{\\omega}{c^2} \\Im B_\\theta$

"},{"location":"examples/fields/field_examples/#units","title":"Units\u00b6","text":""},{"location":"examples/fields/field_examples/#write","title":"Write\u00b6","text":""},{"location":"examples/fields/field_examples/#write-astra-1d","title":"Write Astra 1D\u00b6","text":"

Astra primarily uses simple 1D (on-axis) fieldmaps.

"},{"location":"examples/fields/field_examples/#write-impact-t","title":"Write Impact-T\u00b6","text":"

Impact-T uses a particular Fourier representation for 1D fields. These routines form this data.

"},{"location":"examples/fields/field_examples/#write-gpt","title":"Write GPT\u00b6","text":""},{"location":"examples/fields/field_examples/#read-superfish","title":"Read Superfish\u00b6","text":"

Proper Superfish T7 can also be read.

"},{"location":"examples/fields/field_examples/#read-ansys","title":"Read ANSYS\u00b6","text":"

Read ANSYS E and H ASCII files:

"},{"location":"examples/fields/field_examples/#cleanup","title":"Cleanup\u00b6","text":""},{"location":"examples/fields/field_expansion/","title":"Field expansion","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n\n# Nicer plotting\nimport matplotlib.pyplot as plt\n%config InlineBackend.figure_format = 'retina'\n\nimport numpy as np\n
# Useful for debugging %load_ext autoreload %autoreload 2 # Nicer plotting import matplotlib.pyplot as plt %config InlineBackend.figure_format = 'retina' import numpy as np In\u00a0[2]: Copied!
from pmd_beamphysics import FieldMesh\n
from pmd_beamphysics import FieldMesh In\u00a0[3]: Copied!
FM = FieldMesh('../data/solenoid.h5')\nFM.plot()\n
FM = FieldMesh('../data/solenoid.h5') FM.plot() In\u00a0[4]: Copied!
FM.plot_onaxis()\n
FM.plot_onaxis() In\u00a0[5]: Copied!
from pmd_beamphysics.fields.expansion import fft_derivative_array, spline_derivative_array\n
from pmd_beamphysics.fields.expansion import fft_derivative_array, spline_derivative_array In\u00a0[6]: Copied!
Z = FM.coord_vec('z')\nDZ = FM.dz\nFZ = FM.Bz[0,0,:]\n\ndfield1 = fft_derivative_array(FZ, DZ, ncoef=10)\ndfield2 = spline_derivative_array(Z, FZ, s=1e-9)\n
Z = FM.coord_vec('z') DZ = FM.dz FZ = FM.Bz[0,0,:] dfield1 = fft_derivative_array(FZ, DZ, ncoef=10) dfield2 = spline_derivative_array(Z, FZ, s=1e-9) In\u00a0[7]: Copied!
plt.plot(Z, dfield1[:,1], label='fft')\nplt.plot(Z, dfield2[:,1], label='spline')\nplt.xlabel('z (m)')\nplt.ylabel(r'$dB_z/dz$' + r\" (T/m)\")\n
plt.plot(Z, dfield1[:,1], label='fft') plt.plot(Z, dfield2[:,1], label='spline') plt.xlabel('z (m)') plt.ylabel(r'$dB_z/dz$' + r\" (T/m)\") Out[7]:
Text(0, 0.5, '$dB_z/dz$ (T/m)')
In\u00a0[8]: Copied!
plt.plot(Z, dfield1[:,2], label='fft')\nplt.plot(Z, dfield2[:,2], label='spline')\nplt.xlabel('z (m)')\nplt.ylabel(r'$d^2B_z/dz^2$' + r\" (T/m^2)\")\nplt.legend()\n
plt.plot(Z, dfield1[:,2], label='fft') plt.plot(Z, dfield2[:,2], label='spline') plt.xlabel('z (m)') plt.ylabel(r'$d^2B_z/dz^2$' + r\" (T/m^2)\") plt.legend() Out[8]:
<matplotlib.legend.Legend at 0x7f26843c9b20>
In\u00a0[9]: Copied!
plt.plot(Z, dfield1[:,3], label='fft')\nplt.plot(Z, dfield2[:,3], label='spline')\nplt.xlabel('z (m)')\nplt.ylabel(r'$d^3B_z/dz^3$' + r\" (T/m^3)\")\nplt.legend()\n
plt.plot(Z, dfield1[:,3], label='fft') plt.plot(Z, dfield2[:,3], label='spline') plt.xlabel('z (m)') plt.ylabel(r'$d^3B_z/dz^3$' + r\" (T/m^3)\") plt.legend() Out[9]:
<matplotlib.legend.Legend at 0x7f268436a490>
In\u00a0[10]: Copied!
FM2 = FieldMesh.from_onaxis(z=Z, Bz=FZ)\nFM2.plot_onaxis()\n
FM2 = FieldMesh.from_onaxis(z=Z, Bz=FZ) FM2.plot_onaxis() In\u00a0[11]: Copied!
FM3 = FM2.expand_onaxis(dr=FM.dr, nr=10)\nFM3\n
FM3 = FM2.expand_onaxis(dr=FM.dr, nr=10) FM3 Out[11]:
<FieldMesh with cylindrical geometry and (10, 1, 201) shape at 0x7f26842e2bb0>
In\u00a0[12]: Copied!
FM3.plot('Br')\n
FM3.plot('Br') In\u00a0[13]: Copied!
def compare(fm1, fm2, component='Ez'):\n    \n    z = fm1.coord_vec('z')\n    dr = fm1.dr\n    nr = min(fm1.shape[0], fm2.shape[0])\n    \n    unit = fm1.units(component)\n    Fz1 =  np.squeeze(fm1[component])[0:nr, :]\n    Fz2 =  np.squeeze(fm2[component])[0:nr, :]\n    err = abs(Fz1-Fz2) / np.abs(Fz1).max() \n    \n    extent = np.array([z.min(), z.max(), 0, dr*(nr-1)]) * 1000\n    plt.imshow(err, origin='lower', extent = extent , aspect='auto')\n    plt.xlabel('z (mm)')\n    plt.ylabel('r (mm)')\n    \n    plt.title(f\"{component} expansion error, max err = {err.max()}\")\n    plt.colorbar(label=f'relatic expansion error')\n    \ncompare(FM, FM3, 'B')\n
def compare(fm1, fm2, component='Ez'): z = fm1.coord_vec('z') dr = fm1.dr nr = min(fm1.shape[0], fm2.shape[0]) unit = fm1.units(component) Fz1 = np.squeeze(fm1[component])[0:nr, :] Fz2 = np.squeeze(fm2[component])[0:nr, :] err = abs(Fz1-Fz2) / np.abs(Fz1).max() extent = np.array([z.min(), z.max(), 0, dr*(nr-1)]) * 1000 plt.imshow(err, origin='lower', extent = extent , aspect='auto') plt.xlabel('z (mm)') plt.ylabel('r (mm)') plt.title(f\"{component} expansion error, max err = {err.max()}\") plt.colorbar(label=f'relatic expansion error') compare(FM, FM3, 'B') In\u00a0[14]: Copied!
FM = FieldMesh('../data/rfgun.h5')\nFM.plot()\n
FM = FieldMesh('../data/rfgun.h5') FM.plot() In\u00a0[15]: Copied!
Z = FM.coord_vec('z')\nDZ = FM.dz\nFZ = np.real(FM.Ez[0,0,:])\n\nFM2 = FieldMesh.from_onaxis(z=Z, Ez=FZ, frequency=FM.frequency)\nFM2.plot_onaxis()\n
Z = FM.coord_vec('z') DZ = FM.dz FZ = np.real(FM.Ez[0,0,:]) FM2 = FieldMesh.from_onaxis(z=Z, Ez=FZ, frequency=FM.frequency) FM2.plot_onaxis() In\u00a0[\u00a0]: Copied!
\n
In\u00a0[16]: Copied!
NR = 40\nFM3 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='fft')\ncompare(FM, FM3, 'Er')\n
NR = 40 FM3 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='fft') compare(FM, FM3, 'Er') In\u00a0[17]: Copied!
compare(FM, FM3, 'Ez')\n
compare(FM, FM3, 'Ez') In\u00a0[18]: Copied!
compare(FM, FM3, 'Btheta')\n
compare(FM, FM3, 'Btheta') In\u00a0[19]: Copied!
NR = 40\nFM4 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='spline', spline_s=1e-9)\ncompare(FM, FM4, 'Er')\n
NR = 40 FM4 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='spline', spline_s=1e-9) compare(FM, FM4, 'Er') In\u00a0[20]: Copied!
compare(FM, FM4, 'Ez')\n
compare(FM, FM4, 'Ez') In\u00a0[21]: Copied!
compare(FM, FM4, 'Btheta')\n
compare(FM, FM4, 'Btheta') In\u00a0[22]: Copied!
compare(FM, FM4, 'Btheta')\n
compare(FM, FM4, 'Btheta') In\u00a0[23]: Copied!
# Differences between the two methods\ncompare(FM3, FM4, 'E')\n
# Differences between the two methods compare(FM3, FM4, 'E') In\u00a0[24]: Copied!
def compare2(comp='Er'):\n    NR = 10\n    dr = FM.dr\n    r = (NR-1)*FM.dr\n    FM5 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='fft', ncoef=15)\n    \n    if comp.startswith('E'):\n        func = np.real\n    else:\n        func = np.imag\n    \n    f0 = func(FM[comp][NR-1,0,:])\n    \n    f5 = func(FM5[comp][NR-1,0,:])\n    \n    FM6 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='spline', spline_s=1e-9)\n    f6 = func(FM6[comp][NR-1,0,:])\n    \n    fix, ax = plt.subplots()\n    ax2 = ax.twinx()\n    ax.plot(f0, label='original')\n    ax.plot(f5, '--', label='fourier')\n    ax.plot(f6, '--', label='spline')\n    ax.legend(loc='upper left')\n    ax2.plot(abs((f5-f0)/f0), color='purple', label='relative fourier error')\n    ax2.plot(abs((f6-f0)/f0), color='grey', label='relative spline error')\n    ax2.set_yscale('log')\n    ax.set_ylabel(comp)\n    ax2.set_ylabel('relative error')\n    ax2.legend(loc='upper right')\n    ax.set_xlabel('index along z')\n
def compare2(comp='Er'): NR = 10 dr = FM.dr r = (NR-1)*FM.dr FM5 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='fft', ncoef=15) if comp.startswith('E'): func = np.real else: func = np.imag f0 = func(FM[comp][NR-1,0,:]) f5 = func(FM5[comp][NR-1,0,:]) FM6 = FM2.expand_onaxis(dr=FM.dr, nr=NR, method='spline', spline_s=1e-9) f6 = func(FM6[comp][NR-1,0,:]) fix, ax = plt.subplots() ax2 = ax.twinx() ax.plot(f0, label='original') ax.plot(f5, '--', label='fourier') ax.plot(f6, '--', label='spline') ax.legend(loc='upper left') ax2.plot(abs((f5-f0)/f0), color='purple', label='relative fourier error') ax2.plot(abs((f6-f0)/f0), color='grey', label='relative spline error') ax2.set_yscale('log') ax.set_ylabel(comp) ax2.set_ylabel('relative error') ax2.legend(loc='upper right') ax.set_xlabel('index along z') In\u00a0[25]: Copied!
compare2('Er')\n
compare2('Er')
/tmp/ipykernel_5629/1820762901.py:25: RuntimeWarning: divide by zero encountered in divide\n  ax2.plot(abs((f5-f0)/f0), color='purple', label='relative fourier error')\n/tmp/ipykernel_5629/1820762901.py:26: RuntimeWarning: divide by zero encountered in divide\n  ax2.plot(abs((f6-f0)/f0), color='grey', label='relative spline error')\n
In\u00a0[26]: Copied!
compare2('Ez')\n
compare2('Ez') In\u00a0[27]: Copied!
compare2('Btheta')\n
compare2('Btheta')"},{"location":"examples/fields/field_expansion/#field-expansion","title":"Field expansion\u00b6","text":""},{"location":"examples/fields/field_expansion/#derivative-array","title":"Derivative array\u00b6","text":"

Field expansions depend on numerical derivatives of the on-axis field. Here are two methods.

"},{"location":"examples/fields/field_expansion/#fieldmesh-from-1d-data","title":"FieldMesh from 1D data\u00b6","text":""},{"location":"examples/fields/field_expansion/#expansion-1d-2d","title":"Expansion 1D -> 2D\u00b6","text":""},{"location":"examples/fields/field_expansion/#rf-gun-1d-2d","title":"RF Gun 1D -> 2D\u00b6","text":""},{"location":"examples/fields/field_expansion/#spline-based-expansion","title":"Spline-based expansion\u00b6","text":""},{"location":"examples/fields/field_expansion/#compare-fourier-and-spline","title":"Compare Fourier and Spline\u00b6","text":""},{"location":"examples/fields/field_tracking/","title":"Field Phasing and Scaling (Autophase)","text":"In\u00a0[1]: Copied!
# Useful for debugging\n%load_ext autoreload\n%autoreload 2\n\n# Nicer plotting\nimport matplotlib.pyplot as plt\n%matplotlib inline\n%config InlineBackend.figure_format = 'retina'\n\nimport numpy as np\n
# Useful for debugging %load_ext autoreload %autoreload 2 # Nicer plotting import matplotlib.pyplot as plt %matplotlib inline %config InlineBackend.figure_format = 'retina' import numpy as np In\u00a0[2]: Copied!
from pmd_beamphysics import FieldMesh\n
from pmd_beamphysics import FieldMesh In\u00a0[3]: Copied!
FM = FieldMesh('../data/rfgun.h5')\nFM.plot(aspect='equal', figsize=(12,8))\n
FM = FieldMesh('../data/rfgun.h5') FM.plot(aspect='equal', figsize=(12,8)) In\u00a0[4]: Copied!
# On-axis field\nz0 = FM.coord_vec('z')\nEz0 = FM.Ez[0,0,:]  # this is complex\nplt.plot(z0, np.real(Ez0))\n
# On-axis field z0 = FM.coord_vec('z') Ez0 = FM.Ez[0,0,:] # this is complex plt.plot(z0, np.real(Ez0)) Out[4]:
[<matplotlib.lines.Line2D at 0x1228734c0>]
In\u00a0[5]: Copied!
from pmd_beamphysics.fields.analysis import accelerating_voltage_and_phase\n
from pmd_beamphysics.fields.analysis import accelerating_voltage_and_phase In\u00a0[6]: Copied!
?accelerating_voltage_and_phase\n
?accelerating_voltage_and_phase In\u00a0[7]: Copied!
V0, phase0 = accelerating_voltage_and_phase(z0, -Ez0*120e6, FM.frequency)\n\nV0, (phase0 * 180/np.pi) % 360\n
V0, phase0 = accelerating_voltage_and_phase(z0, -Ez0*120e6, FM.frequency) V0, (phase0 * 180/np.pi) % 360 Out[7]:
(5795904.446882586, 322.1355626180106)

Equations of motion:

$\\frac{dz}{dt} = \\frac{pc}{\\sqrt{(pc)^2 + m^2 c^4)}} c$

$\\frac{dp}{dt} = q E_z $

$E_z = \\Re f(z) \\exp(-i \\omega t) $

In\u00a0[8]: Copied!
from pmd_beamphysics.fields.analysis import track_field_1d\nfrom pmd_beamphysics.units import mec2, c_light\n
from pmd_beamphysics.fields.analysis import track_field_1d from pmd_beamphysics.units import mec2, c_light In\u00a0[9]: Copied!
?track_field_1d\n
?track_field_1d In\u00a0[10]: Copied!
Z = FM.coord_vec('z')\nE = FM.Ez[0,0,:]*np.exp(1j*2*np.pi /360 * 0)*120e6 \n\n# Final z (m) and pz (eV/c)\ntrack_field_1d(Z, E, FM.frequency, pz0=0, t0=0)\n
Z = FM.coord_vec('z') E = FM.Ez[0,0,:]*np.exp(1j*2*np.pi /360 * 0)*120e6 # Final z (m) and pz (eV/c) track_field_1d(Z, E, FM.frequency, pz0=0, t0=0) Out[10]:
(0.13000001229731462, 3896770.3798088795)
In\u00a0[11]: Copied!
# Use debug mode to see the actual track\nsol = track_field_1d(Z, E, FM.frequency, pz0=0, t0=0, debug=True, max_step=1/FM.frequency/100)\n
# Use debug mode to see the actual track sol = track_field_1d(Z, E, FM.frequency, pz0=0, t0=0, debug=True, max_step=1/FM.frequency/100) In\u00a0[12]: Copied!
# Plot the track\nfig, ax = plt.subplots()\n\nax2 = ax.twinx()\n\nax.set_xlabel('f*t')\nax.set_ylabel('z (m)')\nax2.set_ylabel('KE (MeV)')\n\nax.plot(sol.t*FM.frequency, sol.y[0])\nax2.plot(sol.t*FM.frequency, (np.hypot(sol.y[1], mec2)-mec2)/1e6, color='red')\n
# Plot the track fig, ax = plt.subplots() ax2 = ax.twinx() ax.set_xlabel('f*t') ax.set_ylabel('z (m)') ax2.set_ylabel('KE (MeV)') ax.plot(sol.t*FM.frequency, sol.y[0]) ax2.plot(sol.t*FM.frequency, (np.hypot(sol.y[1], mec2)-mec2)/1e6, color='red') Out[12]:
[<matplotlib.lines.Line2D at 0x122e69940>]
In\u00a0[13]: Copied!
from pmd_beamphysics.fields.analysis import autophase_field\n
from pmd_beamphysics.fields.analysis import autophase_field In\u00a0[14]: Copied!
phase_deg1, pz1 = autophase_field(FM, pz0=0, scale=120e6, verbose=True)\nphase_deg1, pz1\n
phase_deg1, pz1 = autophase_field(FM, pz0=0, scale=120e6, verbose=True) phase_deg1, pz1
v=c voltage: 5795904.446882586 V, phase: -37.86443738198939 deg\n    iterations: 18\n    function calls: 23\n
Out[14]:
(304.334830187332, 6234145.7957780445)
In\u00a0[15]: Copied!
# Use debug mode to visualize. This returns the phasiing function\nphase_f = autophase_field(FM, pz0=0, scale=120e6, debug=True)\nphase_f(304.3348289439232)\n
# Use debug mode to visualize. This returns the phasiing function phase_f = autophase_field(FM, pz0=0, scale=120e6, debug=True) phase_f(304.3348289439232) Out[15]:
6234145.795777954
In\u00a0[16]: Copied!
plist = np.linspace(280, 330, 100)\npzlist = np.array([phase_f(p) for p in plist])\n\nplt.plot(plist, pzlist/1e6)\nplt.scatter(phase_deg1, pz1/1e6, color='red')\nplt.xlabel('phase (deg)')\nplt.ylabel('pz (MeV/c)')\n
plist = np.linspace(280, 330, 100) pzlist = np.array([phase_f(p) for p in plist]) plt.plot(plist, pzlist/1e6) plt.scatter(phase_deg1, pz1/1e6, color='red') plt.xlabel('phase (deg)') plt.ylabel('pz (MeV/c)') Out[16]:
Text(0, 0.5, 'pz (MeV/c)')
In\u00a0[17]: Copied!
from pmd_beamphysics.fields.analysis import autophase_and_scale_field\n?autophase_and_scale_field\n
from pmd_beamphysics.fields.analysis import autophase_and_scale_field ?autophase_and_scale_field In\u00a0[18]: Copied!
phase_deg2, scale2 = autophase_and_scale_field(FM, 6e6, pz0=0, verbose=True)\nphase_deg2, scale2\n
phase_deg2, scale2 = autophase_and_scale_field(FM, 6e6, pz0=0, verbose=True) phase_deg2, scale2
v=c voltage: 0.048299203724021564 V, phase: -37.86443738198939 deg\n    Pass 1 delta energy: 6000000.288677918 at phase  304.9786263011349 deg\n    Pass 2 delta energy: 5999999.999982537 at phase  305.14631150985355 deg\n
Out[18]:
(305.14631150985355, 125273551.27124627)
In\u00a0[19]: Copied!
# Use debug mode to visualize. This returns the phasing function\nps_f = autophase_and_scale_field(FM, 6e6, pz0=0, debug=True)\nps_f(phase_deg2, scale2)\n
# Use debug mode to visualize. This returns the phasing function ps_f = autophase_and_scale_field(FM, 6e6, pz0=0, debug=True) ps_f(phase_deg2, scale2) Out[19]:
5999999.999982537
In\u00a0[20]: Copied!
plist = np.linspace(280, 330, 100)\ndenergy = np.array([ps_f(p, scale2) for p in plist])\n\nplt.plot(plist, denergy/1e6)\nplt.scatter(phase_deg2, ps_f(phase_deg2, scale2)/1e6, color='red', label='Autophased')\nplt.xlabel('phase (deg)')\nplt.ylabel('Voltage (MV)')\nplt.legend()\n
plist = np.linspace(280, 330, 100) denergy = np.array([ps_f(p, scale2) for p in plist]) plt.plot(plist, denergy/1e6) plt.scatter(phase_deg2, ps_f(phase_deg2, scale2)/1e6, color='red', label='Autophased') plt.xlabel('phase (deg)') plt.ylabel('Voltage (MV)') plt.legend() Out[20]:
<matplotlib.legend.Legend at 0x1232768b0>
"},{"location":"examples/fields/field_tracking/#field-phasing-and-scaling-autophase","title":"Field Phasing and Scaling (Autophase)\u00b6","text":""},{"location":"examples/fields/field_tracking/#get-field","title":"Get field\u00b6","text":""},{"location":"examples/fields/field_tracking/#vc-voltage-and-phase","title":"v=c voltage and phase\u00b6","text":""},{"location":"examples/fields/field_tracking/#tracking","title":"Tracking\u00b6","text":""},{"location":"examples/fields/field_tracking/#autophase","title":"Autophase\u00b6","text":""},{"location":"examples/fields/field_tracking/#autophase-and-scale","title":"Autophase and Scale\u00b6","text":""}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 81352fd..f4291ff 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ