xcoll 0.5.10__py3-none-any.whl → 0.5.12__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.

@@ -12,8 +12,8 @@ from ..interaction_record import InteractionRecord
12
12
  from ..general import _pkg_root
13
13
 
14
14
 
15
- OPEN_JAW = 3.
16
- OPEN_GAP = 999.
15
+ OPEN_JAW = 3
16
+ OPEN_GAP = 999
17
17
 
18
18
 
19
19
  class InvalidXcoll(xt.BeamElement):
@@ -659,6 +659,11 @@ class BaseCollimator(BaseBlock):
659
659
  self._nemitt_x = val
660
660
  self._apply_optics()
661
661
 
662
+ @property
663
+ def gemitt_x(self):
664
+ if self.nemitt_x is not None and self.optics_ready():
665
+ return self.nemitt_x / self.optics['beta_gamma_rel']
666
+
662
667
  @property
663
668
  def nemitt_y(self):
664
669
  if self._nemitt_y == 0:
@@ -674,6 +679,11 @@ class BaseCollimator(BaseBlock):
674
679
  self._nemitt_y = val
675
680
  self._apply_optics()
676
681
 
682
+ @property
683
+ def gemitt_y(self):
684
+ if self.nemitt_y is not None and self.optics_ready():
685
+ return self.nemitt_y / self.optics['beta_gamma_rel']
686
+
677
687
  @property
678
688
  def emittance(self):
679
689
  if self.nemitt_x is not None and self.nemitt_y is not None:
@@ -734,8 +744,8 @@ class BaseCollimator(BaseBlock):
734
744
  alfy = self.optics[self.align]['alfy'][0]
735
745
  betx = self.optics[self.align]['betx'][0]
736
746
  bety = self.optics[self.align]['bety'][0]
737
- divx = -np.sqrt(self.nemitt_x/self.optics['beta_gamma_rel']/betx)*alfx
738
- divy = -np.sqrt(self.nemitt_y/self.optics['beta_gamma_rel']/bety)*alfy
747
+ divx = -np.sqrt(self.gemitt_x/betx)*alfx
748
+ divy = -np.sqrt(self.gemitt_y/bety)*alfy
739
749
  if hasattr(self, '_cos_zL'):
740
750
  if self.side != 'right':
741
751
  return divx if abs(self.angle_L) < 1e-6 else divy
@@ -1256,6 +1266,10 @@ class BaseCrystal(BaseBlock):
1256
1266
  def nemitt_x(self, val):
1257
1267
  BaseCollimator.nemitt_x.fset(self, val)
1258
1268
 
1269
+ @property
1270
+ def gemitt_x(self):
1271
+ return BaseCollimator.gemitt_x.fget(self)
1272
+
1259
1273
  @property
1260
1274
  def nemitt_y(self):
1261
1275
  return BaseCollimator.nemitt_y.fget(self)
@@ -1264,6 +1278,10 @@ class BaseCrystal(BaseBlock):
1264
1278
  def nemitt_y(self, val):
1265
1279
  BaseCollimator.nemitt_y.fset(self, val)
1266
1280
 
1281
+ @property
1282
+ def gemitt_y(self):
1283
+ return BaseCollimator.gemitt_y.fget(self)
1284
+
1267
1285
  @property
1268
1286
  def emittance(self):
1269
1287
  return BaseCollimator.emittance.fget(self)
xcoll/general.py CHANGED
@@ -12,5 +12,5 @@ citation = "F.F. Van der Veken, et al.: Recent Developments with the New Tools f
12
12
  # ======================
13
13
  # Do not change
14
14
  # ======================
15
- __version__ = '0.5.10'
15
+ __version__ = '0.5.12'
16
16
  # ======================
@@ -10,10 +10,10 @@
10
10
  #define XC_LOST_ON_EVEREST_COLL -331
11
11
  #define XC_LOST_ON_EVEREST_CRYSTAL -332
12
12
  #define XC_LOST_ON_FLUKA_BLOCK -333
13
- #define XC_LOST_ON_FLUKA -334
13
+ #define XC_LOST_ON_FLUKA_COLL -334
14
14
  #define XC_LOST_ON_FLUKA_CRYSTAL -335
15
15
  #define XC_LOST_ON_GEANT4_BLOCK -336
16
- #define XC_LOST_ON_GEANT4 -337
16
+ #define XC_LOST_ON_GEANT4_COLL -337
17
17
  #define XC_LOST_ON_GEANT4_CRYSTAL -338
18
18
  #define XC_LOST_ON_ABSORBER -340
19
19
 
