Skip to content

Commit e74a5f7

Browse files
authored
Merge pull request #1 from mihofer/main
add XY grid and set coordinates functions, to_pandas, unit tests, readme
2 parents c265ff8 + 1a23c4f commit e74a5f7

File tree

6 files changed

+310
-16
lines changed

6 files changed

+310
-16
lines changed

.github/workflows/python-package.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
python-version: ["3.8", "3.9", "3.10"]
19+
python-version: ["3.8", "3.9"]
2020

2121
steps:
2222
- uses: actions/checkout@v3
@@ -27,8 +27,8 @@ jobs:
2727
- name: Install dependencies
2828
run: |
2929
python -m pip install --upgrade pip
30-
python -m pip install flake8 pytest
31-
python -m pip install .
30+
python -m pip install flake8 pytest poetry
31+
poetry install --with gh_actions
3232
- name: Lint with flake8
3333
run: |
3434
# stop the build if there are Python syntax errors or undefined names
@@ -37,4 +37,4 @@ jobs:
3737
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
3838
- name: Test with pytest
3939
run: |
40-
pytest
40+
poetry run pytest

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,48 @@
11
# xdyna
2+
23
Tools to study beam dynamics in xtrack simulations, like dynamic aperture calculations, PYTHIA integration, dynamic indicators, ...
4+
5+
## Dynamic aperture studies
6+
7+
The `xdyna` package provides the `DA` class which serves as a simple front-end for setting up and running dynamic aperture studies.
8+
9+
To start, a `xtrack.line` object is required.
10+
The following code then sets up the study and launches the tracking
11+
12+
```python
13+
14+
import xdyna as xd
15+
16+
da = xd.DA(
17+
name='name_of_your_study', # used to generate a directory where files are stored
18+
normalised_emittance=[1,1], # provide normalized emittance for particle initialization in [m]
19+
max_turns=1e5, # number of turns to track
20+
use_files=False
21+
# in case DA studies should run on HTC condor, files are used to collect the information
22+
# if the tracking is performed locally, no files are needed
23+
)
24+
25+
# initialize a grid of particles using 5 angles in x-y space, in a range from 0 to 20 sigmas in steps of 5 sigma.
26+
da.generate_initial_radial(angles=5, r_min=0, r_max=20, r_step=5, delta=0.)
27+
28+
da.line = line # associate prev. created line, holding the lattice and context, with DA object
29+
30+
da.track_job() # start the tracking
31+
32+
da.survival_data # returns a dataframe with the number of survived turns for the initial position of each particle
33+
34+
```
35+
36+
To use on a platform like HTCondor, perform the same setup as before but using `use_files=True`.
37+
Each HTCondor job then only requires the following lines
38+
39+
```python
40+
import xdyna as xd
41+
# This will load the existing DA based on the meta file with the same name found in the working directory.
42+
# If the script is ran somewhere else, the path to the metafile can be passed with 'path=...'.
43+
DA = xd.DA(name=study, use_files=True)
44+
45+
# Do the tracking, here for 100 particles.
46+
# The code will automatically look for particles that are not-submitted yet and use these.
47+
DA.track_job(npart=100)
48+
```

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ python = ">=3.8"
1414
scipy = ">=1.9.3"
1515
pandas = ">=1.5.1"
1616

17-
1817
[tool.poetry.dev-dependencies]
1918
pytest = "^5.2"
2019

20+
[tool.poetry.group.gh_actions]
21+
optional = true
22+
23+
[tool.poetry.group.gh_actions.dependencies]
24+
xsuite = ">=0.4.0"
25+
2126
[build-system]
2227
requires = ["poetry-core>=1.0.8"] # Needed for pip install -e
2328
build-backend = "poetry.core.masonry.api"

tests/test_ee.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
@pytest.mark.parametrize("mode", ['t']) # use t mode for now, since z takes quite long
1515
def test_simple_radial(mode):
16-
with open(TEST_DIR/'input'/'tapered_t_b1_thin.json', 'r', encoding='utf-8') as fid:
16+
with open(TEST_DIR/'input'/f'tapered_{mode}_b1_thin.json', 'r', encoding='utf-8') as fid:
1717
loaded_dct = json.load(fid)
1818
line = xt.Line.from_dict(loaded_dct)
1919

@@ -22,13 +22,36 @@ def test_simple_radial(mode):
2222
ref_particle = xp.Particles(mass0=xp.ELECTRON_MASS_EV, q0=1, p0c=ENERGY[mode]*10**9, x=0, y=0)
2323
line.particle_ref = ref_particle
2424
tracker = xt.Tracker(_context=context, line=line)
25-
tracker.configure_radiation(mode='mean')
26-
tracker.matrix_stability_tol = 9e-1
25+
tracker.configure_radiation(model='mean')
2726

