xcoll 0.5.8__py3-none-any.whl → 0.5.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xcoll might be problematic. Click here for more details.
- xcoll/beam_elements/__init__.py.orig +32 -0
- xcoll/colldb.py.orig +654 -0
- xcoll/general.py +1 -1
- xcoll/initial_distribution.py.orig +224 -0
- {xcoll-0.5.8.dist-info → xcoll-0.5.9.dist-info}/METADATA +5 -6
- {xcoll-0.5.8.dist-info → xcoll-0.5.9.dist-info}/RECORD +9 -6
- {xcoll-0.5.8.dist-info → xcoll-0.5.9.dist-info}/LICENSE +0 -0
- {xcoll-0.5.8.dist-info → xcoll-0.5.9.dist-info}/NOTICE +0 -0
- {xcoll-0.5.8.dist-info → xcoll-0.5.9.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# copyright ############################### #
|
|
2
|
+
# This file is part of the Xcoll package. #
|
|
3
|
+
# Copyright (c) CERN, 2024. #
|
|
4
|
+
# ######################################### #
|
|
5
|
+
|
|
6
|
+
from .base import BaseBlock, BaseCollimator, BaseCrystal
|
|
7
|
+
from .absorber import BlackAbsorber, BlackCrystal
|
|
8
|
+
from .everest import EverestBlock, EverestCollimator, EverestCrystal
|
|
9
|
+
from .blowup import BlowUp
|
|
10
|
+
from .monitor import EmittanceMonitor
|
|
11
|
+
|
|
12
|
+
block_classes = tuple(v for v in globals().values()
|
|
13
|
+
if isinstance(v, type) and issubclass(v, BaseBlock) and v != BaseBlock
|
|
14
|
+
and v != BaseCollimator and v != BaseCrystal)
|
|
15
|
+
# Includes crystals
|
|
16
|
+
collimator_classes = tuple(v for v in globals().values()
|
|
17
|
+
if isinstance(v, type) and (issubclass(v, BaseCollimator) or issubclass(v, BaseCrystal))
|
|
18
|
+
and v != BaseCollimator and v != BaseCrystal)
|
|
19
|
+
crystal_classes = tuple(v for v in globals().values()
|
|
20
|
+
if isinstance(v, type) and issubclass(v, BaseCrystal) and v != BaseCrystal)
|
|
21
|
+
|
|
22
|
+
element_classes = block_classes + (BlowUp, EmittanceMonitor)
|
|
23
|
+
<<<<<<< HEAD
|
|
24
|
+
|
|
25
|
+
# These should not go into any of the classes lists
|
|
26
|
+
# but are added for compatibility with _K2
|
|
27
|
+
from .k2 import _K2Collimator, _K2Crystal
|
|
28
|
+
_all_block_classes = block_classes + (_K2Collimator, _K2Crystal)
|
|
29
|
+
_all_collimator_classes = collimator_classes + (_K2Collimator, _K2Crystal)
|
|
30
|
+
_all_crystal_classes = crystal_classes + (_K2Crystal,)
|
|
31
|
+
=======
|
|
32
|
+
>>>>>>> release/v0.6.0
|
xcoll/colldb.py.orig
ADDED
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
# copyright ############################### #
|
|
2
|
+
# This file is part of the Xcoll package. #
|
|
3
|
+
# Copyright (c) CERN, 2024. #
|
|
4
|
+
# ######################################### #
|
|
5
|
+
|
|
6
|
+
import io
|
|
7
|
+
import re
|
|
8
|
+
import json
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import xtrack as xt
|
|
14
|
+
|
|
15
|
+
from .beam_elements import BlackAbsorber, BlackCrystal, EverestCollimator, EverestCrystal, \
|
|
16
|
+
<<<<<<< HEAD
|
|
17
|
+
FlukaCollimator, BaseCollimator, BaseCrystal, _all_collimator_classes
|
|
18
|
+
=======
|
|
19
|
+
BaseCollimator, BaseCrystal, collimator_classes
|
|
20
|
+
>>>>>>> release/v0.6.0
|
|
21
|
+
from .scattering_routines.everest.materials import SixTrack_to_xcoll
|
|
22
|
+
from .scattering_routines.fluka import FlukaEngine
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _initialise_None(dct):
|
|
26
|
+
fields = {'gap': None, 'angle': 0, 'offset': 0, 'parking': 1, 'jaw': None, 'family': None}
|
|
27
|
+
fields.update({'overwritten_keys': [], 'side': 'both', 'material': None, 'stage': None})
|
|
28
|
+
fields.update({'length': 0, 'collimator_type': None, 'active': True, 'crystal': None, 'tilt': 0})
|
|
29
|
+
fields.update({'bending_radius': None, 'bending_angle': None, 'width': 0, 'height': 0, 'miscut': 0})
|
|
30
|
+
for f, val in fields.items():
|
|
31
|
+
if f not in dct.keys():
|
|
32
|
+
dct[f] = val
|
|
33
|
+
for key in dct.keys():
|
|
34
|
+
if key not in fields.keys():
|
|
35
|
+
raise ValueError(f"Illegal setting {key} in collimator!")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _dict_keys_to_lower(dct):
|
|
39
|
+
if isinstance(dct, dict):
|
|
40
|
+
return {k.lower(): _dict_keys_to_lower(v) for k,v in dct.items()}
|
|
41
|
+
else:
|
|
42
|
+
return dct
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_coll_dct_by_beam(coll, beam):
|
|
46
|
+
# The dictionary can be a CollimatorDatabase for a single beam (beam=None)
|
|
47
|
+
# or for both beams (beam='b1' or beam='b2)
|
|
48
|
+
if beam is not None:
|
|
49
|
+
if isinstance(beam, int) or len(beam) == 1:
|
|
50
|
+
beam = f'b{beam}'
|
|
51
|
+
beam = beam.lower()
|
|
52
|
+
beam_in_db = list(coll.keys())
|
|
53
|
+
|
|
54
|
+
if beam_in_db == ['b1','b2']:
|
|
55
|
+
if beam is None:
|
|
56
|
+
raise ValueError("Need to specify a beam, because the given dict is for both beams!")
|
|
57
|
+
return coll[beam]
|
|
58
|
+
|
|
59
|
+
elif len(beam_in_db) == 1:
|
|
60
|
+
if beam is None:
|
|
61
|
+
beam = beam_in_db[0].lower()
|
|
62
|
+
elif beam != beam_in_db[0].lower():
|
|
63
|
+
raise ValueError("Variable 'beam' does not match beam specified in CollimatorDatabase!")
|
|
64
|
+
return coll[beam]
|
|
65
|
+
|
|
66
|
+
elif beam is not None:
|
|
67
|
+
print("Warning: Specified a beam, but the CollimatorDatabase is for a single beam only!")
|
|
68
|
+
return coll
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class CollimatorDatabase:
|
|
72
|
+
|
|
73
|
+
def __init__(self, **kwargs):
|
|
74
|
+
# Get all arguments
|
|
75
|
+
for key in ['collimator_dict', 'nemitt_x', 'nemitt_y']:
|
|
76
|
+
if key not in kwargs.keys():
|
|
77
|
+
raise ValueError(f"CollimatorDatabase is missing required argument '{key}'!")
|
|
78
|
+
|
|
79
|
+
self._parse_dict(kwargs['collimator_dict'],
|
|
80
|
+
kwargs.get('family_dict', {}),
|
|
81
|
+
kwargs.get('beam', None),
|
|
82
|
+
kwargs.get('_yaml_merged', False),
|
|
83
|
+
kwargs.get('ignore_crystals', True))
|
|
84
|
+
self.nemitt_x = kwargs['nemitt_x']
|
|
85
|
+
self.nemitt_y = kwargs['nemitt_y']
|
|
86
|
+
self._elements = {}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _parse_dict(self, coll, fam, beam=None, _yaml_merged=False, ignore_crystals=True):
|
|
90
|
+
# We make all keys case-insensitive to avoid confusion between different conventions
|
|
91
|
+
coll = _dict_keys_to_lower(coll)
|
|
92
|
+
fam = _dict_keys_to_lower(fam)
|
|
93
|
+
|
|
94
|
+
# The dictionary can be a CollimatorDatabase for a single beam (beam=None)
|
|
95
|
+
# or for both beams (beam='b1' or beam='b2)
|
|
96
|
+
coll = _get_coll_dct_by_beam(coll, beam)
|
|
97
|
+
|
|
98
|
+
# Apply family settings
|
|
99
|
+
crystals = []
|
|
100
|
+
for thiscoll, settings in coll.items():
|
|
101
|
+
settings = {k.lower(): v for k,v in settings.items()}
|
|
102
|
+
if 'family' in settings.keys() and settings['family'] is not None:
|
|
103
|
+
settings['family'] = settings['family'].lower()
|
|
104
|
+
thisfam = settings['family']
|
|
105
|
+
if thisfam not in fam.keys():
|
|
106
|
+
raise ValueError(f"Collimator {thiscoll} depends on family {thisfam}, "
|
|
107
|
+
+ f"but the latter is not defined!")
|
|
108
|
+
|
|
109
|
+
# Check if some family settings are overwritten for this collimator
|
|
110
|
+
# Only do this check if we didn't do a YAML merge earlier (because then it
|
|
111
|
+
# is already taken care of)
|
|
112
|
+
if not _yaml_merged:
|
|
113
|
+
overwritten_keys = [key.lower() for key in settings.keys() if key in fam[thisfam]]
|
|
114
|
+
if len(overwritten_keys) > 0:
|
|
115
|
+
settings['overwritten_keys'] = overwritten_keys
|
|
116
|
+
|
|
117
|
+
# Load family settings, potentially overwriting settings for this collimator
|
|
118
|
+
settings = {**fam[thisfam], **settings}
|
|
119
|
+
|
|
120
|
+
else:
|
|
121
|
+
settings['family'] = None
|
|
122
|
+
coll[thiscoll] = settings
|
|
123
|
+
|
|
124
|
+
# Save list of crystals
|
|
125
|
+
if 'crystal' in settings:
|
|
126
|
+
if settings['crystal'] != 0.0:
|
|
127
|
+
crystals += [thiscoll]
|
|
128
|
+
else:
|
|
129
|
+
settings['crystal'] = None
|
|
130
|
+
|
|
131
|
+
# Remove crystals from colldb
|
|
132
|
+
if ignore_crystals:
|
|
133
|
+
for thiscoll in crystals:
|
|
134
|
+
del coll[thiscoll]
|
|
135
|
+
|
|
136
|
+
# Check that all collimators have gap settings
|
|
137
|
+
if not np.all(['gap' in val.keys() or 'jaw' in val.keys() for val in coll.values()]):
|
|
138
|
+
raise ValueError("Ill-defined CollimatorDatabase: Not all collimators have a gap or "
|
|
139
|
+
+ "jaw setting, (or the keys / structure of the dictionary is wrong)!")
|
|
140
|
+
|
|
141
|
+
# Update collimators with default values for missing keys
|
|
142
|
+
for name, collimator in coll.items():
|
|
143
|
+
# Change all values to lower case
|
|
144
|
+
for key, val in collimator.items():
|
|
145
|
+
collimator[key] = val.lower() if isinstance(val, str) else val
|
|
146
|
+
_initialise_None(collimator)
|
|
147
|
+
|
|
148
|
+
self._collimator_dict = coll
|
|
149
|
+
self._family_dict = fam
|
|
150
|
+
|
|
151
|
+
# =======================================
|
|
152
|
+
# ====== Loading/dumping functions ======
|
|
153
|
+
# =======================================
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def from_yaml(cls, file, **kwargs):
|
|
157
|
+
# Only do the import here, as to not force people to install
|
|
158
|
+
# ruamel if they don't load CollimatorDatabase yaml's
|
|
159
|
+
from ruamel.yaml import YAML
|
|
160
|
+
yaml = YAML(typ='safe')
|
|
161
|
+
if isinstance(file, io.IOBase):
|
|
162
|
+
dct = yaml.load(file)
|
|
163
|
+
else:
|
|
164
|
+
file = Path(file).resolve()
|
|
165
|
+
with file.open('r') as fid:
|
|
166
|
+
dct = yaml.load(fid)
|
|
167
|
+
dct = _dict_keys_to_lower(dct)
|
|
168
|
+
|
|
169
|
+
# If the CollimatorDatabase uses YAML merging, we need a bit of hackery to get the
|
|
170
|
+
# family names from the tags (anchors/aliases)
|
|
171
|
+
_yaml_merged = False
|
|
172
|
+
if 'families' in dct.keys() and not isinstance(dct['families'], dict):
|
|
173
|
+
_yaml_merged = True
|
|
174
|
+
|
|
175
|
+
# First we load a round-trip yaml
|
|
176
|
+
yaml = YAML()
|
|
177
|
+
if isinstance(file, io.IOBase):
|
|
178
|
+
full_dct = yaml.load(file)
|
|
179
|
+
else:
|
|
180
|
+
with open(file, 'r') as fid:
|
|
181
|
+
full_dct = yaml.load(fid)
|
|
182
|
+
famkey = [key for key in full_dct.keys() if key.lower() == 'families'][0]
|
|
183
|
+
collkey = [key for key in full_dct.keys() if key.lower() == 'collimators'][0]
|
|
184
|
+
families = {}
|
|
185
|
+
|
|
186
|
+
# We loop over family names to get the name tag ('anchor') of each family
|
|
187
|
+
for fam, full_fam in zip(dct['families'], full_dct[famkey]):
|
|
188
|
+
if full_fam.anchor.value is None:
|
|
189
|
+
raise ValueError("Missing name tag / anchor in "
|
|
190
|
+
+ "CollimatorDatabase['families']!")
|
|
191
|
+
# We get the anchor from the rt yaml, and use it as key in the families dict
|
|
192
|
+
families[full_fam.anchor.value.lower()] = {f.lower(): v for f, v in fam.items()}
|
|
193
|
+
dct['families'] = families
|
|
194
|
+
|
|
195
|
+
# Now we need to loop over each collimator, and verify which family was used
|
|
196
|
+
beam = kwargs.get('beam', None)
|
|
197
|
+
coll_dct = _get_coll_dct_by_beam(dct['collimators'], beam)
|
|
198
|
+
full_coll_dct = _get_coll_dct_by_beam(full_dct[collkey], beam)
|
|
199
|
+
for coll, full_coll in zip(coll_dct.values(), full_coll_dct.values()):
|
|
200
|
+
if not isinstance(coll['gap'], (int,float)):
|
|
201
|
+
coll['gap'] = None
|
|
202
|
+
if 'family' in coll.keys():
|
|
203
|
+
raise ValueError(f"Error in {coll}: Cannot use merging for families "
|
|
204
|
+
+ "and manually specify family as well!")
|
|
205
|
+
elif len(full_coll.merge) > 0:
|
|
206
|
+
coll['family'] = full_coll.merge[0][1].anchor.value.lower()
|
|
207
|
+
# Check if some family settings are overwritten for this collimator
|
|
208
|
+
overwritten_keys = [key.lower() for key in full_coll.keys()
|
|
209
|
+
if full_coll._unmerged_contains(key)
|
|
210
|
+
and key.lower() in families[coll['family']].keys()]
|
|
211
|
+
if len(overwritten_keys) > 0:
|
|
212
|
+
coll['overwritten_keys'] = overwritten_keys
|
|
213
|
+
else:
|
|
214
|
+
coll['family'] = None
|
|
215
|
+
|
|
216
|
+
return cls.from_dict(dct, _yaml_merged=_yaml_merged, **kwargs)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def from_json(cls, file, **kwargs):
|
|
221
|
+
if isinstance(file, io.IOBase):
|
|
222
|
+
dct = json.load(file)
|
|
223
|
+
else:
|
|
224
|
+
file = Path(file).resolve()
|
|
225
|
+
with file.open('r') as fid:
|
|
226
|
+
dct = json.load(fid)
|
|
227
|
+
dct = _dict_keys_to_lower(dct)
|
|
228
|
+
return cls.from_dict(dct, **kwargs)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@classmethod
|
|
232
|
+
def from_dict(cls, dct, beam=None, _yaml_merged=False, nemitt_x=None, nemitt_y=None, ignore_crystals=True):
|
|
233
|
+
# We make all keys case-insensitive to avoid confusion between different conventions
|
|
234
|
+
# The families are optional
|
|
235
|
+
fam = {}
|
|
236
|
+
dct = _dict_keys_to_lower(dct)
|
|
237
|
+
|
|
238
|
+
# Get the emittance
|
|
239
|
+
if nemitt_x is None and nemitt_y is None:
|
|
240
|
+
if 'emittance' not in dct.keys():
|
|
241
|
+
raise ValueError("Missing emittance info! Add 'emittance' as a key to "
|
|
242
|
+
+ "the CollimatorDatabase file, or specify it as 'nemitt_x' "
|
|
243
|
+
+ "and 'nemitt_y' to the loader!")
|
|
244
|
+
nemitt_x = dct['emittance']['x']
|
|
245
|
+
nemitt_y = dct['emittance']['y']
|
|
246
|
+
elif nemitt_x is None or nemitt_y is None:
|
|
247
|
+
raise ValueError("Need to provide both 'nemitt_x' and 'nemitt_y'!")
|
|
248
|
+
elif 'emittance' in dct.keys():
|
|
249
|
+
if dct['emittance']['x'] != nemitt_x or dct['emittance']['y'] != nemitt_y:
|
|
250
|
+
raise ValueError("Emittance in CollimatorDatabase file different from "
|
|
251
|
+
+ "'nemitt_x' and 'nemitt_y'!")
|
|
252
|
+
|
|
253
|
+
# Get family and collimator dicts
|
|
254
|
+
if 'families' in dct.keys():
|
|
255
|
+
if not 'collimators' in dct.keys():
|
|
256
|
+
raise ValueError("Could not find 'collimators' dict in CollimatorDatabase!")
|
|
257
|
+
fam = dct['families']
|
|
258
|
+
coll = dct['collimators']
|
|
259
|
+
elif 'collimators' in dct.keys():
|
|
260
|
+
coll = dct['collimators']
|
|
261
|
+
else:
|
|
262
|
+
coll = dct
|
|
263
|
+
|
|
264
|
+
return cls(collimator_dict=coll, family_dict=fam, nemitt_x=nemitt_x, nemitt_y=nemitt_y,
|
|
265
|
+
beam=beam, _yaml_merged=_yaml_merged, ignore_crystals=ignore_crystals)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@classmethod
|
|
269
|
+
def from_SixTrack(cls, file, ignore_crystals=True, **kwargs):
|
|
270
|
+
file = Path(file).resolve()
|
|
271
|
+
with file.open('r') as fp:
|
|
272
|
+
coll_data_string = ''
|
|
273
|
+
family_settings = {}
|
|
274
|
+
family_types = {}
|
|
275
|
+
side = {}
|
|
276
|
+
cry_fields = ['bending_radius', 'width', 'height', 'thick', 'tilt', 'miscut', 'crystal']
|
|
277
|
+
cry = {}
|
|
278
|
+
|
|
279
|
+
for line in fp:
|
|
280
|
+
if line.startswith('#'):
|
|
281
|
+
continue # Comment
|
|
282
|
+
sline = line.split()
|
|
283
|
+
if len(sline) > 0:
|
|
284
|
+
if sline[0].lower() == 'nsig_fam':
|
|
285
|
+
family_settings[sline[1]] = float(sline[2])
|
|
286
|
+
family_types[sline[1]] = sline[3]
|
|
287
|
+
elif sline[0].lower() == 'onesided':
|
|
288
|
+
side[sline[1]] = int(sline[2])
|
|
289
|
+
elif sline[0].lower() == "crystal":
|
|
290
|
+
cry[sline[1]] = {}
|
|
291
|
+
for i, key in enumerate(cry_fields):
|
|
292
|
+
idx = i+2
|
|
293
|
+
if i < 6:
|
|
294
|
+
cry[sline[1]][key] = float(sline[idx])
|
|
295
|
+
else:
|
|
296
|
+
cry[sline[1]][key] = int(sline[idx])
|
|
297
|
+
elif sline[0].lower() == 'settings':
|
|
298
|
+
pass # Acknowledge and ignore this line
|
|
299
|
+
elif len(sline) == 6:
|
|
300
|
+
# Standard collimator definition
|
|
301
|
+
coll_data_string += line
|
|
302
|
+
else:
|
|
303
|
+
print(f"Unknown setting {line}")
|
|
304
|
+
|
|
305
|
+
defaults = {}
|
|
306
|
+
_initialise_None(defaults)
|
|
307
|
+
defaults['thick'] = 0
|
|
308
|
+
|
|
309
|
+
famdct = {key: {'gap': None if family_settings[key] > 900 else family_settings[key],
|
|
310
|
+
'stage': family_types[key]} for key in family_settings}
|
|
311
|
+
names = ['name', 'gap', 'material', 'length', 'angle', 'offset']
|
|
312
|
+
|
|
313
|
+
df = pd.read_csv(io.StringIO(coll_data_string), sep=r'\s+', index_col=False, names=names)
|
|
314
|
+
df['family'] = df['gap'].copy()
|
|
315
|
+
df['family'] = df['family'].apply(lambda s: None if re.match(r'^-?\d+(\.\d+)?$', str(s)) else s)
|
|
316
|
+
df.insert(5,'stage', df['gap'].apply(lambda s: None if s in family_types else 'UNKNOWN'))
|
|
317
|
+
|
|
318
|
+
df['gap'] = df['gap'].apply(lambda s: None if not isinstance(s, str) and s > 900 else s)
|
|
319
|
+
df['gap'] = df['gap'].apply(lambda s: None if isinstance(s, str) else s)
|
|
320
|
+
|
|
321
|
+
# TODO this breaks code if a key has upper case, e.g. gap_L
|
|
322
|
+
df['name'] = df['name'].str.lower() # Make the names lowercase for easy processing
|
|
323
|
+
df['parking'] = 0.025
|
|
324
|
+
if ignore_crystals:
|
|
325
|
+
df = df[~df.name.isin(list(cry.keys()))]
|
|
326
|
+
else:
|
|
327
|
+
for key in cry_fields:
|
|
328
|
+
df[key] = [cry[name][key] if name in cry else defaults[key]
|
|
329
|
+
for name in df['name']]
|
|
330
|
+
if not np.allclose(np.unique(df.thick.values), 0):
|
|
331
|
+
print("Warning: Keyword 'thick' is currently not supported in xcoll! Ignoring.")
|
|
332
|
+
df = df.drop('thick', axis=1)
|
|
333
|
+
df['crystal'] = ['strip' if s==1 else s for s in df['crystal']]
|
|
334
|
+
df['crystal'] = ['quasi-mosaic' if s==2 else s for s in df['crystal']]
|
|
335
|
+
df['side'] = [side[name] if name in side else defaults['side']
|
|
336
|
+
for name in df['name']]
|
|
337
|
+
df['side'] = ['both' if s==0 else s for s in df['side']]
|
|
338
|
+
df['side'] = ['left' if s==1 else s for s in df['side']]
|
|
339
|
+
df['side'] = ['right' if s==2 else s for s in df['side']]
|
|
340
|
+
if not np.allclose(np.unique(df.offset.values), 0):
|
|
341
|
+
print("Warning: Keyword 'offset' is currently not supported in xcoll! Ignoring.")
|
|
342
|
+
df = df.drop('offset', axis=1)
|
|
343
|
+
df = df.set_index('name')
|
|
344
|
+
df = df.replace(np.nan, None)
|
|
345
|
+
|
|
346
|
+
colldict = df.transpose().to_dict()
|
|
347
|
+
# Remove Nonetype families
|
|
348
|
+
colldict = {coll: {kk: vv for kk, vv in coll_settings.items()
|
|
349
|
+
if kk != 'family' or vv is not None}
|
|
350
|
+
for coll, coll_settings in colldict.items()}
|
|
351
|
+
# Remove None gaps and stages if the collimator is assigned a family (they will be set in _parse_dict)
|
|
352
|
+
colldict = {coll: {kk: vv for kk, vv in coll_settings.items()
|
|
353
|
+
if kk != 'gap' or vv is not None or 'family' not in coll_settings}
|
|
354
|
+
for coll, coll_settings in colldict.items()}
|
|
355
|
+
colldict = {coll: {kk: vv for kk, vv in coll_settings.items()
|
|
356
|
+
if kk != 'stage' or vv is not None or 'family' not in coll_settings}
|
|
357
|
+
for coll, coll_settings in colldict.items()}
|
|
358
|
+
|
|
359
|
+
return cls.from_dict({'collimators': colldict, 'families': famdct}, \
|
|
360
|
+
ignore_crystals=ignore_crystals, **kwargs)
|
|
361
|
+
|
|
362
|
+
def to_pandas(self):
|
|
363
|
+
return pd.DataFrame(self._collimator_dict).transpose()
|
|
364
|
+
|
|
365
|
+
def to_yaml(self, out, lhc_style=True):
|
|
366
|
+
"""
|
|
367
|
+
Writes a colldb in memory to disk in the yaml format.
|
|
368
|
+
|
|
369
|
+
> colldb_object.write_to_yaml(<path+name>, lhc_style=Bool)
|
|
370
|
+
|
|
371
|
+
if lhc_style == True, it will add comments assuming that the collimators are named
|
|
372
|
+
as in the lhc.
|
|
373
|
+
|
|
374
|
+
The function can dump b1, b2 and a general bx, however multi-beam functionality is not yet
|
|
375
|
+
added to the collmanager. TODO
|
|
376
|
+
|
|
377
|
+
If any of the dumped keys contains capital letters (e.g. gap_L), it will not be possible
|
|
378
|
+
to load it back into xcoll, since all keys are set to lowercase when importing TODO
|
|
379
|
+
"""
|
|
380
|
+
# Dumps collimator database to a YAML file with optional LHC style formatting
|
|
381
|
+
import re
|
|
382
|
+
|
|
383
|
+
# Local helper functions
|
|
384
|
+
def _format_dict_entry(key, value, spacing='', mapping=False, key_width=15):
|
|
385
|
+
# Formats a dictionary entry into a string for YAML output
|
|
386
|
+
formatted_values = ', '.join(f"{k}: {v}" for k, v in value.items())
|
|
387
|
+
formatted_values = re.sub(r'none', 'null', formatted_values, flags=re.IGNORECASE)
|
|
388
|
+
# Ensure key has a fixed width for alignment
|
|
389
|
+
if mapping:
|
|
390
|
+
formatted_key = f'{key}'.ljust(key_width)
|
|
391
|
+
else:
|
|
392
|
+
formatted_key = f'{key}:'.ljust(key_width)
|
|
393
|
+
#formatted_values = formatted_values.ljust(key_width)
|
|
394
|
+
return f"{spacing}{formatted_key} {{ {formatted_values} }}\n"
|
|
395
|
+
|
|
396
|
+
def _print_values(keys, dct, file, spacing='', mapping=False):
|
|
397
|
+
# Writes formatted dictionary entries to a file
|
|
398
|
+
for key in keys:
|
|
399
|
+
file.write(_format_dict_entry(key, dct[key], spacing=spacing, mapping=mapping))
|
|
400
|
+
|
|
401
|
+
def _print_colls(colls, dcts, beam, file):
|
|
402
|
+
# Filters and formats collimator data, then writes to a file
|
|
403
|
+
coll_items_to_print = ['<<','gap','angle','material','active','length','side']
|
|
404
|
+
file.write(f' {beam}:\n')
|
|
405
|
+
for coll in colls:
|
|
406
|
+
coll_dict = dcts.to_pandas().transpose().to_dict()[coll]
|
|
407
|
+
fam = coll_dict['family']
|
|
408
|
+
fam_keys = []
|
|
409
|
+
if fam is not None:
|
|
410
|
+
fam_keys = dcts._family_dict[fam].keys()
|
|
411
|
+
coll_dict = {**{'<<': '*'+fam}, **coll_dict}
|
|
412
|
+
temp_items_to_print = []
|
|
413
|
+
if coll_dict['crystal'] and str(coll_dict['crystal'])!='nan':
|
|
414
|
+
temp_items_to_print = ['bending_radius','width','height','miscut','crystal']
|
|
415
|
+
# if 'angle_L' in coll_dict and coll_dict['angle_L'] == coll_dict['angle_R']:
|
|
416
|
+
# coll_dict.update({'angle': coll_dict['angle_L']})
|
|
417
|
+
# else:
|
|
418
|
+
# temp_items_to_print = temp_items_to_print + ['angle_L','angle_R']
|
|
419
|
+
# if coll_dict['gap_L'] == coll_dict['gap_R']:
|
|
420
|
+
# coll_dict.update({'gap': coll_dict['gap_L']})
|
|
421
|
+
# elif coll_dict['gap_L'] is None and coll_dict['gap_R'] is not None:
|
|
422
|
+
# coll_dict.update({'gap': coll_dict['gap_R']})
|
|
423
|
+
# elif coll_dict['gap_L'] is not None and coll_dict['gap_R'] is None:
|
|
424
|
+
# coll_dict.update({'gap': coll_dict['gap_L']})
|
|
425
|
+
# else:
|
|
426
|
+
# temp_items_to_print = temp_items_to_print + ['gap_L','gap_R']
|
|
427
|
+
value = {}
|
|
428
|
+
overwritten_keys = coll_dict['overwritten_keys']
|
|
429
|
+
for key, val in coll_dict.items():
|
|
430
|
+
if key == 'active_length':
|
|
431
|
+
key = 'length'
|
|
432
|
+
if (key in coll_items_to_print+temp_items_to_print) and (key not in (set(fam_keys)-set(overwritten_keys))) and (val != 'both'):
|
|
433
|
+
value.update({key: val})
|
|
434
|
+
file.write(_format_dict_entry(coll, value, spacing=' '))
|
|
435
|
+
file.write('\n')
|
|
436
|
+
|
|
437
|
+
LHC_families = ['tcp3', 'tcsg3', 'tcsm3', 'tcla3', 'tcp7', 'tcsg7', 'tcsm7', 'tcla7', 'tcli', 'tdi', 'tcdq', 'tcstcdq', 'tcth1', 'tcth2', 'tcth5', 'tcth8', 'tctv1', 'tctv2', 'tctv5', 'tctv8', 'tclp', 'tcxrp', 'tcryo', 'tcl4', 'tcl5', 'tcl6', 'tct15', 'tct2', 'tct8', 'tcsp', 'tcld']
|
|
438
|
+
with open(f'{out}.yaml', 'w') as file:
|
|
439
|
+
if '_family_dict' in self.__dict__.keys():
|
|
440
|
+
file.write('families:\n')
|
|
441
|
+
if lhc_style:
|
|
442
|
+
printed_families = []
|
|
443
|
+
fams_in_dict = self._family_dict.keys()
|
|
444
|
+
|
|
445
|
+
# Momentum cleaning
|
|
446
|
+
file.write(' # Momentum cleaning\n')
|
|
447
|
+
sel_fam = [fam for fam in LHC_families if re.match('.*3', fam) and (fam in fams_in_dict)]
|
|
448
|
+
printed_families += sel_fam
|
|
449
|
+
_print_values(sel_fam, self._family_dict, file, spacing=' - &', mapping=True)
|
|
450
|
+
|
|
451
|
+
# Betatron cleaning
|
|
452
|
+
file.write(' # Betatron cleaning\n')
|
|
453
|
+
sel_fam = [fam for fam in LHC_families if re.match('.*7', fam) and (fam in fams_in_dict)]
|
|
454
|
+
printed_families += sel_fam
|
|
455
|
+
_print_values(sel_fam, self._family_dict, file, spacing=' - &', mapping=True)
|
|
456
|
+
|
|
457
|
+
# Injection protection
|
|
458
|
+
file.write(' # Injection protection\n')
|
|
459
|
+
sel_fam = [fam for fam in LHC_families if (fam in ['tcli', 'tdi']) and (fam in fams_in_dict)]
|
|
460
|
+
printed_families += sel_fam
|
|
461
|
+
_print_values(sel_fam, self._family_dict, file, spacing=' - &', mapping=True)
|
|
462
|
+
|
|
463
|
+
# Dump protection
|
|
464
|
+
file.write(' # Dump protection\n')
|
|
465
|
+
sel_fam = [fam for fam in LHC_families if (fam in ['tcdq', 'tcsp', 'tcstcdq']) and (fam in fams_in_dict)]
|
|
466
|
+
printed_families += sel_fam
|
|
467
|
+
_print_values(sel_fam, self._family_dict, file, spacing=' - &', mapping=True)
|
|
468
|
+
|
|
469
|
+
# Physics background / debris
|
|
470
|
+
file.write(' # Physics background / debris\n')
|
|
471
|
+
sel_fam = [fam for fam in LHC_families if ((re.match('tc[lt][0-9dp].*', fam)) or (fam in ['tcryo', 'tcxrp'])) and (fam in fams_in_dict)]
|
|
472
|
+
printed_families += sel_fam
|
|
473
|
+
_print_values(sel_fam, self._family_dict, file, spacing=' - &', mapping=True)
|
|
474
|
+
|
|
475
|
+
# Other families
|
|
476
|
+
if set(printed_families) != set(fams_in_dict):
|
|
477
|
+
file.write(' # Other families\n')
|
|
478
|
+
_print_values(set(fams_in_dict) - set(printed_families), self._family_dict, file, spacing=' - &', mapping=True)
|
|
479
|
+
else:
|
|
480
|
+
file.write(' # Families\n')
|
|
481
|
+
_print_values(self._family_dict.keys(), self._family_dict, file, spacing=' - &', mapping=True)
|
|
482
|
+
|
|
483
|
+
# Emittance section
|
|
484
|
+
ex = self.nemitt_x
|
|
485
|
+
ey = self.nemitt_y
|
|
486
|
+
file.write(f'\nemittance:\n x: {ex}\n y: {ey}\n')
|
|
487
|
+
|
|
488
|
+
# Collimators section
|
|
489
|
+
file.write('\ncollimators:\n')
|
|
490
|
+
b1_colls, b2_colls, bx_colls = [], [], []
|
|
491
|
+
for coll in self.to_pandas().index:
|
|
492
|
+
if coll == 'tclia.4r2' or coll == 'tclia.4l8': # TODO: hardcoded!!!
|
|
493
|
+
b1_colls.append(coll)
|
|
494
|
+
b2_colls.append(coll)
|
|
495
|
+
elif coll[-2:] == 'b1':
|
|
496
|
+
b1_colls.append(coll)
|
|
497
|
+
elif coll[-2:] == 'b2':
|
|
498
|
+
b2_colls.append(coll)
|
|
499
|
+
else:
|
|
500
|
+
bx_colls.append(coll)
|
|
501
|
+
|
|
502
|
+
# Handle special cases for collimators
|
|
503
|
+
if (('tclia.4r2' in b1_colls) or ('tclia.4l8' in b1_colls)) and (len(b1_colls) <= 2):
|
|
504
|
+
b1_colls = []
|
|
505
|
+
if (('tclia.4r2' in b2_colls) or ('tclia.4l8' in b2_colls)) and (len(b2_colls) <= 2):
|
|
506
|
+
b2_colls = []
|
|
507
|
+
|
|
508
|
+
# Print collimators for each beam
|
|
509
|
+
if len(b1_colls) > 0:
|
|
510
|
+
_print_colls(b1_colls, self, 'b1', file)
|
|
511
|
+
if len(b2_colls) > 0:
|
|
512
|
+
_print_colls(b2_colls, self, 'b2', file)
|
|
513
|
+
if len(bx_colls) > 0:
|
|
514
|
+
_print_colls(bx_colls, self, 'bx', file)
|
|
515
|
+
print('WARNING -- some collimators could not be assigned to b1 or b2. Tracking might not work with those collimators. Please manually change the output file if necessary.')
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
# ====================================
|
|
519
|
+
# ====== Installing collimators ======
|
|
520
|
+
# ====================================
|
|
521
|
+
|
|
522
|
+
def _get_names_from_line(self, line, names, families):
|
|
523
|
+
if names is None and families is None:
|
|
524
|
+
names = self.collimator_names
|
|
525
|
+
elif names is None:
|
|
526
|
+
names = self.get_collimators_from_family(families)
|
|
527
|
+
elif families is not None:
|
|
528
|
+
names.append(self.get_collimators_from_family(families))
|
|
529
|
+
return list(set(names)) # Remove duplicates
|
|
530
|
+
|
|
531
|
+
def _check_installed(self, line, name, collimator_class):
|
|
532
|
+
# Check that collimator is not installed as different type
|
|
533
|
+
# TODO: automatically replace collimator type and print warning
|
|
534
|
+
if isinstance(line[name], collimator_classes):
|
|
535
|
+
raise ValueError(f"Trying to install {name} as {collimator_class.__name__}, "
|
|
536
|
+
+ f"but it is already installed as {line[name].__class__.__name__}!\n"
|
|
537
|
+
+ f"Please reconstruct the line.")
|
|
538
|
+
# TODO: only allow Marker elements, no Drifts!!
|
|
539
|
+
# How to do this with importing a line for MAD-X or SixTrack...?
|
|
540
|
+
# Maybe we want a DriftCollimator type in Xtrack as a general placeholder
|
|
541
|
+
elif not isinstance(line[name], (xt.Marker, xt.Drift)):
|
|
542
|
+
raise ValueError(f"Trying to install {name} as {collimator_class.__name__}, "
|
|
543
|
+
+ f"but the line element to replace is not an xtrack.Marker "
|
|
544
|
+
+ f"(or xtrack.Drift)!\nPlease check the name, or correct the "
|
|
545
|
+
+ f"element.")
|
|
546
|
+
|
|
547
|
+
def _create_collimator(self, line, collimator_class, name, **kwargs):
|
|
548
|
+
assert issubclass(collimator_class, BaseCollimator)
|
|
549
|
+
self._check_installed(line, name, collimator_class)
|
|
550
|
+
if kwargs.pop('verbose', False):
|
|
551
|
+
print(f"Installing {name:20} as {collimator_class.__name__}")
|
|
552
|
+
el = collimator_class(gap=self[name]['gap'], angle=self[name]['angle'],
|
|
553
|
+
length=self[name]['length'], side=self[name]['side'],
|
|
554
|
+
_tracking=False, **kwargs)
|
|
555
|
+
el.emittance = [self.nemitt_x, self.nemitt_y]
|
|
556
|
+
self._elements[name] = el
|
|
557
|
+
|
|
558
|
+
def _create_crystal(self, line, crystal_class, name, **kwargs):
|
|
559
|
+
assert issubclass(crystal_class, BaseCrystal)
|
|
560
|
+
self._check_installed(line, name, crystal_class)
|
|
561
|
+
if kwargs.pop('verbose', False):
|
|
562
|
+
print(f"Installing {name:20} as {crystal_class.__name__}")
|
|
563
|
+
el = crystal_class(gap=self[name]['gap'], angle=self[name]['angle'],
|
|
564
|
+
length=self[name]['length'], side=self[name]['side'],
|
|
565
|
+
bending_radius=self[name]['bending_radius'],
|
|
566
|
+
width=self[name]['width'], height=self[name]['height'],
|
|
567
|
+
_tracking=False, **kwargs)
|
|
568
|
+
el.emittance = [self.nemitt_x, self.nemitt_y]
|
|
569
|
+
self._elements[name] = el
|
|
570
|
+
|
|
571
|
+
def install_black_absorbers(self, line, *, names=None, families=None, verbose=False, need_apertures=True):
|
|
572
|
+
names = self._get_names_from_line(line, names, families)
|
|
573
|
+
for name in names:
|
|
574
|
+
if self[name]['bending_radius'] is None:
|
|
575
|
+
self._create_collimator(line, BlackAbsorber, name, verbose=verbose)
|
|
576
|
+
else:
|
|
577
|
+
self._create_crystal(line, BlackCrystal, name, verbose=verbose)
|
|
578
|
+
elements = [self._elements[name] for name in names]
|
|
579
|
+
line.collimators.install(names, elements, need_apertures=need_apertures)
|
|
580
|
+
|
|
581
|
+
def install_everest_collimators(self, line, *, names=None, families=None, verbose=False, need_apertures=True):
|
|
582
|
+
names = self._get_names_from_line(line, names, families)
|
|
583
|
+
for name in names:
|
|
584
|
+
mat = SixTrack_to_xcoll(self[name]['material'])
|
|
585
|
+
if self[name]['bending_radius'] is None:
|
|
586
|
+
self._create_collimator(line, EverestCollimator, name, material=mat[0],
|
|
587
|
+
verbose=verbose)
|
|
588
|
+
else:
|
|
589
|
+
self._create_crystal(line, EverestCrystal, name, material=mat[1],
|
|
590
|
+
lattice=self[name]['crystal'], verbose=verbose,
|
|
591
|
+
miscut=self[name]['miscut'])
|
|
592
|
+
elements = [self._elements[name] for name in names]
|
|
593
|
+
line.collimators.install(names, elements, need_apertures=need_apertures)
|
|
594
|
+
|
|
595
|
+
def install_fluka_collimators(self, line, *, names=None, families=None, verbose=False, need_apertures=True,
|
|
596
|
+
fluka_input_file=None, remove_missing=True):
|
|
597
|
+
# Check server
|
|
598
|
+
if FlukaEngine.is_running():
|
|
599
|
+
print("Warning: FLUKA server is already running. Stopping server to install collimators.")
|
|
600
|
+
FlukaEngine.stop_server(fluka_input_file)
|
|
601
|
+
names = self._get_names_from_line(line, names, families)
|
|
602
|
+
for name in names:
|
|
603
|
+
self._create_collimator(line, FlukaCollimator, name, verbose=verbose)
|
|
604
|
+
elements = [self._elements[name] for name in names]
|
|
605
|
+
line.collimators.install(names, elements, need_apertures=need_apertures)
|
|
606
|
+
|
|
607
|
+
# ==================================
|
|
608
|
+
# ====== Accessing attributes ======
|
|
609
|
+
# ==================================
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def collimator_names(self):
|
|
613
|
+
return list(self._collimator_dict.keys())
|
|
614
|
+
|
|
615
|
+
@property
|
|
616
|
+
def collimator_families(self):
|
|
617
|
+
families = {fam: [] for fam in self._family_dict.keys()}
|
|
618
|
+
families["no family"] = []
|
|
619
|
+
for name in self.collimator_names:
|
|
620
|
+
if 'family' not in self[name] or self[name]['family'].lower() == 'unknown':
|
|
621
|
+
families["no family"].append(name)
|
|
622
|
+
else:
|
|
623
|
+
families[self[name]['family']].append(name)
|
|
624
|
+
return families
|
|
625
|
+
|
|
626
|
+
def get_collimators_from_family(self, family):
|
|
627
|
+
if not hasattr(family, '__iter__') and not isinstance(family, str):
|
|
628
|
+
family = [family]
|
|
629
|
+
result = []
|
|
630
|
+
for fam in family:
|
|
631
|
+
if fam not in self.collimator_families:
|
|
632
|
+
raise ValueError(f"Family '{fam}' not found in CollimatorDatabase!")
|
|
633
|
+
result += self.collimator_families[fam]
|
|
634
|
+
return result
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def properties(self):
|
|
638
|
+
return {attr for d in self._collimator_dict.values() for attr in d.keys()}
|
|
639
|
+
|
|
640
|
+
def __getattr__(self, attr):
|
|
641
|
+
if attr in self.properties:
|
|
642
|
+
# TODO: include families
|
|
643
|
+
return {kk: vv.get(attr, None) for kk, vv in self._collimator_dict.items()}
|
|
644
|
+
else:
|
|
645
|
+
raise ValueError(f"Property `{attr}` not present in CollimatorDatabase!")
|
|
646
|
+
|
|
647
|
+
def __getitem__(self, name):
|
|
648
|
+
if name in self._family_dict:
|
|
649
|
+
return self._family_dict[name]
|
|
650
|
+
elif name in self._collimator_dict:
|
|
651
|
+
return self._collimator_dict[name]
|
|
652
|
+
else:
|
|
653
|
+
raise ValueError(f"Family nor collimator `{name}` found in CollimatorDatabase!")
|
|
654
|
+
|
xcoll/general.py
CHANGED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# copyright ############################### #
|
|
2
|
+
# This file is part of the Xcoll package. #
|
|
3
|
+
# Copyright (c) CERN, 2024. #
|
|
4
|
+
# ######################################### #
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from warnings import warn
|
|
8
|
+
|
|
9
|
+
import xtrack as xt
|
|
10
|
+
import xobjects as xo
|
|
11
|
+
import xpart as xp
|
|
12
|
+
|
|
13
|
+
<<<<<<< HEAD
|
|
14
|
+
from .beam_elements import _all_collimator_classes, EverestCrystal, FlukaCollimator
|
|
15
|
+
=======
|
|
16
|
+
from .beam_elements import collimator_classes, EverestCrystal
|
|
17
|
+
>>>>>>> release/v0.6.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def generate_pencil_on_collimator(line, name, num_particles, *, side='+-', pencil_spread=1e-6,
|
|
21
|
+
impact_parameter=0, sigma_z=7.61e-2, twiss=None, longitudinal=None,
|
|
22
|
+
longitudinal_betatron_cut=None, tw=None, **kwargs):
|
|
23
|
+
"""
|
|
24
|
+
Generate a pencil beam on a collimator.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
if not line._has_valid_tracker():
|
|
28
|
+
raise ValueError("Please build tracker before generating pencil distribution!")
|
|
29
|
+
|
|
30
|
+
coll = line[name]
|
|
31
|
+
|
|
32
|
+
if not isinstance(coll, tuple(collimator_classes)):
|
|
33
|
+
raise ValueError("Need to provide a valid collimator!")
|
|
34
|
+
|
|
35
|
+
if coll.optics is None:
|
|
36
|
+
raise ValueError("Need to assign optics to collimators before generating pencil distribution!")
|
|
37
|
+
|
|
38
|
+
num_particles = int(num_particles)
|
|
39
|
+
if len(line.get_elements_of_type(FlukaCollimator)[0]) > 0:
|
|
40
|
+
kwargs.setdefault('_capacity', 2*num_particles)
|
|
41
|
+
|
|
42
|
+
if coll.side == 'left':
|
|
43
|
+
side = '+'
|
|
44
|
+
if coll.side == 'right':
|
|
45
|
+
side = '-'
|
|
46
|
+
|
|
47
|
+
# Define the plane
|
|
48
|
+
angle = coll.angle
|
|
49
|
+
if abs(np.mod(angle-90,180)-90) < 1e-6:
|
|
50
|
+
plane = 'x'
|
|
51
|
+
transv_plane = 'y'
|
|
52
|
+
elif abs(np.mod(angle,180)-90) < 1e-6:
|
|
53
|
+
plane = 'y'
|
|
54
|
+
transv_plane = 'x'
|
|
55
|
+
else:
|
|
56
|
+
raise NotImplementedError("Pencil beam on a skew collimator not yet supported!")
|
|
57
|
+
|
|
58
|
+
if tw is not None:
|
|
59
|
+
warn("The argument tw is deprecated. Please use twiss instead.", FutureWarning)
|
|
60
|
+
if twiss is None:
|
|
61
|
+
twiss = tw
|
|
62
|
+
|
|
63
|
+
if twiss is None:
|
|
64
|
+
twiss = line.twiss()
|
|
65
|
+
|
|
66
|
+
# Is it converging or diverging? # TODO: This might change with a tilt!!!!!!
|
|
67
|
+
is_converging = twiss[f'alf{plane}', name] > 0
|
|
68
|
+
print(f"Collimator {name} is {'con' if is_converging else 'di'}verging.")
|
|
69
|
+
|
|
70
|
+
beam_sizes = twiss.get_beam_covariance(nemitt_x=coll.nemitt_x, nemitt_y=coll.nemitt_y)
|
|
71
|
+
if is_converging:
|
|
72
|
+
# pencil at front of jaw
|
|
73
|
+
sigma = beam_sizes.rows[name:f'{name}>>1'][f'sigma_{plane}'][0]
|
|
74
|
+
sigma_transv = beam_sizes.rows[name:f'{name}>>1'][f'sigma_{transv_plane}'][0]
|
|
75
|
+
tw_at_s = twiss.rows[name]
|
|
76
|
+
at_element = name
|
|
77
|
+
else:
|
|
78
|
+
# pencil at back of jaw
|
|
79
|
+
sigma = beam_sizes.rows[name:f'{name}>>1'][f'sigma_{plane}'][1]
|
|
80
|
+
sigma_transv = beam_sizes.rows[name:f'{name}>>1'][f'sigma_{transv_plane}'][1]
|
|
81
|
+
tw_at_s = twiss.rows[f'{name}>>1']
|
|
82
|
+
at_element = line.element_names[line.element_names.index(name)+1]
|
|
83
|
+
|
|
84
|
+
dr_sigmas = pencil_spread/sigma
|
|
85
|
+
|
|
86
|
+
# Generate 4D coordinates
|
|
87
|
+
# TODO: there is some looping in the calculation here and in xpart. Can it be improved?
|
|
88
|
+
if side == '+-':
|
|
89
|
+
num_plus = int(num_particles/2)
|
|
90
|
+
num_min = int(num_particles - num_plus)
|
|
91
|
+
coords_plus = _generate_4D_pencil_one_jaw(line, name, num_plus, plane, '+', impact_parameter, dr_sigmas, at_element, is_converging, tw_at_s)
|
|
92
|
+
coords_min = _generate_4D_pencil_one_jaw(line, name, num_min, plane, '-', impact_parameter, dr_sigmas, at_element, is_converging, tw_at_s)
|
|
93
|
+
coords = [ [*c_plus, *c_min] for c_plus, c_min in zip(coords_plus, coords_min)]
|
|
94
|
+
else:
|
|
95
|
+
coords = _generate_4D_pencil_one_jaw(line, name, num_particles, plane, side, impact_parameter, dr_sigmas, at_element, is_converging, tw_at_s)
|
|
96
|
+
pencil = coords[0]
|
|
97
|
+
p_pencil = coords[1]
|
|
98
|
+
transverse_norm = coords[2]
|
|
99
|
+
p_transverse_norm = coords[3]
|
|
100
|
+
|
|
101
|
+
# Longitudinal plane
|
|
102
|
+
# TODO: make this more general, make this better
|
|
103
|
+
if longitudinal is None:
|
|
104
|
+
delta = 0
|
|
105
|
+
zeta = 0
|
|
106
|
+
elif longitudinal == 'matched_dispersion':
|
|
107
|
+
raise NotImplementedError
|
|
108
|
+
# if longitudinal_betatron_cut is None:
|
|
109
|
+
# cut = 0
|
|
110
|
+
# else:
|
|
111
|
+
# cut = np.random.uniform(-longitudinal_betatron_cut, longitudinal_betatron_cut,
|
|
112
|
+
# num_particles)
|
|
113
|
+
# delta = generate_delta_from_dispersion(line, name, plane=plane, position_mm=pencil,
|
|
114
|
+
# nemitt_x=nemitt_x, nemitt_y=nemitt_y, twiss=tw,
|
|
115
|
+
# betatron_cut=cut, match_at_front=is_converging)
|
|
116
|
+
# zeta = 0
|
|
117
|
+
elif longitudinal == 'bucket':
|
|
118
|
+
zeta, delta = xp.generate_longitudinal_coordinates(
|
|
119
|
+
num_particles=num_particles, distribution='gaussian', sigma_z=sigma_z, line=line
|
|
120
|
+
)
|
|
121
|
+
elif not hasattr(longitudinal, '__iter__'):
|
|
122
|
+
raise ValueError
|
|
123
|
+
elif len(longitudinal) != 2:
|
|
124
|
+
raise ValueError
|
|
125
|
+
elif isinstance(longitudinal, str):
|
|
126
|
+
raise ValueError
|
|
127
|
+
elif isinstance(longitudinal, dict):
|
|
128
|
+
zeta = longitudinal['zeta']
|
|
129
|
+
delta = longitudinal['delta']
|
|
130
|
+
else:
|
|
131
|
+
zeta = longitudinal[0]
|
|
132
|
+
delta = longitudinal[1]
|
|
133
|
+
|
|
134
|
+
# Build the particles
|
|
135
|
+
if plane == 'x':
|
|
136
|
+
part = xp.build_particles(
|
|
137
|
+
x=pencil, px=p_pencil, y_norm=transverse_norm, py_norm=p_transverse_norm,
|
|
138
|
+
zeta=zeta, delta=delta, nemitt_x=coll.nemitt_x, nemitt_y=coll.nemitt_y,
|
|
139
|
+
line=line, at_element=at_element, _context=coll._buffer.context, **kwargs
|
|
140
|
+
)
|
|
141
|
+
else:
|
|
142
|
+
part = xp.build_particles(
|
|
143
|
+
x_norm=transverse_norm, px_norm=p_transverse_norm, y=pencil, py=p_pencil,
|
|
144
|
+
zeta=zeta, delta=delta, nemitt_x=coll.nemitt_x, nemitt_y=coll.nemitt_y,
|
|
145
|
+
line=line, at_element=at_element, _context=coll._buffer.context, **kwargs
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
part._init_random_number_generator()
|
|
149
|
+
|
|
150
|
+
if not is_converging:
|
|
151
|
+
dri = xt.Drift(length=-coll.length)
|
|
152
|
+
dri.track(part)
|
|
153
|
+
part.start_tracking_at_element -= 1
|
|
154
|
+
part.at_element -= 1
|
|
155
|
+
|
|
156
|
+
return part
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def generate_delta_from_dispersion(line, at_element, *, plane, position_mm, nemitt_x, nemitt_y,
|
|
160
|
+
twiss=None, betatron_cut=0, match_at_front=True):
|
|
161
|
+
if line.tracker is None:
|
|
162
|
+
raise ValueError("Need to build tracker first!")
|
|
163
|
+
if not hasattr(betatron_cut, '__iter__'):
|
|
164
|
+
if hasattr(position_mm, '__iter__'):
|
|
165
|
+
betatron_cut = np.full_like(position_mm, betatron_cut)
|
|
166
|
+
elif not hasattr(position_mm, '__iter__'):
|
|
167
|
+
position_mm = np.full_like(betatron_cut, position_mm)
|
|
168
|
+
elif len(position_mm) != len(betatron_cut):
|
|
169
|
+
raise ValueError
|
|
170
|
+
if plane not in ['x', 'y']:
|
|
171
|
+
raise ValueError("The variable 'plane' needs to be either 'x' or 'y'!")
|
|
172
|
+
|
|
173
|
+
if twiss is None:
|
|
174
|
+
twiss = line.twiss()
|
|
175
|
+
|
|
176
|
+
beam_sizes = twiss.get_beam_covariance(nemitt_x=nemitt_x, nemitt_y=nemitt_y)
|
|
177
|
+
beam_sizes = beam_sizes.rows[at_element:f'{at_element}>>1'][f'sigma_{plane}']
|
|
178
|
+
sigma = beam_sizes[0] if match_at_front else beam_sizes[1]
|
|
179
|
+
delta = (position_mm - betatron_cut*sigma - twiss.rows[at_element][plane])
|
|
180
|
+
delta /= twiss.rows[at_element][f'd{plane}']
|
|
181
|
+
|
|
182
|
+
return delta
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _generate_4D_pencil_one_jaw(line, name, num_particles, plane, side, impact_parameter,
|
|
186
|
+
dr_sigmas, at_element, is_converging, tw_at_s=None):
|
|
187
|
+
coll = line[name]
|
|
188
|
+
|
|
189
|
+
if side == '+':
|
|
190
|
+
if is_converging:
|
|
191
|
+
if isinstance(coll, EverestCrystal):
|
|
192
|
+
pencil_pos = coll.jaw_U + impact_parameter
|
|
193
|
+
else:
|
|
194
|
+
pencil_pos = coll.jaw_LU + impact_parameter
|
|
195
|
+
else:
|
|
196
|
+
if isinstance(coll, EverestCrystal):
|
|
197
|
+
pencil_pos = coll.jaw_D - impact_parameter
|
|
198
|
+
else:
|
|
199
|
+
pencil_pos = coll.jaw_LD + impact_parameter
|
|
200
|
+
elif side == '-':
|
|
201
|
+
if is_converging:
|
|
202
|
+
if isinstance(coll, EverestCrystal):
|
|
203
|
+
pencil_pos = coll.jaw_U - impact_parameter
|
|
204
|
+
else:
|
|
205
|
+
pencil_pos = coll.jaw_RU - impact_parameter
|
|
206
|
+
else:
|
|
207
|
+
if isinstance(coll, EverestCrystal):
|
|
208
|
+
pencil_pos = coll.jaw_D + impact_parameter
|
|
209
|
+
else:
|
|
210
|
+
pencil_pos = coll.jaw_RD - impact_parameter
|
|
211
|
+
|
|
212
|
+
# Collimator plane: generate pencil distribution
|
|
213
|
+
pencil, p_pencil = xp.generate_2D_pencil_with_absolute_cut(
|
|
214
|
+
num_particles, plane=plane, absolute_cut=pencil_pos, line=line,
|
|
215
|
+
dr_sigmas=dr_sigmas, nemitt_x=coll.nemitt_x, nemitt_y=coll.nemitt_y,
|
|
216
|
+
at_element=at_element, side=side, twiss=tw_at_s
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Other plane: generate gaussian distribution in normalized coordinates
|
|
220
|
+
transverse_norm = np.random.normal(size=num_particles)
|
|
221
|
+
p_transverse_norm = np.random.normal(size=num_particles)
|
|
222
|
+
|
|
223
|
+
return [pencil, p_pencil, transverse_norm, p_transverse_norm]
|
|
224
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: xcoll
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.9
|
|
4
4
|
Summary: Xsuite collimation package
|
|
5
5
|
Home-page: https://github.com/xsuite/xcoll
|
|
6
6
|
License: Apache 2.0
|
|
@@ -18,11 +18,10 @@ Provides-Extra: tests
|
|
|
18
18
|
Requires-Dist: numpy (>=1.0)
|
|
19
19
|
Requires-Dist: pandas (>=1.4)
|
|
20
20
|
Requires-Dist: ruamel-yaml (>=0.17.31,<0.18.0) ; extra == "tests"
|
|
21
|
-
Requires-Dist: xdeps (>=0.
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist: xtrack (>=0.36.5)
|
|
21
|
+
Requires-Dist: xdeps (>=0.7.3)
|
|
22
|
+
Requires-Dist: xobjects (>=0.4.5)
|
|
23
|
+
Requires-Dist: xpart (>=0.19.1)
|
|
24
|
+
Requires-Dist: xtrack (>=0.69.7)
|
|
26
25
|
Project-URL: Repository, https://github.com/xsuite/xcoll
|
|
27
26
|
Description-Content-Type: text/markdown
|
|
28
27
|
|
|
@@ -3,6 +3,7 @@ NOTICE,sha256=6DO_E7WCdRKc42vUoVVBPGttvQi4mRt9fAcxj9u8zy8,74
|
|
|
3
3
|
xcoll/__init__.py,sha256=b_61vh5irhf5fPmqTFJlyhNSt4rmftXg9uXPIEpgVB4,1612
|
|
4
4
|
xcoll/_manager.py,sha256=9NQKaNxZR2I1ChMVBeKQc0A8h6W8gVgRRg72a5NgbXU,808
|
|
5
5
|
xcoll/beam_elements/__init__.py,sha256=06bU8rzvlUPhcvwpaUippddm5IChpcCHBvpmvXJQU74,1122
|
|
6
|
+
xcoll/beam_elements/__init__.py.orig,sha256=XZMbC-0IzLmKN4LXXT5zm2zOXzRWvkpuElCBGE3VWtw,1507
|
|
6
7
|
xcoll/beam_elements/absorber.py,sha256=efK6gyUgD4x_FnBLpMR7-5_HCdp_753nkYikcdn6ulw,2502
|
|
7
8
|
xcoll/beam_elements/base.py,sha256=6scwhofls5AHPJcUyEbTJOJ8U86EQU4S7BB7NGoE_j0,52728
|
|
8
9
|
xcoll/beam_elements/blowup.py,sha256=gBXdlISvoDiMjXVpA77ls5QdAU3H9krwvFt2bSW_NII,8029
|
|
@@ -16,10 +17,12 @@ xcoll/beam_elements/elements_src/everest_crystal.h,sha256=WwwNF6it7TuOimhpSXJa7U
|
|
|
16
17
|
xcoll/beam_elements/everest.py,sha256=PA_VWpnPrIuO1xN__eKyT_ejbGZK7p93QHDVi3re7cM,8541
|
|
17
18
|
xcoll/beam_elements/monitor.py,sha256=zzMdN3JMFSAs-30_ntRvd5qZGdsXfGtColhiFDuMcIk,16928
|
|
18
19
|
xcoll/colldb.py,sha256=lEpkDRAT54szT9i7-jbTMRvkuW_0W2piZCdDaungcIs,30983
|
|
19
|
-
xcoll/
|
|
20
|
+
xcoll/colldb.py.orig,sha256=v4AXbY44o_mlealtFXzMzDNLVt-Hkt_ZkjKs5QUJlec,31898
|
|
21
|
+
xcoll/general.py,sha256=-xLa-F2jd_1I3hIhlntMYsz_B-DZw_bj0qqh1SR1Cno,534
|
|
20
22
|
xcoll/headers/checks.h,sha256=qdXsOTBOK1MwW6bdFF93j4yE648mcDtEv5rGN1w9sfk,1582
|
|
21
23
|
xcoll/headers/particle_states.h,sha256=DZa_ZSaJrjnA8aHFriZKfRCkArQ8nK1t445MRwevDtA,840
|
|
22
24
|
xcoll/initial_distribution.py,sha256=x5G4LTXn4boEg5jBFrQCk_l759h91XiAUhDTdcUvLkc,8779
|
|
25
|
+
xcoll/initial_distribution.py.orig,sha256=NOtDtAoNYx8p5l-hUO9ueafl3jtbIzGU0z8-xHtTX_E,9026
|
|
23
26
|
xcoll/install.py,sha256=SxEFQnhWXlsXyPBIo847q6wPgId_f5ZtFRR1awGbkjc,2108
|
|
24
27
|
xcoll/interaction_record/__init__.py,sha256=UFoLiKa-z2oX7YoszP-7Vgdt1nM6kT382v1CaIu8_u0,50
|
|
25
28
|
xcoll/interaction_record/interaction_record.py,sha256=ixsQtVZn71vVEuTAA27a2NWcZZZ8iAcWFOa58bcWEQU,13271
|
|
@@ -406,8 +409,8 @@ xcoll/scattering_routines/geometry/objects.h,sha256=A5ktGvVlSkC4hUsI_PQFsE80CuDw
|
|
|
406
409
|
xcoll/scattering_routines/geometry/rotation.h,sha256=lO3RaQBys9r0ROMjR8T8Rr7UsIEm-H9_C_70Nwz4MXY,701
|
|
407
410
|
xcoll/scattering_routines/geometry/segments.h,sha256=7nKnnin2ByxkKyaYwGvFaqgLQg5uBba4CdLHL7L3iQs,7667
|
|
408
411
|
xcoll/scattering_routines/geometry/sort.h,sha256=b1MkFO2ddzv1fWGeQzsLuz46qo2pKyRSXHjoAEVU7Ts,5763
|
|
409
|
-
xcoll-0.5.
|
|
410
|
-
xcoll-0.5.
|
|
411
|
-
xcoll-0.5.
|
|
412
|
-
xcoll-0.5.
|
|
413
|
-
xcoll-0.5.
|
|
412
|
+
xcoll-0.5.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
413
|
+
xcoll-0.5.9.dist-info/METADATA,sha256=ZBJuoHNhd-lFobMiSbj1SmSZ8yyie9OC_QTUo2Af4PI,2675
|
|
414
|
+
xcoll-0.5.9.dist-info/NOTICE,sha256=6DO_E7WCdRKc42vUoVVBPGttvQi4mRt9fAcxj9u8zy8,74
|
|
415
|
+
xcoll-0.5.9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
416
|
+
xcoll-0.5.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|