@@ -57,8 +57,14 @@ def generate_pencil_on_collimator(line, name, num_particles, *, side='+-', penci
57
57
  if twiss is None:
58
58
  twiss = line.twiss()
59
59
 
60
- # Is it converging or diverging? # TODO: This might change with a tilt!!!!!!
61
- is_converging = twiss[f'alf{plane}', name] > 0
60
+ # Is it converging or diverging?
61
+ # TODO: dispersion might change this
62
+ # TODO: this should be checked jaw by jaw (we are currently checking the left jaw - watch out for sign of tilt of right jaw)
63
+ # TODO: skew collimators
64
+ tilt = coll.tilt[0] if isinstance(coll.tilt, list) else coll.tilt
65
+ betatron_angle = coll.gap * coll.divergence
66
+ tolerance_tilt = 1e-12 # 0.1 urad tolerance on jaw tilt => we prioritise converging
67
+ is_converging = tilt + tolerance_tilt >= betatron_angle
62
68
  print(f"Collimator {name} is {'con' if is_converging else 'di'}verging.")
63
69
 
64
70
  beam_sizes = twiss.get_beam_covariance(nemitt_x=coll.nemitt_x, nemitt_y=coll.nemitt_y)
@@ -181,25 +187,21 @@ def _generate_4D_pencil_one_jaw(line, name, num_particles, plane, side, impact_p
181
187
  coll = line[name]
182
188
 
183
189
  if side == '+':
184
- if is_converging:
185
- if isinstance(coll, EverestCrystal):
186
- pencil_pos = coll.jaw_U + impact_parameter
187
- else:
188
- pencil_pos = coll.jaw_LU + impact_parameter
190
+ if isinstance(coll, EverestCrystal):
191
+ # A pencil on the crystal should always be upstream
192
+ pencil_pos = coll.jaw_U + impact_parameter
189
193
  else:
190
- if isinstance(coll, EverestCrystal):
191
- pencil_pos = coll.jaw_D - impact_parameter
194
+ if is_converging:
195
+ pencil_pos = coll.jaw_LU + impact_parameter
192
196
  else:
193
197
  pencil_pos = coll.jaw_LD + impact_parameter
194
198
  elif side == '-':
195
- if is_converging:
196
- if isinstance(coll, EverestCrystal):
197
- pencil_pos = coll.jaw_U - impact_parameter
198
- else:
199
- pencil_pos = coll.jaw_RU - impact_parameter
199
+ if isinstance(coll, EverestCrystal):
200
+ # A pencil on the crystal should always be upstream
201
+ pencil_pos = coll.jaw_U - impact_parameter
200
202
  else:
201
- if isinstance(coll, EverestCrystal):
202
- pencil_pos = coll.jaw_D + impact_parameter
203
+ if is_converging:
204
+ pencil_pos = coll.jaw_RU - impact_parameter
203
205
  else:
204
206
  pencil_pos = coll.jaw_RD - impact_parameter
205
207
 
xcoll/line_tools.py CHANGED
@@ -33,7 +33,7 @@ class XcollScatteringAPI:
33
33
  if nemitt_y is None:
34
34
  nemitt_y = el.nemitt_y
35
35
  if not np.isclose(el.nemitt_x, nemitt_x) \
36
- or not np.isclose(el.nemitt_x, nemitt_x):
36
+ or not np.isclose(el.nemitt_y, nemitt_y):
37
37
  raise ValueError("Not all collimators have the same "
38
38
  + "emittance. This is not supported.")
39
39
  if hasattr(el, 'enable_scattering'):
@@ -78,7 +78,6 @@ class XcollCollimatorAPI:
78
78
 
79
79
  # Verify elements
80
80
  for el in elements:
81
- print(el.__class__)
82
81
  assert isinstance(el, block_classes)
83
82
  el._tracking = False
84
83
 
@@ -243,7 +242,7 @@ class XcollCollimatorAPI:
243
242
  tw_upstream, tw_downstream = self.get_optics_at(names, twiss=twiss)
244
243
  beta_gamma_rel = self.line.particle_ref._xobject.gamma0[0]*self.line.particle_ref._xobject.beta0[0]
245
244
  for coll in names:
246
- self.line[coll].assign_optics(name=coll, nemitt_x=nemitt_x, nemitt_y=nemitt_x, twiss_upstream=tw_upstream,
245
+ self.line[coll].assign_optics(name=coll, nemitt_x=nemitt_x, nemitt_y=nemitt_y, twiss_upstream=tw_upstream,
247
246
  twiss_downstream=tw_downstream, beta_gamma_rel=beta_gamma_rel)
248
247
 
249
248
  def open(self, names=None):
@@ -282,22 +281,22 @@ class XcollCollimatorAPI:
282
281
 
283
282
  def assign_optics_to_collimators(line, nemitt_x=None, nemitt_y=None, twiss=None):
284
283
  warn("The function xcoll.assign_optics_to_collimators() is deprecated and will be "
285
- + "removed in the future. Please use line.scattering.assign_optics() instead.", FutureWarning)
284
+ + "removed in the future. Please use line.collimators.assign_optics() instead.", FutureWarning)
286
285
  line.collimators.assign_optics(nemitt_x=nemitt_x, nemitt_y=nemitt_y, twiss=twiss)
287
286
 
288
287
  def get_optics_at(names, *, twiss=None, line=None):
289
288
  warn("The function xcoll.get_optics_at() is deprecated and will be "
290
- + "removed in the future. Please use line.scattering.get_optics_at() instead.", FutureWarning)
289
+ + "removed in the future. Please use line.collimators.get_optics_at() instead.", FutureWarning)
291
290
  return line.collimators.get_optics_at(names=names, twiss=twiss)
292
291
 
293
292
  def open_collimators(line, names=None):
294
293
  warn("The function xcoll.open_collimators() is deprecated and will be "
295
- + "removed in the future. Please use line.scattering.open_collimators() instead.", FutureWarning)
294
+ + "removed in the future. Please use line.collimators.open_collimators() instead.", FutureWarning)
296
295
  line.collimators.open(names=names)
297
296
 
298
297
  def send_to_parking(line, names=None):
