xcoll 0.3.6__py3-none-any.whl → 0.4.0__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.
Files changed (56) hide show
  1. xcoll/__init__.py +12 -4
  2. xcoll/beam_elements/__init__.py +7 -5
  3. xcoll/beam_elements/absorber.py +41 -7
  4. xcoll/beam_elements/base.py +1161 -244
  5. xcoll/beam_elements/collimators_src/black_absorber.h +118 -0
  6. xcoll/beam_elements/collimators_src/black_crystal.h +111 -0
  7. xcoll/beam_elements/collimators_src/everest_block.h +40 -28
  8. xcoll/beam_elements/collimators_src/everest_collimator.h +129 -50
  9. xcoll/beam_elements/collimators_src/everest_crystal.h +217 -73
  10. xcoll/beam_elements/everest.py +60 -113
  11. xcoll/colldb.py +250 -750
  12. xcoll/general.py +2 -2
  13. xcoll/headers/checks.h +1 -1
  14. xcoll/headers/particle_states.h +2 -2
  15. xcoll/initial_distribution.py +195 -0
  16. xcoll/install.py +177 -0
  17. xcoll/interaction_record/__init__.py +1 -0
  18. xcoll/interaction_record/interaction_record.py +252 -0
  19. xcoll/interaction_record/interaction_record_src/interaction_record.h +98 -0
  20. xcoll/{impacts → interaction_record}/interaction_types.py +11 -4
  21. xcoll/line_tools.py +83 -0
  22. xcoll/lossmap.py +209 -0
  23. xcoll/manager.py +2 -937
  24. xcoll/rf_sweep.py +1 -1
  25. xcoll/scattering_routines/everest/amorphous.h +239 -0
  26. xcoll/scattering_routines/everest/channeling.h +245 -0
  27. xcoll/scattering_routines/everest/crystal_parameters.h +137 -0
  28. xcoll/scattering_routines/everest/everest.h +8 -30
  29. xcoll/scattering_routines/everest/everest.py +13 -10
  30. xcoll/scattering_routines/everest/jaw.h +27 -197
  31. xcoll/scattering_routines/everest/materials.py +2 -0
  32. xcoll/scattering_routines/everest/multiple_coulomb_scattering.h +31 -10
  33. xcoll/scattering_routines/everest/nuclear_interaction.h +86 -0
  34. xcoll/scattering_routines/geometry/__init__.py +6 -0
  35. xcoll/scattering_routines/geometry/collimator_geometry.h +219 -0
  36. xcoll/scattering_routines/geometry/crystal_geometry.h +150 -0
  37. xcoll/scattering_routines/geometry/geometry.py +26 -0
  38. xcoll/scattering_routines/geometry/get_s.h +92 -0
  39. xcoll/scattering_routines/geometry/methods.h +111 -0
  40. xcoll/scattering_routines/geometry/objects.h +154 -0
  41. xcoll/scattering_routines/geometry/rotation.h +23 -0
  42. xcoll/scattering_routines/geometry/segments.h +226 -0
  43. xcoll/scattering_routines/geometry/sort.h +184 -0
  44. {xcoll-0.3.6.dist-info → xcoll-0.4.0.dist-info}/METADATA +1 -1
  45. {xcoll-0.3.6.dist-info → xcoll-0.4.0.dist-info}/RECORD +48 -33
  46. xcoll/beam_elements/collimators_src/absorber.h +0 -141
  47. xcoll/collimator_settings.py +0 -457
  48. xcoll/impacts/__init__.py +0 -1
  49. xcoll/impacts/impacts.py +0 -102
  50. xcoll/impacts/impacts_src/impacts.h +0 -99
  51. xcoll/scattering_routines/everest/crystal.h +0 -1302
  52. xcoll/scattering_routines/everest/scatter.h +0 -169
  53. xcoll/scattering_routines/everest/scatter_crystal.h +0 -260
  54. {xcoll-0.3.6.dist-info → xcoll-0.4.0.dist-info}/LICENSE +0 -0
  55. {xcoll-0.3.6.dist-info → xcoll-0.4.0.dist-info}/NOTICE +0 -0
  56. {xcoll-0.3.6.dist-info → xcoll-0.4.0.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
1
1
  // copyright ############################### #
2
2
  // This file is part of the Xcoll Package. #
3
- // Copyright (c) CERN, 2023. #
3
+ // Copyright (c) CERN, 2024. #
4
4
  // ######################################### #
5
5
 
6
6
  #ifndef XCOLL_EVEREST_CRYSTAL_H
@@ -9,106 +9,250 @@
9
9
  #include <stdio.h>
10
10
 
11
11
 
12
- /*gpufun*/
13
12
  void EverestCrystal_set_material(EverestCrystalData el){
14
13
  CrystalMaterialData material = EverestCrystalData_getp__material(el);
15
14
  RandomRutherfordData rng = EverestCrystalData_getp_rutherford_rng(el);
16
15
  RandomRutherford_set_by_xcoll_material(rng, (GeneralMaterialData) material);
17
16
  }
18
17
 
18
+
19
19
  /*gpufun*/
20
- void EverestCrystal_track_local_particle(EverestCrystalData el, LocalParticle* part0) {
21
- int8_t active = EverestCrystalData_get_active(el);
22
- active *= EverestCrystalData_get__tracking(el);
23
- double const inactive_front = EverestCrystalData_get_inactive_front(el);
24
- double const active_length = EverestCrystalData_get_active_length(el);
25
- double const inactive_back = EverestCrystalData_get_inactive_back(el);
20
+ CrystalGeometry EverestCrystal_init_geometry(EverestCrystalData el, LocalParticle* part0, int8_t active){
21
+ CrystalGeometry cg = (CrystalGeometry) malloc(sizeof(CrystalGeometry_));
22
+ if (active){ // This is needed in order to avoid that the initialisation is called during a twiss!
23
+ cg->length = EverestCrystalData_get_length(el);
24
+ cg->side = EverestCrystalData_get__side(el);
25
+ if (cg->side == 0){
26
+ kill_all_particles(part0, XC_ERR_INVALID_XOFIELD);
27
+ return cg;
28
+ }
29
+ double R = EverestCrystalData_get__bending_radius(el);
30
+ double t_R = EverestCrystalData_get__bending_angle(el);
31
+ cg->bending_radius = R;
32
+ cg->bending_angle = t_R;
33
+ cg->miscut_angle = EverestCrystalData_get_miscut(el);
34
+ cg->width = EverestCrystalData_get_width(el);
35
+ cg->height = EverestCrystalData_get_height(el);
36
+ cg->jaw_U = EverestCrystalData_get__jaw_U(el);
37
+ cg->sin_z = EverestCrystalData_get__sin_z(el);
38
+ cg->cos_z = EverestCrystalData_get__cos_z(el);
39
+ cg->sin_y = EverestCrystalData_get__sin_y(el);
40
+ cg->cos_y = EverestCrystalData_get__cos_y(el);
41
+ // Segments
42
+ if (cg->side == 1){
43
+ cg->segments = create_crystal(cg->bending_radius, cg->width, cg->length, cg->jaw_U, \
44
+ cg->sin_y, cg->cos_y);
45
+ } else if (cg->side == -1){
46
+ // jaw_U is the inner corner (shifted if right-sided crystal)
47
+ cg->segments = create_crystal(cg->bending_radius, cg->width, cg->length, cg->jaw_U - cg->width, \
48
+ cg->sin_y, cg->cos_y);
49
+ }
50
+ // Bend centre
51
+ cg->s_B = 0;
52
+ cg->x_B = cg->bending_radius;
53
+ // Miscut centre
54
+ cg->s_P = -R*sin(cg->miscut_angle);
55
+ cg->x_P = R*cos(cg->miscut_angle);
56
+ // Mirror the crystal geometry
57
+ if (cg->side == -1){
58
+ cg->bending_radius = -cg->bending_radius;
59
+ cg->bending_angle = -cg->bending_angle;
60
+ cg->miscut_angle = -cg->miscut_angle;
61
+ cg->x_P = -cg->x_P;
62
+ cg->x_B = -cg->x_B;
63
+ }
64
+ if (R < 0){
65
+ // If R<0, a left-sided crystal bends towards the beam
66
+ cg->x_P = cg->x_P + cg->width;
67
+ cg->x_B = cg->x_B + cg->width;
68
+ }
69
+ // From here on, crystal geometry parameters can always be treated as left-sided.
70
+ // Note that the segments are not mirrored, which is fine as get_s_of_first_crossing_with_vlimit
71
+ // is absolute (not in the jaw reference frame). It is only after a hit is registered, that we
72
+ // need to transform the particle to the jaw reference frame.
73
+ double Rb;
74
+ if (cg->miscut_angle > 0){
75
+ Rb = R - cg->width;
76
+ } else {
77
+ Rb = R;
78
+ }
79
+ cg->t_VImax = atan( (Rb*sin(t_R) - cg->s_P) / (R - Rb*cos(t_R) - cg->x_P) );
80
+ // Impact table
81
+ cg->record = EverestCrystalData_getp_internal_record(el, part0);
82
+ cg->record_index = NULL;
83
+ cg->record_touches = 0;
84
+ if (cg->record){
85
+ cg->record_index = InteractionRecordData_getp__index(cg->record);
86
+ cg->record_touches = EverestCrystalData_get_record_touches(el);
87
+ }
88
+ }
26
89
 
27
- CrystalMaterialData material = EverestCrystalData_getp__material(el);
28
- RandomRutherfordData rng = EverestCrystalData_getp_rutherford_rng(el);
90
+ return cg;
91
+ }
29
92
 
30
- // Crystal properties
31
- double length = EverestCrystalData_get_active_length(el);
32
- double const co_x = EverestCrystalData_get_ref_x(el);
33
- double const co_y = EverestCrystalData_get_ref_y(el);
34
- // TODO: use xtrack C-code for rotation element
35
- // TODO: we are ignoring the angle of the right jaw
36
- // TODO: is a crystal always one-sided...?
37
- double const sin_zL = EverestCrystalData_get_sin_zL(el);
38
- double const cos_zL = EverestCrystalData_get_cos_zL(el);
39
- double const sin_zR = EverestCrystalData_get_sin_zR(el);
40
- double const cos_zR = EverestCrystalData_get_cos_zR(el);
41
- if (fabs(sin_zL-sin_zR) > 1.e-10 || fabs(cos_zL-cos_zR) > 1.e-10 ){
42
- kill_all_particles(part0, XC_ERR_NOT_IMPLEMENTED);
43
- };
44
- double const c_aperture = EverestCrystalData_get_jaw_L(el) - EverestCrystalData_get_jaw_R(el);
45
- double const c_offset = ( EverestCrystalData_get_jaw_L(el) + EverestCrystalData_get_jaw_R(el) ) /2;
46
- double const c_tilt0 = asin(EverestCrystalData_get_sin_yL(el));
47
- double const c_tilt1 = asin(EverestCrystalData_get_sin_yR(el));
48
- if (fabs(c_tilt1) > 1.e-10){
49
- kill_all_particles(part0, XC_ERR_INVALID_XOFIELD);
50
- };
51
- int const side = EverestCrystalData_get__side(el);
52
- double const bend = EverestCrystalData_get__bending_radius(el);
53
- // TODO: cry_tilt should be given by jaw positions...?
54
- double const cry_tilt = EverestCrystalData_get_align_angle(el) + c_tilt0;
55
- double const bend_ang = length/bend; // temporary value
56
- if (cry_tilt >= -bend_ang) {
57
- length = bend*(sin(bend_ang + cry_tilt) - sin(cry_tilt));
58
- } else {
59
- length = bend*(sin(bend_ang - cry_tilt) + sin(cry_tilt));
93
+ /*gpufun*/
94
+ void EverestCrystal_free(CrystalGeometry restrict cg, int8_t active){
95
+ if (active){
96
+ destroy_crystal(cg->segments);
60
97
  }
61
- double const cry_rcurv = bend;
62
- double const cry_bend = length/cry_rcurv; //final value (with corrected length)
63
- double const cry_alayer = EverestCrystalData_get_thick(el);
64
- double const cry_xmax = EverestCrystalData_get_xdim(el);
65
- double const cry_ymax = EverestCrystalData_get_ydim(el);
66
- double const cry_orient = EverestCrystalData_get__orient(el);
67
- double const cry_miscut = EverestCrystalData_get_miscut(el);
68
-
69
- // Impact table
70
- CollimatorImpactsData record = EverestCrystalData_getp_internal_record(el, part0);
71
- RecordIndex record_index = NULL;
72
- if (record){
73
- record_index = CollimatorImpactsData_getp__index(record);
98
+ free(cg);
99
+ }
100
+
101
+
102
+ // TODO: it would be great if we could set EverestData as an xofield, because then we could
103
+ // run this function at creation of the collimator instead of every turn
104
+ /*gpufun*/
105
+ EverestCollData EverestCrystal_init(EverestCrystalData el, LocalParticle* part0, int8_t active){
106
+ EverestCollData coll = (EverestCollData) malloc(sizeof(EverestCollData_));
107
+ if (active){ // This is needed in order to avoid that the initialisation is called during a twiss!
108
+ // Random generator and material
109
+ coll->rng = EverestCrystalData_getp_rutherford_rng(el);
110
+ CrystalMaterialData material = EverestCrystalData_getp__material(el);
111
+ coll->exenergy = CrystalMaterialData_get_excitation_energy(material)*1.0e3; // MeV
112
+ coll->rho = CrystalMaterialData_get_density(material);
113
+ coll->anuc = CrystalMaterialData_get_A(material);
114
+ coll->zatom = CrystalMaterialData_get_Z(material);
115
+ coll->bnref = CrystalMaterialData_get_nuclear_elastic_slope(material);
116
+ coll->csref[0] = CrystalMaterialData_get_cross_section(material, 0);
117
+ coll->csref[1] = CrystalMaterialData_get_cross_section(material, 1);
118
+ coll->csref[5] = CrystalMaterialData_get_cross_section(material, 5);
119
+ coll->dlri = CrystalMaterialData_get_crystal_radiation_length(material);
120
+ coll->dlyi = CrystalMaterialData_get_crystal_nuclear_length(material);
121
+ coll->ai = CrystalMaterialData_get_crystal_plane_distance(material);
122
+ coll->eum = CrystalMaterialData_get_crystal_potential(material);
123
+ coll->collnt = CrystalMaterialData_get_nuclear_collision_length(material);
124
+ coll->eta = 0.9; // Hard-coded channeling saturation factor
125
+ coll->orient = EverestCrystalData_get__orient(el);
126
+ // Impact table
127
+ coll->record = EverestCrystalData_getp_internal_record(el, part0);
128
+ coll->record_index = NULL;
129
+ if (coll->record){
130
+ coll->record_index = InteractionRecordData_getp__index(coll->record);
131
+ coll->record_scatterings = EverestCrystalData_get_record_scatterings(el);
132
+ coll->record_touches = EverestCrystalData_get_record_touches(el);
133
+ }
74
134
  }
135
+ return coll;
136
+ }
137
+
138
+
139
+ /*gpufun*/
140
+ EverestData EverestCrystal_init_data(LocalParticle* part, EverestCollData restrict coll, CrystalGeometry restrict cg){
141
+ EverestData everest = (EverestData) malloc(sizeof(EverestData_));
142
+ everest->coll = coll;
143
+ everest->rescale_scattering = 1;
144
+ #ifndef XCOLL_REFINE_ENERGY
145
+ // Preinitialise scattering parameters
146
+ double charge_ratio = LocalParticle_get_charge_ratio(part);
147
+ double mass_ratio = charge_ratio / LocalParticle_get_chi(part);
148
+ double energy = ( LocalParticle_get_ptau(part) + 1 / LocalParticle_get_beta0(part)
149
+ ) * mass_ratio * LocalParticle_get_p0c(part) / 1e9; // energy in GeV
150
+ calculate_scattering(everest, energy);
151
+ calculate_ionisation_properties(everest, energy);
152
+ calculate_critical_angle(everest, part, cg, energy);
153
+ calculate_VI_parameters(everest, part, energy);
154
+ #endif
155
+ return everest;
156
+ }
157
+
158
+
159
+ /*gpufun*/
160
+ void EverestCrystal_track_local_particle(EverestCrystalData el, LocalParticle* part0) {
161
+ int8_t active = EverestCrystalData_get_active(el);
162
+ active *= EverestCrystalData_get__tracking(el);
163
+ double length = EverestCrystalData_get_length(el);
164
+
165
+ // Initialise collimator data
166
+ // TODO: we want this to happen before tracking (instead of every turn), as a separate kernel
167
+ EverestCollData coll = EverestCrystal_init(el, part0, active);
168
+ CrystalGeometry cg = EverestCrystal_init_geometry(el, part0, active);
169
+
170
+ double t_c = 0;
75
171
 
76
172
  //start_per_particle_block (part0->part)
77
173
  if (!active){
78
174
  // Drift full length
79
- Drift_single_particle(part, inactive_front+active_length+inactive_back);
175
+ Drift_single_particle(part, length);
80
176
 
81
177
  } else {
82
178
  // Check collimator initialisation
83
- int8_t is_tracking = assert_tracking(part, XC_ERR_INVALID_TRACK);
84
- int8_t rng_is_set = assert_rng_set(part, RNG_ERR_SEEDS_NOT_SET);
85
- int8_t ruth_is_set = assert_rutherford_set(rng, part, RNG_ERR_RUTH_NOT_SET);
179
+ int8_t is_valid = xcoll_check_particle_init(coll->rng, part);
86
180
 
87
- if (is_tracking && rng_is_set && ruth_is_set) {
88
- // Drift inactive front
89
- Drift_single_particle(part, inactive_front);
181
+ if (is_valid) {
182
+ double const s_coll = LocalParticle_get_s(part);
183
+ LocalParticle_set_s(part, 0);
90
184
 
91
- // Scatter
185
+ // Store initial coordinates for updating later
186
+ double const e0 = LocalParticle_get_energy0(part);
187
+ double const p0 = LocalParticle_get_p0c(part);
188
+ double const ptau_in = LocalParticle_get_ptau(part);
189
+ double const rvv_in = LocalParticle_get_rvv(part);
190
+ #ifdef XCOLL_USE_EXACT
191
+ double const xp_in = LocalParticle_get_exact_xp(part);
192
+ double const yp_in = LocalParticle_get_exact_yp(part);
193
+ #else
194
+ double const xp_in = LocalParticle_get_xp(part);
195
+ double const yp_in = LocalParticle_get_yp(part);
196
+ #endif
197
+ double const zeta_in = LocalParticle_get_zeta(part);
198
+ double const mass_ratio = LocalParticle_get_charge_ratio(part) / LocalParticle_get_chi(part); // m/m0
199
+ double energy = (p0*ptau_in + e0) * mass_ratio;
92
200
 
93
- // Move to closed orbit
94
- LocalParticle_add_to_x(part, -co_x);
95
- LocalParticle_add_to_y(part, -co_y);
201
+ // Check if hit on jaws
202
+ int8_t is_hit = hit_crystal_check_and_transform(part, cg);
96
203
 
97
- scatter_cry(part, length, material, rng, cos_zL, sin_zL, c_aperture, c_offset,
98
- side, cry_tilt, cry_rcurv, cry_bend, cry_alayer, cry_xmax, cry_ymax, cry_orient,
99
- cry_miscut, record, record_index);
204
+ if (is_hit != 0) {
205
+ // Hit one of the jaws, so scatter
206
+ double remaining_length = length - LocalParticle_get_s(part);
207
+ // Scatter
208
+ EverestData everest = EverestCrystal_init_data(part0, coll, cg);
209
+ calculate_initial_angle(everest, part, cg);
210
+ #ifdef XCOLL_USE_EXACT
211
+ double const xp = LocalParticle_get_exact_xp(part);
212
+ #else
213
+ double const xp = LocalParticle_get_xp(part);
214
+ #endif
215
+ if (fabs(xp - everest->t_I) < everest->t_c) {
216
+ energy = Channel(everest, part, cg, energy/1.e9, remaining_length)*1.e9;
217
+ } else {
218
+ energy = Amorphous(everest, part, cg, energy/1.e9, remaining_length)*1.e9;
219
+ }
220
+ // Temporary workaround to store the critical angle for use later
221
+ calculate_critical_angle(everest, part, cg, e0/1.e9);
222
+ t_c = everest->t_c;
223
+ free(everest);
224
+ }
100
225
 
101
- // Return from closed orbit
102
- LocalParticle_add_to_x(part, co_x);
103
- LocalParticle_add_to_y(part, co_y);
226
+ // Transform back to the lab frame
227
+ hit_crystal_transform_back(is_hit, part, cg);
228
+ LocalParticle_add_to_s(part, s_coll);
104
229
 
105
- // Drift inactive back (only surviving particles)
106
- if (LocalParticle_get_state(part) > 0){
107
- Drift_single_particle(part, inactive_back);
230
+ LocalParticle_set_zeta(part, zeta_in);
231
+ // Hit and survived particles need correcting:
232
+ if (is_hit!=0 && LocalParticle_get_state(part)>0){
233
+ // Update energy
234
+ double ptau_out = (energy/mass_ratio - e0) / p0;
235
+ LocalParticle_update_ptau(part, ptau_out);
236
+ // Update zeta
237
+ #ifdef XCOLL_USE_EXACT
238
+ double xp = LocalParticle_get_exact_xp(part);
239
+ double yp = LocalParticle_get_exact_yp(part);
240
+ #else
241
+ double xp = LocalParticle_get_xp(part);
242
+ double yp = LocalParticle_get_yp(part);
243
+ #endif
244
+ double rvv = LocalParticle_get_rvv(part);
245
+ // First we drift half the length with the old angles:
246
+ LocalParticle_add_to_zeta(part, drift_zeta_single(rvv_in, xp_in, yp_in, length/2) );
247
+ // then half the length with the new angles:
248
+ LocalParticle_add_to_zeta(part, drift_zeta_single(rvv, xp, yp, length/2) );
108
249
  }
109
250
  }
110
251
  }
111
252
  //end_per_particle_block
253
+ EverestCrystalData_set__critical_angle(el, t_c);
254
+ EverestCrystal_free(cg, active);
255
+ free(coll);
112
256
  }
113
257
 
114
258
 
@@ -1,6 +1,6 @@
1
1
  # copyright ############################### #
2
2
  # This file is part of the Xcoll Package. #
3
- # Copyright (c) CERN, 2023. #
3
+ # Copyright (c) CERN, 2024. #
4
4
  # ######################################### #
5
5
 
6
6
  import numpy as np
@@ -9,8 +9,8 @@ import xobjects as xo
9
9
  import xpart as xp
10
10
  import xtrack as xt
11
11
 
12
- from .base import BaseBlock, BaseCollimator, InvalidXcoll
13
- from ..scattering_routines.everest import GeneralMaterial, Material, CrystalMaterial, EverestEngine
12
+ from .base import BaseBlock, BaseCollimator, BaseCrystal, InvalidXcoll
13
+ from ..scattering_routines.everest import Material, CrystalMaterial, EverestEngine
14
14
  from ..general import _pkg_root
15
15
 
16
16
 
@@ -21,10 +21,9 @@ from ..general import _pkg_root
21
21
  # only activated around the track command. Furthermore, because of 'iscollective = False' we need to specify
22
22
  # get_backtrack_element. We want it nicer..
23
23
 
24
- # TODO: _per_particle_kernels should be a normal kernel (such that we don't need to pass a dummy Particles() )
25
24
 
26
25
  class EverestBlock(BaseBlock):
27
- _xofields = { **BaseBlock._xofields,
26
+ _xofields = {**BaseBlock._xofields,
28
27
  '_material': Material,
29
28
  'rutherford_rng': xt.RandomRutherford,
30
29
  '_tracking': xo.Int8
@@ -55,21 +54,15 @@ class EverestBlock(BaseBlock):
55
54
 
56
55
 
57
56
  def __init__(self, **kwargs):
57
+ to_assign = {}
58
58
  if '_xobject' not in kwargs:
59
- mat = kwargs.pop('material', None)
60
- if mat is None:
61
- raise ValueError("Need to provide a material to the block!")
62
- if not isinstance(mat, Material):
63
- if not isinstance(mat, dict) \
64
- or mat['__class__'] != "Material":
65
- raise ValueError("Invalid material!")
66
- kwargs['_material'] = mat
59
+ to_assign['material'] = kwargs.pop('material', None)
60
+ kwargs['_material'] = Material()
67
61
  kwargs.setdefault('rutherford_rng', xt.RandomRutherford())
68
62
  kwargs.setdefault('_tracking', True)
69
- use_prebuilt_kernels = kwargs.pop('use_prebuilt_kernels', True)
70
63
  super().__init__(**kwargs)
71
- if '_xobject' not in kwargs:
72
- self.EverestBlock_set_material(el=self)
64
+ for key, val in to_assign.items():
65
+ setattr(self, key, val)
73
66
 
74
67
 
75
68
  @property
@@ -78,9 +71,12 @@ class EverestBlock(BaseBlock):
78
71
 
79
72
  @material.setter
80
73
  def material(self, material):
74
+ if material is None:
75
+ material = Material()
76
+ if isinstance(material, dict):
77
+ material = Material.from_dict(material)
81
78
  if not isinstance(material, Material):
82
- if not isinstance('material', dict) or material['__class__'] != "Material":
83
- raise ValueError("Invalid material!")
79
+ raise ValueError("Invalid material!")
84
80
  if not xt.line._dicts_equal(self.material.to_dict(), material.to_dict()):
85
81
  self._material = material
86
82
  self.EverestBlock_set_material(el=self)
@@ -91,7 +87,7 @@ class EverestBlock(BaseBlock):
91
87
 
92
88
 
93
89
  class EverestCollimator(BaseCollimator):
94
- _xofields = { **BaseCollimator._xofields,
90
+ _xofields = {**BaseCollimator._xofields,
95
91
  '_material': Material,
96
92
  'rutherford_rng': xt.RandomRutherford,
97
93
  '_tracking': xo.Int8
@@ -122,20 +118,15 @@ class EverestCollimator(BaseCollimator):
122
118
 
123
119
 
124
120
  def __init__(self, **kwargs):
121
+ to_assign = {}
125
122
  if '_xobject' not in kwargs:
126
- if kwargs.get('material') is None:
127
- raise ValueError("Need to provide a material to the collimator!")
128
- if not isinstance(kwargs['material'], Material):
129
- if not isinstance(kwargs['material'], dict) \
130
- or kwargs['material']['__class__'] != "Material":
131
- raise ValueError("Invalid material!")
132
- kwargs['_material'] = kwargs.pop('material')
123
+ to_assign['material'] = kwargs.pop('material', None)
124
+ kwargs['_material'] = Material()
133
125
  kwargs.setdefault('rutherford_rng', xt.RandomRutherford())
134
126
  kwargs.setdefault('_tracking', True)
135
- use_prebuilt_kernels = kwargs.pop('use_prebuilt_kernels', True)
136
127
  super().__init__(**kwargs)
137
- if '_xobject' not in kwargs:
138
- self.EverestCollimator_set_material(el=self)
128
+ for key, val in to_assign.items():
129
+ setattr(self, key, val)
139
130
 
140
131
  @property
141
132
  def material(self):
@@ -143,9 +134,12 @@ class EverestCollimator(BaseCollimator):
143
134
 
144
135
  @material.setter
145
136
  def material(self, material):
137
+ if material is None:
138
+ material = Material()
139
+ if isinstance(material, dict):
140
+ material = Material.from_dict(material)
146
141
  if not isinstance(material, Material):
147
- if not isinstance('material', dict) or material['__class__'] != "Material":
148
- raise ValueError("Invalid material!")
142
+ raise ValueError("Invalid material!")
149
143
  if not xt.line._dicts_equal(self.material.to_dict(), material.to_dict()):
150
144
  self._material = material
151
145
  self.EverestCollimator_set_material(el=self)
@@ -156,17 +150,11 @@ class EverestCollimator(BaseCollimator):
156
150
 
157
151
 
158
152
 
159
- class EverestCrystal(BaseCollimator):
160
- _xofields = { **BaseCollimator._xofields,
161
- 'align_angle': xo.Float64, # = - sqrt(eps/beta)*alpha*nsigma
162
- '_bending_radius': xo.Float64,
163
- '_bending_angle': xo.Float64,
164
- '_critical_angle': xo.Float64,
165
- 'xdim': xo.Float64,
166
- 'ydim': xo.Float64,
167
- 'thick': xo.Float64,
153
+ class EverestCrystal(BaseCrystal):
154
+ _xofields = {**BaseCrystal._xofields,
168
155
  'miscut': xo.Float64,
169
156
  '_orient': xo.Int8,
157
+ '_critical_angle': xo.Float64,
170
158
  '_material': CrystalMaterial,
171
159
  'rutherford_rng': xt.RandomRutherford,
172
160
  '_tracking': xo.Int8
@@ -178,12 +166,11 @@ class EverestCrystal(BaseCollimator):
178
166
  behaves_like_drift = True
179
167
  skip_in_loss_location_refinement = True
180
168
 
181
- _skip_in_to_dict = [*BaseCollimator._skip_in_to_dict, '_orient', '_material', '_bending_radius',
182
- '_bending_angle']
183
- _store_in_to_dict = [*BaseCollimator._store_in_to_dict, 'lattice', 'material', 'bending_radius', 'bending_angle']
184
- _internal_record_class = BaseCollimator._internal_record_class
169
+ _skip_in_to_dict = [*BaseCrystal._skip_in_to_dict, '_orient', '_material']
170
+ _store_in_to_dict = [*BaseCrystal._store_in_to_dict, 'lattice', 'material']
171
+ _internal_record_class = BaseCrystal._internal_record_class
185
172
 
186
- _depends_on = [BaseCollimator, EverestEngine]
173
+ _depends_on = [BaseCrystal, EverestEngine]
187
174
 
188
175
  _extra_c_sources = [
189
176
  _pkg_root.joinpath('beam_elements','collimators_src','everest_crystal.h')
@@ -198,62 +185,38 @@ class EverestCrystal(BaseCollimator):
198
185
 
199
186
 
200
187
  def __init__(self, **kwargs):
188
+ to_assign = {}
201
189
  if '_xobject' not in kwargs:
202
- if kwargs.get('material') is None:
203
- raise ValueError("Need to provide a material to the collimator!")
204
- if not isinstance(kwargs['material'], CrystalMaterial):
205
- if not isinstance(kwargs['material'], dict) \
206
- or kwargs['material']['__class__'] != "CrystalMaterial":
207
- raise ValueError("Invalid material!")
208
- kwargs['_material'] = kwargs.pop('material')
209
- bending_radius = False
210
- bending_angle = False
211
- if 'bending_radius' in kwargs:
212
- if 'bending_angle' in kwargs:
213
- raise ValueError("Need to choose between 'bending_radius' and 'bending_angle'!")
214
- bending_radius = kwargs['bending_radius']
215
- elif 'bending_angle' in kwargs:
216
- bending_angle = kwargs['bending_angle']
217
- kwargs['_bending_radius'] = kwargs.pop('bending_radius',0)
218
- kwargs['_bending_angle'] = kwargs.pop('bending_angle', 0)
219
- kwargs.setdefault('xdim', 0)
220
- kwargs.setdefault('ydim', 0)
221
- kwargs.setdefault('thick', 0)
190
+ to_assign['material'] = kwargs.pop('material', None)
191
+ kwargs['_material'] = CrystalMaterial()
192
+ to_assign['lattice'] = kwargs.pop('lattice', 'strip')
222
193
  kwargs.setdefault('miscut', 0)
223
- kwargs['_orient'] = _lattice_setter(kwargs.pop('lattice', 'strip'))
224
194
  kwargs.setdefault('rutherford_rng', xt.RandomRutherford())
225
195
  kwargs.setdefault('_tracking', True)
226
- use_prebuilt_kernels = kwargs.pop('use_prebuilt_kernels', True)
227
196
  super().__init__(**kwargs)
228
- if '_xobject' not in kwargs:
229
- if bending_radius:
230
- self._bending_angle = np.arcsin(self.active_length/bending_radius)
231
- if bending_angle:
232
- self._bending_radius = self.active_length / np.sin(bending_angle)
233
- self.EverestCrystal_set_material(el=self)
197
+ for key, val in to_assign.items():
198
+ setattr(self, key, val)
234
199
 
235
200
 
236
201
  @property
237
- def critical_angle(self):
238
- return self._critical_angle if abs(self._critical_angle) > 1.e-10 else None
239
-
240
- @property
241
- def bending_radius(self):
242
- return self._bending_radius
202
+ def material(self):
203
+ return self._material
243
204
 
244
- @bending_radius.setter
245
- def bending_radius(self, bending_radius):
246
- self._bending_radius = bending_radius
247
- self._bending_angle = np.arcsin(self.active_length/bending_radius)
205
+ @material.setter
206
+ def material(self, material):
207
+ if material is None:
208
+ material = CrystalMaterial()
209
+ if isinstance(material, dict):
210
+ material = CrystalMaterial.from_dict(material)
211
+ if not isinstance(material, CrystalMaterial):
212
+ raise ValueError("Invalid material!")
213
+ if not xt.line._dicts_equal(self.material.to_dict(), material.to_dict()):
214
+ self._material = material
215
+ self.EverestCrystal_set_material(el=self)
248
216
 
249
217
  @property
250
- def bending_angle(self):
251
- return self._bending_angle
252
-
253
- @bending_angle.setter
254
- def bending_angle(self, bending_angle):
255
- self._bending_angle = bending_angle
256
- self._bending_radius = self.active_length / np.sin(bending_angle)
218
+ def critical_angle(self):
219
+ return self._critical_angle if abs(self._critical_angle) > 1.e-10 else None
257
220
 
258
221
  @property
259
222
  def lattice(self):
@@ -266,20 +229,13 @@ class EverestCrystal(BaseCollimator):
266
229
 
267
230
  @lattice.setter
268
231
  def lattice(self, lattice):
269
- self._orient = _lattice_setter(lattice)
270
-
271
- @property
272
- def material(self):
273
- return self._material
274
-
275
- @material.setter
276
- def material(self, material):
277
- if not isinstance(material, CrystalMaterial):
278
- if not isinstance(material, dict) or material['__class__'] != "CrystalMaterial":
279
- raise ValueError("Invalid material!")
280
- if not xt.line._dicts_equal(self.material.to_dict(), material.to_dict()):
281
- self._material = material
282
- self.EverestCrystal_set_material(el=self)
232
+ if lattice == 'strip' or lattice == '110' or lattice == 110:
233
+ self._orient = 1
234
+ elif lattice == 'quasi-mosaic' or lattice == '111' or lattice == 111:
235
+ self._orient = 2
236
+ else:
237
+ raise ValueError(f"Illegal value {lattice} for 'lattice'! "
238
+ + "Only use 'strip' (110) or 'quasi-mosaic' (111).")
283
239
 
284
240
 
285
241
  def get_backtrack_element(self, _context=None, _buffer=None, _offset=None):
@@ -287,12 +243,3 @@ class EverestCrystal(BaseCollimator):
287
243
  _buffer=_buffer, _offset=_offset)
288
244
 
289
245
 
290
- def _lattice_setter(lattice):
291
- if lattice == 'strip' or lattice == '110' or lattice == 110:
292
- return 1
293
- elif lattice == 'quasi-mosaic' or lattice == '111' or lattice == 111:
294
- return 2
295
- else:
296
- raise ValueError(f"Illegal value {lattice} for 'lattice'! "
297
- + "Only use 'strip' (110) or 'quasi-mosaic' (111).")
298
-