2827
DA = xd.DA(name=f'fcc_ee_{mode}',
29-
normalised_emittance=[EMITTANCE[mode]['X'], EMITTANCE[mode]['Y']],
28+
normalised_emittance=[EMITTANCE[mode]['X']*ref_particle.beta0[0]*ref_particle.gamma0[0],
29+
EMITTANCE[mode]['Y']*ref_particle.beta0[0]*ref_particle.gamma0[0]],
3030
max_turns=TURNS[mode],
3131
use_files=False)
3232
DA.generate_initial_radial(angles=1, r_min=2, r_max=20, r_step=4., delta=0.000)
3333
DA.line = line
3434
DA.track_job()
35+
36+
37+
@pytest.mark.parametrize("mode", ['t']) # use t mode for now, since z takes quite long
38+
def test_simple_grid(mode):
39+
with open(TEST_DIR/'input'/f'tapered_{mode}_b1_thin.json', 'r', encoding='utf-8') as fid:
40+
loaded_dct = json.load(fid)
41+
line = xt.Line.from_dict(loaded_dct)
42+
43+
context = xo.ContextCpu()
44+
45+
ref_particle = xp.Particles(mass0=xp.ELECTRON_MASS_EV, q0=1, p0c=ENERGY[mode]*10**9, x=0, y=0)
46+
line.particle_ref = ref_particle
47+
tracker = xt.Tracker(_context=context, line=line)
48+
tracker.configure_radiation(model='mean')
49+
50+
DA = xd.DA(name=f'fcc_ee_{mode}',
51+
normalised_emittance=[EMITTANCE[mode]['X']*ref_particle.beta0[0]*ref_particle.gamma0[0],
52+
EMITTANCE[mode]['Y']*ref_particle.beta0[0]*ref_particle.gamma0[0]],
53+
max_turns=TURNS[mode],
54+
use_files=False)
55+
DA.generate_initial_grid(x_min=0, x_max=20, x_step=10, y_min=0, y_max=20, y_step=10, delta=0.000)
56+
DA.line = line
57+
DA.track_job()

tests/test_initialization.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import pytest
2+
import pandas as pd
3+
from pandas.testing import assert_frame_equal
4+
import xdyna as xd
5+
6+
7+
SURV_COLUMNS = ['ang_xy', 'r_xy', 'nturns', 'x_norm_in', 'y_norm_in', 'px_norm_in',
8+
'py_norm_in', 'zeta_in', 'delta_in', 'x_out', 'y_out', 'px_out',
9+
'py_out', 'zeta_out', 'delta_out', 's_out', 'state', 'submitted',
10+
'finished']
11+
12+
13+
14+
def test_mismatch_user_coordinates():
15+
16+
DA = xd.DA(name='user_coordinates',
17+
normalised_emittance=[1,1],
18+
max_turns=2,
19+
use_files=False)
20+
with pytest.raises(AssertionError):
21+
DA.set_coordinates(x=[1], y = [1,2])
22+
23+
24+
def test_user_coordinates():
25+
26+
DA = xd.DA(name='user_coordinates',
27+
normalised_emittance=[1,1],
28+
max_turns=2,
29+
use_files=False)
30+
DA.set_coordinates(x=[1,2], y = [3,4], px=[5,6])
31+
32+
assert_frame_equal(DA.survival_data[['x', 'y', 'px', 'py', 'delta']],
33+
pd.DataFrame(data={
34+
'x':[1,2],
35+
'y':[3,4],
36+
'px':[5,6],
37+
'py':[0,0],
38+
'delta':[0,0]
39+
}) )
40+
41+
42+
def test_xy_grid():
43+
44+
DA = xd.DA(name='user_coordinates',
45+
normalised_emittance=[1,1],
46+
max_turns=2,
47+
use_files=False)
48+
DA.generate_initial_grid(
49+
x_min=0, x_max=2, x_step=2,
50+
y_min=0, y_max=2, y_step=2,
51+
)
52+
assert_frame_equal(DA.survival_data[['x', 'y']],
53+
pd.DataFrame(data={'x':[0.,2.,0.,2.], 'y':[0.,0.,2.,2.]}) )
54+
55+
56+
def test_radial_grid():
57+
58+
DA = xd.DA(name='user_coordinates',
59+
normalised_emittance=[1,1],
60+
max_turns=2,
61+
use_files=False)
62+
DA.generate_initial_radial(
63+
r_min=0, r_max=2, r_step=2,
64+
ang_min=0, ang_max=90, angles=2,
65+
)
66+
assert_frame_equal(DA.survival_data[['amplitude', 'angle']],
67+
pd.DataFrame(data={'amplitude':[0.,2.,0.,2.], 'angle':[0.,0.,90.,90.]}) )
68+
69+
70+
def test_pandas():
71+
72+
DA = xd.DA(name='user_coordinates',
73+
normalised_emittance=[1,1],
74+
max_turns=2,
75+
use_files=False)
76+
DA.generate_initial_radial(
77+
r_min=0, r_max=2, r_step=2,
78+
ang_min=0, ang_max=90, angles=2,
79+
)
80+
81+
assert_frame_equal(DA.survival_data,
82+
DA.to_pandas())
83+
assert all(elem in DA.to_pandas(full=True).columns for elem in SURV_COLUMNS)

0 commit comments

Comments
 (0)