299
298
  warn("The function xcoll.send_to_parking() is deprecated and will be "
300
- + "removed in the future. Please use line.scattering.send_to_parking() instead.", FutureWarning)
299
+ + "removed in the future. Please use line.collimators.send_to_parking() instead.", FutureWarning)
301
300
  line.collimators.to_parking(names=names)
302
301
 
303
302
  def enable_scattering(line):
@@ -0,0 +1,403 @@
1
+ # copyright ############################### #
2
+ # This file is part of the Xcoll Package. #
3
+ # Copyright (c) CERN, 2024. #
4
+ # ######################################### #
5
+
6
+ import os
7
+ import numpy as np
8
+ import shutil
9
+
10
+ import xobjects as xo
11
+ import xpart as xp
12
+ import xtrack as xt
13
+ try:
14
+ # TODO: once xaux is in Xsuite keep only this
15
+ from xaux import ClassProperty, ClassPropertyMeta, FsPath, singleton
16
+ except ImportError:
17
+ from ..xaux import ClassProperty, ClassPropertyMeta, FsPath, singleton
18
+
19
+
20
+ class BaseEngineMeta(xo.hybrid_class.MetaHybridClass, ClassPropertyMeta):
21
+ pass
22
+
23
+ @singleton
24
+ class BaseEngine(xo.HybridClass, metaclass=BaseEngineMeta):
25
+ _xofields = {
26
+ '_particle_ref': xp.Particles,
27
+ '_seed': xo.UInt64,
28
+ '_capacity': xo.Int64,
29
+ }
30
+
31
+ _int32 = False
32
+ _element_classes = None
33
+ _only_protons = False
34
+ _uses_input_file = False
35
+ _uses_run_folder = False
36
+
37
+ def __init__(self, **kwargs):
38
+ if not self._initialised:
39
+ if np.any([key[0] != '_' for key in self._xofields.keys()]):
40
+ raise ValueError(f"All fields in `{self.__class__.__name__}._xofields` have "
41
+ + f"to start with an underscore! This is to ensure to work "
42
+ + f"correctly with `ClassProperty`.")
43
+ if '_xobject' not in kwargs:
44
+ # Initialise defaults
45
+ self._cwd = None
46
+ self._line = None
47
+ self._verbose = False
48
+ self._input_file = None
49
+ self._element_dict = {}
50
+ self._warning_given = False
51
+ self._tracking_initialised = False
52
+ kwargs.setdefault('_particle_ref', xp.Particles())
53
+ kwargs.setdefault('_seed', 0)
54
+ kwargs.setdefault('_capacity', 0)
55
+ filtered_kwargs = {}
56
+ remaining_kwargs = {}
57
+ for key, value in kwargs.items():
58
+ if key in self._xofields.keys() or key == '_xobject':
59
+ filtered_kwargs[key] = value
60
+ else:
61
+ remaining_kwargs[key] = value
62
+ super().__init__(**filtered_kwargs)
63
+ kwargs = remaining_kwargs
64
+ self._initialised = True
65
+ # Apply kwargs
66
+ for kk, vv in kwargs.items():
67
+ if not hasattr(self.__class__, kk):
68
+ raise ValueError(f"Invalid attribute {kk} for {self.__class__.__name__}!")
69
+ setattr(self, kk, vv)
70
+
71
+ def __del__(self, *args, **kwargs):
72
+ self.stop(warn=False)
73
+
74
+
75
+ def _warn(self, error=None):
76
+ if not self._warning_given:
77
+ print(f"Warning: Failed to import {self.__class__.__name__} environment "
78
+ + f" (did you compile?).\n{self.__class__.__name__.replace('Engine', '')} "
79
+ + f"elements will be installed but are not trackable.\n", flush=True)
80
+ if error:
81
+ print(error, flush=True)
82
+ self._warning_given = True
83
+
84
+
85
+ # ==================
86
+ # === Properties ===
87
+ # ==================
88
+
89
+
90
+ @ClassProperty
91
+ def name(cls):
92
+ return cls.__name__.replace('Engine', '').lower()
93
+
94
+ @ClassProperty
95
+ def verbose(cls):
96
+ return cls.get_self()._verbose
97
+
98
+ @verbose.setter
99
+ def verbose(cls, val):
100
+ cls.get_self()._verbose = val
101
+
102
+ @ClassProperty
103
+ def line(cls):
104
+ return cls.get_self()._line
105
+
106
+ @line.setter
107
+ def line(cls, val):
108
+ if not val is None and not isinstance(val, xt.Line):
109
+ raise ValueError("`line` has to be an xt.Line object!")
110
+ cls.get_self()._line = val
111
+
112
+ @line.deleter
113
+ def line(cls):
114
+ cls.get_self()._line = None
115
+
116
+ @ClassProperty
117
+ def particle_ref(cls):
118
+ self = cls.get_self()
119
+ initial = xp.Particles().to_dict()
120
+ current = self._particle_ref.to_dict()
121
+ if xt.line._dicts_equal(initial, current):
122
+ return None
123
+ else:
124
+ return self._particle_ref
125
+
126
+ @particle_ref.setter
127
+ def particle_ref(cls, val):
128
+ self = cls.get_self()
129
+ if val is None:
130
+ self._particle_ref = xp.Particles()
131
+ else:
132
+ if not isinstance(val, xp.Particles):
133
+ raise ValueError("`particle_ref` has to be an xp.Particles object!")
134
+ if val._capacity > 1:
135
+ raise ValueError("`particle_ref` has to be a single particle!")
136
+ if val.pdg_id[0] == 0:
137
+ if cls._only_protons:
138
+ val.pdg_id[0] = xp.get_pdg_id_from_name('proton')
139
+ else:
140
+ raise ValueError("`particle_ref` needs to have a valid pdg_id")
141
+ elif cls._only_protons and val.pdg_id[0] != xp.get_pdg_id_from_name('proton'):
142
+ raise ValueError("{cls.__name__} only supports protons!")
143
+ self._particle_ref = val
144
+
145
+ @particle_ref.deleter
146
+ def particle_ref(cls):
147
+ cls.get_self()._particle_ref = xp.Particles()
148
+
149
+ @ClassProperty
150
+ def capacity(cls):
151
+ self = cls.get_self()
152
+ if self._capacity == 0:
153
+ return None
154
+ else:
155
+ return int(self._capacity)
156
+
157
+ @capacity.setter
158
+ def capacity(cls, val):
159
+ if val is None:
160
+ val = 0
161
+ cls.get_self()._capacity = int(val)
162
+
163
+ @capacity.deleter
164
+ def capacity(cls):
165
+ raise ValueError("Not allowed.")
166
+
167
+ @ClassProperty
168
+ def seed(cls):
169
+ self = cls.get_self()
170
+ if self._seed == 0:
171
+ return None
172
+ else:
173
+ return self._seed
174
+
175
+ @seed.setter
176
+ def seed(cls, val):
177
+ if val is None:
178
+ val = 0
179
+ val = int(val)
180
+ if cls._int32:
181
+ new_val = np.uint32(val)
182
+ else:
183
+ new_val = np.uint64(val)
184
+ if new_val != val:
185
+ print(f"Warning: type change for seed {val}. Using {new_val}.")
186
+ cls.get_self()._seed = new_val
187
+
188
+ @seed.deleter
189
+ def seed(cls):
190
+ cls.get_self()._seed = 0
191
+
192
+ @ClassProperty
193
+ def input_file(cls):
194
+ return cls.get_self()._input_file
195
+
196
+
197
+ # ======================
198
+ # === Public Methods ===
199
+ # ======================
200
+
201
+
202
+ @classmethod
203
+ def start(cls, *, line=None, elements=None, names=None, cwd=None, seed=None,
204
+ particle_ref=None, input_file=None, **kwargs):
205
+ self = cls.get_self(**kwargs)
206
+ if self.is_running() is None:
207
+ raise NotImplementedError(f"Need to implement `is_running` for {cls.__name__}!")
208
+ elif self.is_running() is True:
209
+ if self.verbose:
210
+ print("Engine already running.", flush=True)
211
+ return
212
+
213
+ self._starting_engine = True # We need this to allow changing the element settings which otherwise are locked
214
+ self._use_seed(seed)
215
+ self._use_line(line)
216
+ self._use_particle_ref(particle_ref)
217
+ self._sync_line_particle_ref()
218
+ self._get_elements(line=line, elements=elements, names=names)
219
+ self._set_cwd(cwd=cwd)
220
+ self._use_input_file(input_file=input_file, **kwargs)
221
+ self.clean_output_files()
222
+ self._starting_engine = False
223
+
224
+
225
+ @classmethod
226
+ def stop(cls, clean=False, **kwargs):
227
+ self = cls.get_self(**kwargs)
228
+ if hasattr(self, '_old_seed'):
229
+ self.seed = self._old_seed
230
+ del self._old_seed
231
+ if hasattr(self, '_old_line'):
232
+ self.line = self._old_line
233
+ del self._old_line
234
+ if hasattr(self, '_old_particle_ref'):
235
+ self.particle_ref = self._old_particle_ref
236
+ del self._old_particle_ref
237
+ self._sync_line_particle_ref()
238
+ if hasattr(self, '_old_cwd') and self._old_cwd is not None:
239
+ os.chdir(self._old_cwd)
240
+ del self._old_cwd
241
+ if clean:
242
+ self.clean_output_files(clean_all=True)
243
+ self._cwd = None
244
+ self._input_file = None
245
+ self._element_dict = {}
246
+ self._warning_given = False
247
+ self._tracking_initialised = False
248
+
249
+
250
+ @classmethod
251
+ def assert_particle_ref(cls, **kwargs):
252
+ if cls.get_self(**kwargs).particle_ref is None:
253
+ raise ValueError(f"{cls.__name__} reference particle not set!")
254
+
255
+
256
+ # =======================
257
+ # === Private Methods ===
258
+ # =======================
259
+
260
+ # For all the following fields, they can either be set in advance on the engine,
261
+ # or they can be set when the engine is started. In the latter case, the values
262
+ # are temporary and the original will be restored when the engine is stopped.
263
+
264
+ def _use_line(self, line=None):
265
+ self._old_line = self.line
266
+ self.line = line
267
+
268
+ def _use_seed(self, seed=None):
269
+ self._old_seed = self.seed
270
+ if seed is not None:
271
+ self.seed = seed
272
+ else:
273
+ if self.seed is None:
274
+ if self._int32:
275
+ self.seed = np.random.randint(0, int(2**32))
276
+ else:
277
+ self.seed = np.random.randint(0, int(2**64))
278
+ if self.verbose:
279
+ print(f"Using seed {self.seed}.")
280
+
281
+ def _use_particle_ref(self, particle_ref=None):
282
+ # Prefer: provided particle_ref > existing particle_ref > particle_ref from line
283
+ self._old_particle_ref = self.particle_ref
284
+ if particle_ref is not None:
285
+ self.particle_ref = particle_ref
286
+ elif self.particle_ref is None:
287
+ if self.line is None or not hasattr(self.line, 'particle_ref') \
288
+ or self.line.particle_ref is None:
289
+ raise ValueError("Need to provide either a line with a reference "
290
+ + "particle, or `particle_ref`.")
291
+ self.particle_ref = self.line.particle_ref
292
+ if self.verbose:
293
+ print(f"Using {xp.get_name_from_pdg_id(self.particle_ref.pdg_id[0])}.")
294
+
295
+ def _sync_line_particle_ref(self):
296
+ if self.line is None:
297
+ return
298
+ if self.line.particle_ref is not None \
299
+ and not xt.line._dicts_equal(self.line.particle_ref.to_dict(),
300
+ self.particle_ref.to_dict()):
301
+ overwrite_particle_ref_in_line = True
302
+ if overwrite_particle_ref_in_line:
303
+ print("Warning: Found different reference particle in line! Temporarily overwritten.")
304
+ self.line.particle_ref = self.particle_ref
305
+
306
+ def _get_elements(self, line=None, elements=None, names=None):
307
+ if self._element_classes is None:
308
+ raise NotImplementedError(f"{self.__class__.__name__} needs to define `_element_classes`!")
309
+ if line is None:
310
+ if elements is None:
311
+ raise ValueError("Need to provide either `line` or `elements`.")
312
+ if not hasattr(elements, '__iter__') or isinstance(elements, str):
313
+ elements = [elements]
314
+ if names is None:
315
+ names = [f"{self.__class__.name}_el_{i}" for i, _ in enumerate(elements)]
316
+ else:
317
+ if not hasattr(names, '__iter__') or isinstance(names, str):
318
+ names = [names]
319
+ if len(names) != len(elements):
320
+ raise ValueError("Length of `elements` and `names` doesn't match.")
321
+ else:
322
+ if elements is not None:
323
+ raise ValueError("Cannot provide both `line` and `elements`.")
324
+ if names is None:
325
+ elements, names = line.get_elements_of_type(self._element_classes)
326
+ else:
327
+ if not hasattr(names, '__iter__') or isinstance(names, str):
328
+ names = [names]
329
+ elements = [line[name] for name in names]
330
+ elements = [el for el in elements if el.active and el.jaw is not None]
331
+ names = [name for el, name in zip(elements,names) if el.active and el.jaw is not None]
332
+ for el in elements:
333
+ if not isinstance(el, self._element_classes):
334
+ raise ValueError(f"Element {el} is not a "
335
+ + ", or a ".join([c.__name__ for c in self._element_classes])
336
+ + ".")
337
+ if len(elements) == 0:
338
+ raise ValueError(f"No active {self.name} elements found!")
339
+ self._element_dict = dict(zip(names, elements))
340
+
341
+ def _set_cwd(self, cwd):
342
+ if self._uses_run_folder:
343
+ if cwd is not None:
344
+ cwd = FsPath(cwd).expanduser().resolve()
345
+ else:
346
+ # TODO: use xaux.ranID here
347
+ import base64
348
+ ran = base64.urlsafe_b64encode(os.urandom(8)).decode('utf-8')
349
+ ran_str = ''.join(c if c.isalnum() else 'X' for c in ran)
350
+ cwd = FsPath.cwd() / f'{self.name}_run_{ran_str}'
351
+ self._cwd = cwd
352
+ cwd.mkdir(parents=True, exist_ok=True)
353
+ self._old_cwd = FsPath.cwd()
354
+ os.chdir(cwd)
355
+
356
+ def _use_input_file(self, input_file=None, **kwargs):
357
+ if self._uses_input_file:
358
+ if input_file is None:
359
+ if not hasattr(self, 'generate_input_file'):
360
+ raise NotImplementedError("Need to implement `generate_input_file`"
361
+ "for {cls.__name__}!")
362
+ input_file = self.generate_input_file(**kwargs)
363
+ if not hasattr(input_file, '__iter__') or isinstance(input_file, str):
364
+ # Some engines might need multiple input files (like Fluka)
365
+ input_file = [input_file]
366
+ input_file = [FsPath(f).expanduser().resolve() for f in input_file]
367
+ new_files = []
368
+ for file in input_file:
369
+ if not file.exists():
370
+ raise ValueError(f"Input file {file.as_posix()} not found!")
371
+ if file.parent != FsPath.cwd():
372
+ shutil.copy(file, FsPath.cwd())
373
+ new_files.append(FsPath.cwd() / file.name)
374
+ else:
375
+ new_files.append(file)
376
+ self._input_file = new_files[0] if len(new_files)==1 else new_files
377
+ self._match_input_file()
378
+
379
+
380
+ # ===============================
381
+ # === Methods to be inherited ===
382
+ # ===============================
383
+
384
+ @classmethod
385
+ def is_running(cls, **kwargs):
386
+ self = cls.get_self(**kwargs)
387
+ if hasattr(self, '_starting_engine') and self._starting_engine:
388
+ # We need this to allow changing the element settings which otherwise are locked
389
+ return False
390
+ # If we get here, we cannot say if the engine is running or not and we need an
391
+ # implementation in the child class
392
+ return None
393
+
394
+ @classmethod
395
+ def clean_output_files(cls, **kwargs):
396
+ pass
397
+
398
+ def _match_input_file(self, **kwargs):
399
+ raise NotImplementedError("Need to implement `_match_input_file` for {cls.__name__}!")
400
+
401
+ @classmethod
402
+ def generate_input_file(cls, **kwargs):
403
+ raise NotImplementedError("Need to implement `generate_input_file` for {cls.__name__}!")
@@ -171,7 +171,7 @@ double calculate_dechanneling_length(EverestData restrict everest, double pc) {
171
171
  double energy = sqrt(pow(momentum, 2.) + pow(XC_PROTON_MASS, 2.)); // [MeV]
172
172
  double gammar = energy/XC_PROTON_MASS;
173
173
 
174
- double const_dech = 256.0/(9.*pow(M_PI, 2.)) / (log(2.*XC_ELECTRON_MASS*gammar/exenergy) - 1.);
174
+ double const_dech = 256.0/(9.*pow(M_PI, 2.)) / (log(2.*XC_ELECTRON_MASS*gammar/exenergy/1000) - 1.);
175
175
  const_dech *= (XC_SCREENING*XC_PLANE_DISTANCE)/(XC_CRADE*XC_ELECTRON_MASS)*1.0e3; // [m/GeV]
176
176
  return const_dech;
177
177
  }
