"
]
},
"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:
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:
Component data is always 3D.
Initialization from openPMD-beamphysics HDF5 file:
Initialization from a data dict:
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
Plotting:
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:
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:
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:
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¶meter 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:
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:
Component data is always 3D.
Initialization from openPMD-beamphysics HDF5 file:
Initialization from a data dict:
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
Plotting:
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:
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:
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:
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¶meter 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