xcoll/xaux.py ADDED
@@ -0,0 +1,117 @@
1
+ # copyright ############################### #
2
+ # This file is part of the Xcoll Package. #
3
+ # Copyright (c) CERN, 2024. #
4
+ # ######################################### #
5
+
6
+ import functools
7
+ from pathlib import Path
8
+
9
+ FsPath = Path
10
+
11
+
12
+ def singleton(cls):
13
+ # Store the original __new__ method if it exists
14
+ original_new = cls.__new__ if hasattr(cls, '__new__') else None
15
+
16
+ # Define a new __new__ method for the singleton
17
+ def singleton_new(cls, *args, **kwargs):
18
+ if not hasattr(cls, 'instance'):
19
+ cls.instance = (original_new(cls, *args, **kwargs)
20
+ if original_new
21
+ else super(cls, cls).__new__(cls))
22
+ cls.instance._initialised = False
23
+ return cls.instance
24
+ cls.__new__ = singleton_new
25
+
26
+ # Define the get_self method
27
+ @classmethod
28
+ def get_self(cls, **kwargs):
29
+ # Filter kwargs to include only ClassProperty attributes
30
+ filtered_kwargs = {key: value for key, value in kwargs.items()
31
+ if key in ClassProperty.get_properties(cls) or
32
+ key in getattr(cls, '_xofields', {})}
33
+ return cls(**filtered_kwargs)
34
+ cls.get_self = get_self
35
+
36
+ return cls
37
+
38
+
39
+
40
+ class ClassPropertyMeta(type):
41
+ def __setattr__(cls, key, value):
42
+ # Check if the attribute is a ClassProperty
43
+ for parent in cls.__mro__:
44
+ if key in parent.__dict__ and isinstance(parent.__dict__[key], ClassProperty):
45
+ return parent.__dict__[key].__set__(cls, value)
46
+ return super(ClassPropertyMeta, cls).__setattr__(key, value)
47
+
48
+
49
+ class ClassProperty:
50
+ _registry = {} # Registry to store ClassProperty names for each class
51
+
52
+ @classmethod
53
+ def get_properties(cls, owner, parents=True):
54
+ if not parents:
55
+ return cls._registry.get(owner, [])
56
+ else:
57
+ return [prop for parent in owner.__mro__
58
+ for prop in cls._registry.get(parent, [])]
59
+
60
+ def __init__(self, fget=None, fset=None, fdel=None, doc=None):
61
+ functools.update_wrapper(self, fget)
62
+ self.fget = fget
63
+ self.fset = fset
64
+ self.fdel = fdel
65
+ if doc is None and fget is not None:
66
+ doc = fget.__doc__
67
+ self.__doc__ = doc
68
+
69
+ def __set_name__(self, owner, name):
70
+ self.name = name
71
+ # Verify that the class is a subclass of ClassPropertyMeta
72
+ if ClassPropertyMeta not in type(owner).__mro__:
73
+ raise AttributeError(f"Class `{owner.__name__}` must be a subtype of ClassPropertyMeta!")
74
+ # Add the property name to the registry for the class
75
+ if owner not in ClassProperty._registry:
76
+ ClassProperty._registry[owner] = []
77
+ ClassProperty._registry[owner].append(name)
78
+ # Create default getter, setter, and deleter
79
+ if self.fget is None:
80
+ def _getter(*args, **kwargs):
81
+ raise AttributeError(f"Unreadable attribute `{name}` for class {owner.__name__}!")
82
+ self.fget = _getter
83
+ if self.fset is None:
84
+ def _setter(self, *args, **kwargs):
85
+ raise AttributeError(f"Can't set attribute `{name}` for class {owner.__name__}!")
86
+ self.fset = _setter
87
+ if self.fdel is None:
88
+ def _deleter(*args, **kwargs):
89
+ raise AttributeError(f"Can't delete attribute `{name}` for class {owner.__name__}!")
90
+ self.fdel = _deleter
91
+
92
+ def __get__(self, instance, owner):
93
+ if owner is None:
94
+ owner = type(instance)
95
+ try:
96
+ return self.fget(owner)
97
+ except ValueError:
98
+ # Return a fallback if initialization fails
99
+ return None
100
+
101
+ def __set__(self, cls, value):
102
+ self.fset(cls, value)
103
+
104
+ def __delete__(self, instance):
105
+ self.fdel(instance.__class__)
106
+
107
+ def getter(self, fget):
108
+ self.fget = fget
109
+ return self
110
+
111
+ def setter(self, fset):
112
+ self.fset = fset
113
+ return self
114
+
115
+ def deleter(self, fdel):
116
+ self.fdel = fdel
117
+ return self
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xcoll
3
- Version: 0.5.10
3
+ Version: 0.5.12
4
4
  Summary: Xsuite collimation package
5
5
  Home-page: https://github.com/xsuite/xcoll
6
6
  License: Apache 2.0
@@ -18,10 +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.7.4)
22
- Requires-Dist: xobjects (>=0.4.5)
23
- Requires-Dist: xpart (>=0.19.1)
24
- Requires-Dist: xtrack (>=0.69.7)
21
+ Requires-Dist: xdeps (>=0.8.4)
22
+ Requires-Dist: xobjects (>=0.4.6)
23
+ Requires-Dist: xpart (>=0.19.3)
24
+ Requires-Dist: xtrack (>=0.72.2)
25
25
  Project-URL: Repository, https://github.com/xsuite/xcoll
26
26
  Description-Content-Type: text/markdown
27
27
 
@@ -4,7 +4,7 @@ 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
6
  xcoll/beam_elements/absorber.py,sha256=efK6gyUgD4x_FnBLpMR7-5_HCdp_753nkYikcdn6ulw,2502
7
- xcoll/beam_elements/base.py,sha256=5k4ngEXNgvoiJWu7dx2bwHlv73tfaxn-4JlCdLwHdYo,53495
7
+ xcoll/beam_elements/base.py,sha256=J69dwAILHGwGthaQK3D9f2Etibv6fe11oczcKftTEac,53943
8
8
  xcoll/beam_elements/blowup.py,sha256=gBXdlISvoDiMjXVpA77ls5QdAU3H9krwvFt2bSW_NII,8029
9
9
  xcoll/beam_elements/elements_src/black_absorber.h,sha256=jCyQoaZ7VsIu8coQ99J1dDtAGqZpFIxxFLBlfhO6Drw,5027
10
10
  xcoll/beam_elements/elements_src/black_crystal.h,sha256=x7oUwsrkUwUrFvhFsq8vtPkJJTSi1hYAhSt6fjzkJQo,4622
@@ -16,18 +16,19 @@ xcoll/beam_elements/elements_src/everest_crystal.h,sha256=WwwNF6it7TuOimhpSXJa7U
16
16
  xcoll/beam_elements/everest.py,sha256=PA_VWpnPrIuO1xN__eKyT_ejbGZK7p93QHDVi3re7cM,8541
17
17
  xcoll/beam_elements/monitor.py,sha256=zzMdN3JMFSAs-30_ntRvd5qZGdsXfGtColhiFDuMcIk,16928
18
18
  xcoll/colldb.py,sha256=hGScl4lU93KDemHrgHOKfu2OqVAufP1BtgKKHJ_THDc,30501
19
- xcoll/general.py,sha256=wIlggzlMxO2sa0ntCbJyJatOL4CSQ7ndh3Ff9SNuOsc,535
19
+ xcoll/general.py,sha256=VqLfCrl6mLRBUaBR8TC0bGG8MH271fZEGKITEw3UfYo,535
20
20
  xcoll/headers/checks.h,sha256=qdXsOTBOK1MwW6bdFF93j4yE648mcDtEv5rGN1w9sfk,1582
21
- xcoll/headers/particle_states.h,sha256=DZa_ZSaJrjnA8aHFriZKfRCkArQ8nK1t445MRwevDtA,840
22
- xcoll/initial_distribution.py,sha256=x5G4LTXn4boEg5jBFrQCk_l759h91XiAUhDTdcUvLkc,8779
21
+ xcoll/headers/particle_states.h,sha256=9hzp5abC_pIV6QrHy0iNm0qblcvsymI-pJQMv6v1Rhc,840
22
+ xcoll/initial_distribution.py,sha256=yjjEk2PJjE1YB9xDMeVDa-qFVlPTvVWEf8yynHvgAow,9015
23
23
  xcoll/install.py,sha256=SxEFQnhWXlsXyPBIo847q6wPgId_f5ZtFRR1awGbkjc,2108
24
24
  xcoll/interaction_record/__init__.py,sha256=UFoLiKa-z2oX7YoszP-7Vgdt1nM6kT382v1CaIu8_u0,50
25
25
  xcoll/interaction_record/interaction_record.py,sha256=ixsQtVZn71vVEuTAA27a2NWcZZZ8iAcWFOa58bcWEQU,13271
26
26
  xcoll/interaction_record/interaction_record_src/interaction_record.h,sha256=0rNagnfSGc2i1jauOMIcDbj9QFic9dV_MOyqVx1kw5Q,6067
27
27
  xcoll/interaction_record/interaction_types.py,sha256=Vh6oFYKdRNOx9hc_E0iT8auNZVKC0qYVp_p7oyClZ8o,2894
28
- xcoll/line_tools.py,sha256=UtRVlwX1T-wzocFzgb3VoJuBavvggNnYONjvP2WigKM,14326
28
+ xcoll/line_tools.py,sha256=ZVUNRP_UI9RP7PBYBUVegww4J_BmdSo2dn3BS4ZAohI,14298
29
29
  xcoll/lossmap.py,sha256=W2EzOe4aKmJFd8kEpebeQaAn1818QF3ih_nhA_br-2I,8062
30
30
  xcoll/rf_sweep.py,sha256=P2X1S9pGi4OpNYnzYfQVyblFt2p8aw9EWHsKDkAuYt0,8936
31
+ xcoll/scattering_routines/engine.py,sha256=ZE2a_RbjGvoRZEr6XNGa6avsB_faiIqPBqb7SVWZyng,15018
31
32
  xcoll/scattering_routines/everest/__init__.py,sha256=7lkkeZ1liBjXVHCuRpgzZI6ohzHVMj5uJBO792147XY,286
32
33
  xcoll/scattering_routines/everest/amorphous.h,sha256=0eSV8F7yb2xrhrEGPZGWu_Lgr3gjtU8RddaJElsZ-Tk,10362
33
34
  xcoll/scattering_routines/everest/channeling.h,sha256=k7ohwPec3gk3WCvwm-0C8cB39F__CO2yLyvKCWCg714,11050
@@ -39,7 +40,7 @@ xcoll/scattering_routines/everest/jaw.h,sha256=BWpfrHNPLaMF8JjsNAhWwy6TDI0jsiOT3
39
40
  xcoll/scattering_routines/everest/materials.py,sha256=nO0ZayGRgqgF7Eku-mtxDl0BPOls5YS8a0qPPG32qw0,10260
40
41
  xcoll/scattering_routines/everest/multiple_coulomb_scattering.h,sha256=NgrUEX-zCaiS1mwJvMLhfmg8GPNcPSGzh6UQKYqphJ8,5432
41
42
  xcoll/scattering_routines/everest/nuclear_interaction.h,sha256=jr49uo2cCk5SiebMZtb_If2WFHSxNLpYYKDJiQbhW1g,3110
42
- xcoll/scattering_routines/everest/properties.h,sha256=mVPntq9lgZPnJdtOJDi_tSawecBPLB09b7BKbisPe7w,6774
43
+ xcoll/scattering_routines/everest/properties.h,sha256=9kf-a8EYGTVVZPkIzI-YXLW3ZvKEAJvACB8eSpcD2dM,6779
43
44
  xcoll/scattering_routines/fluka/flukaio/.git,sha256=5ZxurpwP1waJk2Zu2AstENGOp4rXU8-C-oU4CypmIVI,73
44
45
  xcoll/scattering_routines/fluka/flukaio/.gitignore,sha256=bhrpWiAnKChkcwIaIPX_zKxyG2nCtNzNgQAHnIJa8Fw,12
45
46
  xcoll/scattering_routines/fluka/flukaio/CMakeLists.txt,sha256=iWWqTC26UqcuQWQj_JCKtX6iiOb7arnhQ9kOo1fDVkE,591
@@ -406,8 +407,9 @@ xcoll/scattering_routines/geometry/objects.h,sha256=A5ktGvVlSkC4hUsI_PQFsE80CuDw
406
407
  xcoll/scattering_routines/geometry/rotation.h,sha256=lO3RaQBys9r0ROMjR8T8Rr7UsIEm-H9_C_70Nwz4MXY,701
407
408
  xcoll/scattering_routines/geometry/segments.h,sha256=7nKnnin2ByxkKyaYwGvFaqgLQg5uBba4CdLHL7L3iQs,7667
408
409
  xcoll/scattering_routines/geometry/sort.h,sha256=b1MkFO2ddzv1fWGeQzsLuz46qo2pKyRSXHjoAEVU7Ts,5763
409
- xcoll-0.5.10.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
410
- xcoll-0.5.10.dist-info/METADATA,sha256=QjZzi_xm9l64o5NVMUqkX5E0Tn5RrbW4pJbGMdqyrPI,2676
411
- xcoll-0.5.10.dist-info/NOTICE,sha256=6DO_E7WCdRKc42vUoVVBPGttvQi4mRt9fAcxj9u8zy8,74
412
- xcoll-0.5.10.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
413
- xcoll-0.5.10.dist-info/RECORD,,
410
+ xcoll/xaux.py,sha256=KME-8QXkZwQLqgtuC2q7KM27WCCBDLWHR9zq7uVaMlc,4085
411
+ xcoll-0.5.12.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
412
+ xcoll-0.5.12.dist-info/METADATA,sha256=Ag2SZen5Cuuvjz6ZoIZjvxluHWDkaDEgsxVC4XLU9Io,2676
413
+ xcoll-0.5.12.dist-info/NOTICE,sha256=6DO_E7WCdRKc42vUoVVBPGttvQi4mRt9fAcxj9u8zy8,74
414
+ xcoll-0.5.12.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
415
+ xcoll-0.5.12.dist-info/RECORD,,
File without changes