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
xcoll/rf_sweep.py CHANGED
@@ -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
@@ -0,0 +1,239 @@
1
+ // copyright ############################### #
2
+ // This file is part of the Xcoll Package. #
3
+ // Copyright (c) CERN, 2023. #
4
+ // ######################################### #
5
+
6
+ #ifndef XCOLL_EVEREST_AMORPHOUS_H
7
+ #define XCOLL_EVEREST_AMORPHOUS_H
8
+ #include <math.h>
9
+ #include <stdio.h>
10
+ #include <stdlib.h>
11
+
12
+
13
+ /*gpufun*/
14
+ void volume_reflection(EverestData restrict everest, LocalParticle* part, int8_t transition) {
15
+
16
+ InteractionRecordData record = everest->coll->record;
17
+ RecordIndex record_index = everest->coll->record_index;
18
+ int8_t sc = everest->coll->record_scatterings;
19
+ int64_t i_slot;
20
+
21
+
22
+ double Ang_avr = everest->Ang_avr;
23
+ double Ang_rms = everest->Ang_rms;
24
+
25
+ if (transition == XC_VOLUME_REFLECTION_TRANS_CH){
26
+ // We are in transition from CH to VR
27
+ double xp = LocalParticle_get_xp(part);
28
+ // TODO: we believe the original 0.45 comes from the 0.9 saturation factor, so we changed it to 0.5
29
+ Ang_avr *= 0.5*((xp - everest->t_I)/everest->t_c + 1);
30
+ Ang_rms = 0; // TODO: why does transition to CH not use any spread?
31
+ if (sc) i_slot = InteractionRecordData_log(record, record_index, part, XC_VOLUME_REFLECTION_TRANS_CH);
32
+
33
+ } else if (transition == XC_VOLUME_REFLECTION_TRANS_MCS){
34
+ // We are in transition from VR to MCS
35
+ // double t_c = everest->t_c;
36
+ // double t_P = everest->t_P;
37
+ // double xp_rel = LocalParticle_get_xp(part) - everest->t_I;
38
+ // // TODO: where does 3 come from
39
+ // Ang_rms *= -3.*(xp_rel-t_P)/(2.*t_c); // TODO: why no random number?
40
+ Ang_rms *= RandomNormal_generate(part);
41
+ if (sc) i_slot = InteractionRecordData_log(record, record_index, part, XC_VOLUME_REFLECTION_TRANS_MCS);
42
+
43
+ } else {
44
+ Ang_rms *= RandomNormal_generate(part);
45
+ if (sc) i_slot = InteractionRecordData_log(record, record_index, part, XC_VOLUME_REFLECTION);
46
+ }
47
+
48
+ double t_VR = Ang_avr + Ang_rms;
49
+ LocalParticle_add_to_xp(part, t_VR);
50
+ if (sc) InteractionRecordData_log_child(record, i_slot, part, 0);
51
+ }
52
+
53
+
54
+ // Amorphous transport is just Multiple Coulomb scattering
55
+ /*gpufun*/
56
+ double amorphous_transport(EverestData restrict everest, LocalParticle* part, double pc, double length, int8_t transition) {
57
+
58
+ InteractionRecordData record = everest->coll->record;
59
+ RecordIndex record_index = everest->coll->record_index;
60
+ int8_t sc = everest->coll->record_scatterings;
61
+ int64_t i_slot;
62
+
63
+ // Accumulated effect of mcs on the angles (with initial energy)
64
+ // TODO: pc is energy
65
+ // TODO: missing factor (1 + 0.038*log( L / dlri) ) ( *Z if not protons)
66
+ double dya = (13.6/pc)*sqrt(length/everest->coll->dlri)*1.0e-3; // RMS of coloumb scattering MCS (rad)
67
+ double kxmcs, kymcs;
68
+
69
+ if (transition == XC_MULTIPLE_COULOMB_TRANS_VR){
70
+ // Transition MCS
71
+ if (sc) i_slot = InteractionRecordData_log(record, record_index, part, XC_MULTIPLE_COULOMB_TRANS_VR);
72
+ // double xp_rel = LocalParticle_get_xp(part) - everest->t_I;
73
+ // double t_P = everest->t_P;
74
+ // double t_c = everest->t_c;
75
+ // dya *= 1 - (xp_rel-t_P)/(2.*t_c);
76
+ } else {
77
+ // Normal MCS
78
+ if (sc) i_slot = InteractionRecordData_log(record, record_index, part, XC_MULTIPLE_COULOMB_SCATTERING);
79
+ }
80
+ kxmcs = dya*RandomNormal_generate(part);
81
+ kymcs = dya*RandomNormal_generate(part);
82
+
83
+ // Transport of Multiple Coulomb Scattering
84
+ Drift_single_particle_4d(part, length);
85
+
86
+ // Energy lost because of ionisation process[GeV]
87
+ double energy_loss = calcionloss(everest, part, length);
88
+ pc = pc - energy_loss*length;
89
+
90
+ // Store new angles
91
+ LocalParticle_add_to_xp_yp(part, kxmcs, kymcs);
92
+
93
+ // Finally log particle at end of mcs
94
+ if (sc) InteractionRecordData_log_child(record, i_slot, part, length);
95
+
96
+ return pc;
97
+ }
98
+
99
+ double Channel(EverestData restrict everest, LocalParticle* part, CrystalGeometry restrict cg, double pc, double length);
100
+
101
+ /*gpufun*/
102
+ double Amorphous(EverestData restrict everest, LocalParticle* part, CrystalGeometry restrict cg, double pc, double length) {
103
+
104
+ if (LocalParticle_get_state(part) < 1){
105
+ // Do nothing if already absorbed
106
+ return pc;
107
+ }
108
+
109
+ InteractionRecordData record = everest->coll->record;
110
+ RecordIndex record_index = everest->coll->record_index;
111
+ int8_t sc = everest->coll->record_scatterings;
112
+
113
+ calculate_initial_angle(everest, part, cg);
114
+
115
+ // -----------------------------------------------
116
+ // Calculate longitudinal position where we go out
117
+ // -----------------------------------------------
118
+ // Assumption: crystal bends away from beam (this does not work when crystal bends towards beam)
119
+ // All s are absolute in the collimator frame
120
+ double R = cg->bending_radius;
121
+ double t = cg->bending_angle;
122
+ double d = cg->width;
123
+ double s = LocalParticle_get_s(part);
124
+ double x = LocalParticle_get_x(part);
125
+ double xp = LocalParticle_get_xp(part);
126
+ // We either exit the crystal at the exit face
127
+ double s1 = (R - x + s*xp) / (xp + cos(t)/sin(t) );
128
+ if (s1 < s) {s1 = 1e10;}
129
+ // or the larger bend
130
+ double dd = (R - x + s*xp) / (1 + xp*xp);
131
+ double s2 = dd*xp + sqrt(R*R / (1 + xp*xp) * dd*dd);
132
+ if (s2 < s) {s2 = 1e10;}
133
+ // or the smaller bend (which potentially has two solutions, in case the particle enters again before the exit face)
134
+ double s3 = dd*xp - sqrt( (R-d)*(R-d) / (1 + xp*xp) * dd*dd);
135
+ if (s3 < s) {s3 = 1e10;}
136
+ // whichever comes first after the current position.
137
+ // This gives us an "exit length" \with respect to the current position
138
+ double exit_point = fmin(fmin(s1, s2), s3);
139
+ double length_exit = fmin(exit_point - s, length);
140
+
141
+ // ----------------------------------------------------
142
+ // Calculate longitudinal length of nuclear interaction
143
+ // ----------------------------------------------------
144
+ double length_nucl = everest->coll->collnt*RandomExponential_generate(part);
145
+
146
+ // --------------------------------------------------------------
147
+ // Calculate longitudinal length of volume interaction (VR or VC)
148
+ // --------------------------------------------------------------
149
+ // This happens when the particle is tangential to the crystal planes.
150
+ // The (straight) trajectory until the point of reflection is r sin(xp - t_I),
151
+ // hence the longitudinal length is r sin(xp - miscut) cos xp
152
+ double length_VI = everest->r * sin(xp - everest->t_I) * cos(xp);
153
+ // If length_VI is negative, VI is not possible, so set to a big value
154
+ if (length_VI < 0) {length_VI = 1e10;}
155
+ // Calculate extra length to transition region between VR and AM
156
+ double length_VR_trans = everest->r * sin(xp - everest->t_I - 2.*everest->t_c) * cos(xp - 2.*everest->t_c);
157
+ if (length_VR_trans < 0) {length_VR_trans = 1e10;}
158
+
159
+ // ------------------------------------------------------------------------
160
+ // Compare the 3 lengths: the first one encountered is what will be applied
161
+ // ------------------------------------------------------------------------
162
+ if (length_VI <= fmin(length_nucl, length_exit)){
163
+ // MCS to volume interaction
164
+ pc = amorphous_transport(everest, part, pc, length_VI, 0);
165
+ #ifdef XCOLL_REFINE_ENERGY
166
+ calculate_VI_parameters(everest, part, pc);
167
+ #endif
168
+ // Are we reflecting or captured?
169
+ if (RandomUniform_generate(part) > everest->Vcapt) {
170
+ // Volume Reflection
171
+ volume_reflection(everest, part, 0);
172
+ // We call the main Amorphous function for the leftover
173
+ pc = Amorphous(everest, part, cg, pc, length - length_VI);
174
+
175
+ } else {
176
+ // Volume Capture
177
+ if (sc) InteractionRecordData_log(record, record_index, part, XC_VOLUME_CAPTURE);
178
+ // We call the main Channel function for the leftover
179
+ pc = Channel(everest, part, cg, pc, length - length_VI);
180
+ }
181
+
182
+ #ifdef XCOLL_TRANSITION
183
+ } else if (length_VR_trans <= fmin(length_nucl, length_exit)){
184
+ // Transition region between VR and AM for t_P < xp - tI < t_P + 2t_c
185
+ #ifdef XCOLL_REFINE_ENERGY
186
+ calculate_critical_angle(everest, part, cg, pc);
187
+ #endif
188
+ double xp_rel = xp - everest->t_I;
189
+ double t_P = everest->t_P;
190
+ double t_c = everest->t_c;
191
+ double prob_MCS = (xp_rel - t_P) / (2*t_c);
192
+ if (RandomUniform_generate(part) > prob_MCS){
193
+ // We are on the VR side
194
+ pc = amorphous_transport(everest, part, pc, length_VR_trans, 0);
195
+ volume_reflection(everest, part, XC_VOLUME_REFLECTION_TRANS_MCS);
196
+ pc = amorphous_transport(everest, part, pc, length - length_VR_trans, 0);
197
+ } else {
198
+ // We are on the AM side
199
+ pc = amorphous_transport(everest, part, pc, length, XC_MULTIPLE_COULOMB_TRANS_VR);
200
+ }
201
+ #endif
202
+
203
+ } else if (length_nucl < length_exit) {
204
+ // MCS to nuclear interaction
205
+ pc = amorphous_transport(everest, part, pc, length_nucl, 0);
206
+ // interact
207
+ pc = nuclear_interaction(everest, part, pc);
208
+ if (LocalParticle_get_state(part) == XC_LOST_ON_EVEREST_COLL){
209
+ LocalParticle_set_state(part, XC_LOST_ON_EVEREST_CRYSTAL);
210
+ } else {
211
+ // We call the main Amorphous function for the leftover
212
+ pc = Amorphous(everest, part, cg, pc, length - length_nucl);
213
+ }
214
+
215
+ } else {
216
+ // Exit crystal
217
+ // MCS to exit point
218
+ pc = amorphous_transport(everest, part, pc, length_exit, 0);
219
+ // Now drift the remaining
220
+ // However, if we have exited at s3, and we encounter s4 before s2, we reenter:
221
+ double s4 = dd*xp + sqrt( (R-d)*(R-d) / (1 + xp*xp) * dd*dd); // second solution for smaller bend
222
+ if (s3 < fmin(s1, s2) && s4 < s2){
223
+ // We drift until re-entry
224
+ Drift_single_particle_4d(part, s4 - exit_point);
225
+ // We call the main Amorphous function for the leftover
226
+ pc = Amorphous(everest, part, cg, pc, length - length_exit - s4 + exit_point);
227
+ } else {
228
+ // Drift leftover out of the crystal
229
+ if (everest->coll->record_touches){
230
+ InteractionRecordData_log(record, record_index, part, XC_EXIT_JAW);
231
+ }
232
+ Drift_single_particle_4d(part, length - length_exit);
233
+ }
234
+ }
235
+
236
+ return pc;
237
+ }
238
+
239
+ #endif /* XCOLL_EVEREST_AMORPHOUS_H */
@@ -0,0 +1,245 @@
1
+ // copyright ############################### #
2
+ // This file is part of the Xcoll Package. #
3
+ // Copyright (c) CERN, 2023. #
4
+ // ######################################### #
5
+
6
+ #ifndef XCOLL_EVEREST_CHANNELING_H
7
+ #define XCOLL_EVEREST_CHANNELING_H
8
+ #include <math.h>
9
+ #include <stdio.h>
10
+ #include <stdlib.h>
11
+
12
+
13
+ // Convention:
14
+ // L: length of curved trajectory
15
+ // s: longitudinal coordinate (in collimator frame) - projection of curved trajectory
16
+ // t: opening angle of curve ('t' for theta)
17
+ // r: radius from position to bending centre (including t_I): L = t*r
18
+
19
+
20
+ /*gpufun*/
21
+ double channeling_average_density(EverestData restrict everest, CrystalGeometry restrict cg, LocalParticle* part, double pc) {
22
+
23
+ // Material properties
24
+ double const anuc = everest->coll->anuc;
25
+ double const rho = everest->coll->rho;
26
+ double const eum = everest->coll->eum;
27
+
28
+ double bend_r = cg->bending_radius;
29
+ double ratio = everest->Rc_over_R;
30
+
31
+ //Rescale the total and inelastic cross-section accordigly to the average density seen
32
+ double x_i = LocalParticle_get_x(part);
33
+ double xp = LocalParticle_get_xp(part);
34
+ int np = x_i/XC_PLANE_DISTANCE; //Calculate in which crystalline plane the particle enters
35
+ x_i -= (np + 0.5)*XC_PLANE_DISTANCE; //Rescale the incoming x to the middle of the crystalline plane
36
+
37
+ double pv = pow(pc, 2.)/sqrt(pow(pc, 2.) + pow(XC_PROTON_MASS*1.0e-3, 2.))*1.0e9; //Calculate pv=P/E TODO: this is beta?
38
+ double Ueff = eum*4.*pow(x_i/XC_PLANE_DISTANCE, 2.) + pv*x_i/bend_r; //Calculate effective potential
39
+ double Et = pv*pow(xp, 2.)/2. + Ueff; //Calculate transverse energy
40
+ double Ec = eum*pow(1. - ratio, 2.); //Calculate critical energy in bent crystals
41
+
42
+ //To avoid negative Et
43
+ double xminU = -pow(XC_PLANE_DISTANCE, 2.)*pc*1.0e9/(8.*eum*bend_r);
44
+ double Umin = fabs(eum*4.*pow(xminU/XC_PLANE_DISTANCE, 2.) + pv*xminU/bend_r);
45
+ Et = Et + Umin;
46
+ Ec = Ec + Umin;
47
+
48
+ //Calculate min e max of the trajectory between crystalline planes
49
+ double x_min = -0.5*XC_PLANE_DISTANCE*ratio - 0.5*XC_PLANE_DISTANCE*sqrt(Et/Ec);
50
+ double x_max = -0.5*XC_PLANE_DISTANCE*ratio + 0.5*XC_PLANE_DISTANCE*sqrt(Et/Ec);
51
+
52
+ //Change ref. frame and go back with 0 on the crystalline plane on the left
53
+ x_min = x_min - XC_PLANE_DISTANCE/2.;
54
+ x_max = x_max - XC_PLANE_DISTANCE/2.;
55
+
56
+ //Calculate the "normal density" in m^-3
57
+ double N_am = rho*XC_AVOGADRO*1.0e6/anuc;
58
+
59
+ //Calculate atomic density at min and max of the trajectory oscillation
60
+ // erf returns the error function of complex argument
61
+ double rho_max = erf(x_max/sqrt(2*pow(XC_THERMAL_VIBRATIONS, 2.)));
62
+ rho_max -= erf((XC_PLANE_DISTANCE-x_max)/sqrt(2*pow(XC_THERMAL_VIBRATIONS, 2.)));
63
+ rho_max *= N_am*XC_PLANE_DISTANCE/2.;
64
+ double rho_min = erf(x_min/sqrt(2*pow(XC_THERMAL_VIBRATIONS, 2.)));
65
+ rho_min -= erf((XC_PLANE_DISTANCE-x_min)/sqrt(2*pow(XC_THERMAL_VIBRATIONS, 2.)));
66
+ rho_min *= N_am*XC_PLANE_DISTANCE/2.;
67
+
68
+ //"zero-approximation" of average nuclear density seen along the trajectory
69
+ double avrrho = (rho_max - rho_min)/(x_max - x_min);
70
+ avrrho = 2.*avrrho/N_am;
71
+
72
+ return avrrho;
73
+ }
74
+
75
+
76
+ /*gpufun*/
77
+ double* channel_transport(EverestData restrict everest, LocalParticle* part, double pc, double L_chan, double t_I, double t_P) {
78
+ // Channeling: happens over an arc length L_chan (potentially less if dechanneling)
79
+ // This equates to an opening angle t_P wrt. to the point P (center of miscut if at start of crystal)
80
+ // The chord angle xp at the start of channeling (I) is t_P/2 + t_I
81
+ // The angle xp at the end of channeling (F) is t_P + t_I
82
+ // In practice: we drift from start to end, but overwrite the angle afterwards
83
+ // TODO: why does channeling only have 50% energy loss?
84
+
85
+ double* result = (double*)malloc(2 * sizeof(double));
86
+
87
+ InteractionRecordData record = everest->coll->record;
88
+ RecordIndex record_index = everest->coll->record_index;
89
+ int8_t sc = everest->coll->record_scatterings;
90
+
91
+ // First log particle at start of channeling
92
+ int64_t i_slot;
93
+ if (sc) i_slot = InteractionRecordData_log(record, record_index, part, XC_CHANNELING);
94
+
95
+ // Do channeling.
96
+ // The distance from I to F is the chord length of the angle t_P: d = 2 r sin(t_P/2)
97
+ // Hence the longitudinal distance (the length to be drifted) is the projection of this using the
98
+ // xp at the start of channeling: s = 2 r sin(t_P/2)cos(t_P/2 + t_I)
99
+ double t_chord= t_I + t_P/2.;
100
+ double drift_length = 2.*L_chan/t_P * sin(t_P/2.) * cos(t_chord);
101
+ LocalParticle_set_xp(part, t_chord); // Angle at start of channeling
102
+ Drift_single_particle_4d(part, drift_length);
103
+ // In reality,the particle oscillates horizontally between the planes while channeling.
104
+ // This effect is mimicked by giving a random angle spread at the exit
105
+ double sigma_ran = 0.5*everest->t_c;
106
+ double ran_angle = RandomNormal_generate(part)*sigma_ran;
107
+ LocalParticle_set_xp(part, t_I + t_P + ran_angle); // Angle at end of channeling
108
+
109
+ // Apply energy loss along trajectory
110
+ double energy_loss = 0.5*calcionloss(everest, part, L_chan);
111
+ // TODO: LocalParticle_add_to_energy(part, - energy_loss*L_chan*1.e9, change_angle);
112
+ // if change_angle = 0 => LocalParticle_scale_px(part, old_rpp / new_rpp) such that xp remains the same
113
+ // It is done in K2, so we should do it. Though, it seems that with the current implementation in xtrack
114
+ // this is no longer correct with exact drifts...?
115
+ pc = pc - energy_loss*L_chan; //energy loss to ionization [GeV]
116
+
117
+ // Finally log particle at end of channeling
118
+ if (sc) InteractionRecordData_log_child(record, i_slot, part, drift_length);
119
+
120
+ result[0] = drift_length;
121
+ result[1] = pc;
122
+ return result;
123
+ }
124
+
125
+
126
+ double Channel(EverestData restrict everest, LocalParticle* part, CrystalGeometry restrict cg, double pc, double length) {
127
+
128
+ if (LocalParticle_get_state(part) < 1){
129
+ // Do nothing if already absorbed
130
+ return pc;
131
+ }
132
+
133
+ InteractionRecordData record = everest->coll->record;
134
+ RecordIndex record_index = everest->coll->record_index;
135
+ int8_t sc = everest->coll->record_scatterings;
136
+
137
+ calculate_initial_angle(everest, part, cg);
138
+ #ifdef XCOLL_REFINE_ENERGY
139
+ calculate_critical_angle(everest, part, cg, pc);
140
+ #endif
141
+
142
+ // Do we channel, or are we in the transition between channeling and VR?
143
+ double xp = LocalParticle_get_xp(part);
144
+ double alpha = fabs(xp - everest->t_I) / everest->t_c;
145
+ double ratio = everest->Rc_over_R;
146
+ double xi = RandomUniform_generate(part)/(1 - ratio)/sqrt(everest->coll->eta);
147
+
148
+ if (xi > 1 || alpha > 2*sqrt(xi)*sqrt(1-xi)) {
149
+ #ifdef XCOLL_TRANSITION
150
+ // TRANSITION
151
+ // We feel that this transition is not needed, as it interpolates between two regions
152
+ // (adding a slant below the channeling region) which does not seem to be present in
153
+ // experimental data.
154
+ #ifdef XCOLL_REFINE_ENERGY
155
+ calculate_VI_parameters(everest, part, pc);
156
+ #endif
157
+ volume_reflection(everest, part, XC_VOLUME_REFLECTION_TRANS_CH);
158
+ #endif
159
+ pc = Amorphous(everest, part, cg, pc, length);
160
+
161
+ } else {
162
+ // CHANNEL
163
+ calculate_opening_angle(everest, part, cg);
164
+ double t_I = everest->t_I;
165
+ double t_P = everest->t_P;
166
+ double L_chan = everest->r*t_P;
167
+
168
+ // ------------------------------------------------
169
+ // Calculate curved length L_dechan of dechanneling
170
+ // ------------------------------------------------
171
+ double const_dech = calculate_dechanneling_length(everest, pc);
172
+ double TLdech1 = const_dech*pc*pow(1. - ratio, 2.); //Updated calculate typical dech. length(m)
173
+ double N_atom = 1.0e-1;
174
+ if(RandomUniform_generate(part) <= N_atom) {
175
+ TLdech1 /= 200.; // Updated dechanneling length (m)
176
+ }
177
+ double L_dechan = TLdech1*RandomExponential_generate(part); // Actual dechan. length
178
+
179
+ // -----------------------------------------------------
180
+ // Calculate curved length L_nucl of nuclear interaction
181
+ // -----------------------------------------------------
182
+ // Nuclear interaction length is rescaled in this case, because channeling
183
+ double collnt = everest->coll->collnt;
184
+ double avrrho = channeling_average_density(everest, cg, part, pc);
185
+ if (avrrho == 0) {
186
+ collnt = 1.e10; // very large because essentially 1/0
187
+ } else {
188
+ collnt = collnt/avrrho;
189
+ }
190
+ double L_nucl = collnt*RandomExponential_generate(part);
191
+
192
+ // printf("Channeling (%f -> %f): L_c: %f, L_d: %f, L_n: %f\n", t_I, t_P, L_chan, L_dechan, L_nucl);
193
+ // ------------------------------------------------------------------------
194
+ // Compare the 3 lengths: the first one encountered is what will be applied
195
+ // ------------------------------------------------------------------------
196
+ if (L_chan <= fmin(L_dechan, L_nucl)){
197
+ // Channel full length
198
+ double* result_chan = channel_transport(everest, part, pc, L_chan, t_I, t_P);
199
+ double channeled_length = result_chan[0];
200
+ pc = result_chan[1];
201
+ free(result_chan);
202
+ // Drift leftover outside of crystal
203
+ if (everest->coll->record_touches){
204
+ InteractionRecordData_log(record, record_index, part, XC_EXIT_JAW);
205
+ }
206
+ Drift_single_particle_4d(part, length - channeled_length);
207
+
208
+ } else if (L_dechan < L_nucl) {
209
+ // Channel up to L_dechan, then amorphous
210
+ double* result_chan = channel_transport(everest, part, pc, L_dechan, t_I, t_P*L_dechan/L_chan);
211
+ double channeled_length = result_chan[0];
212
+ pc = result_chan[1];
213
+ free(result_chan);
214
+ if (sc) InteractionRecordData_log(record, record_index, part, XC_DECHANNELING);
215
+ pc = Amorphous(everest, part, cg, pc, length - channeled_length);
216
+
217
+ } else {
218
+ // Channel up to L_nucl, then scatter, then amorphous
219
+ double* result_chan = channel_transport(everest, part, pc, L_nucl, t_I, t_P*L_nucl/L_chan);
220
+ double channeled_length = result_chan[0];
221
+ pc = result_chan[1];
222
+ free(result_chan);
223
+ // Rescale nuclear interaction parameters
224
+ everest->rescale_scattering = avrrho;
225
+ #ifndef XCOLL_REFINE_ENERGY
226
+ calculate_scattering(everest, pc);
227
+ #endif
228
+ pc = nuclear_interaction(everest, part, pc);
229
+ if (LocalParticle_get_state(part) == XC_LOST_ON_EVEREST_COLL){
230
+ LocalParticle_set_state(part, XC_LOST_ON_EVEREST_CRYSTAL);
231
+ } else {
232
+ // We call the main Amorphous function for the leftover
233
+ everest->rescale_scattering = 1;
234
+ #ifndef XCOLL_REFINE_ENERGY
235
+ calculate_scattering(everest, pc);
236
+ #endif
237
+ pc = Amorphous(everest, part, cg, pc, length - channeled_length);
238
+ }
239
+ }
240
+ }
241
+
242
+ return pc;
243
+ }
244
+
245
+ #endif /* XCOLL_EVEREST_CHANNELING_H */
@@ -0,0 +1,137 @@
1
+ // copyright ############################### #
2
+ // This file is part of the Xcoll Package. #
3
+ // Copyright (c) CERN, 2023. #
4
+ // ######################################### #
5
+
6
+ #ifndef XCOLL_EVEREST_CRYSTAL_PARAMETERS_H
7
+ #define XCOLL_EVEREST_CRYSTAL_PARAMETERS_H
8
+ #include <math.h>
9
+ #include <stdio.h>
10
+ #include <stdlib.h>
11
+
12
+
13
+ /*gpufun*/
14
+ void calculate_initial_angle(EverestData restrict everest, LocalParticle* part, CrystalGeometry restrict cg) {
15
+ double R = cg->bending_radius;
16
+ double s = LocalParticle_get_s(part);
17
+ double x = LocalParticle_get_x(part);
18
+ double s_P = cg->s_P;
19
+ double x_P = cg->x_P;
20
+ double r = sqrt((s-s_P)*(s-s_P) + (x-x_P)*(x-x_P));
21
+ everest->r = r;
22
+ everest->t_I = R/fabs(R)*asin( (s-s_P)/r); // Tangent angle of the channeling planes (not neccecarly the same as xp)
23
+ }
24
+
25
+
26
+ /*gpufun*/
27
+ void calculate_opening_angle(EverestData restrict everest, LocalParticle* part, CrystalGeometry restrict cg) {
28
+ double t = cg->bending_angle;
29
+ double xd = cg->width;
30
+ double R = cg->bending_radius;
31
+ double sinp = sin(cg->miscut_angle);
32
+ double cosp = cos(cg->miscut_angle);
33
+
34
+ // Radius from starting point to miscut centre
35
+ double r = everest->r;
36
+ // Intersection with exit face
37
+ double bb = R/2. * sin(2.*t) * (1 - cosp - tan(t)*sinp);
38
+ double s_F = bb + sqrt(bb*bb - tan(t)*sin(2.*t)*(R*R*(1-cosp) - r*r/2.));
39
+ double x_F = R - s_F/tan(t);
40
+
41
+ // We could intersect with the upper (positive miscut) or lower bend (negative miscut) before the exit face
42
+ // Distance between bending centre and miscut centre:
43
+ double d = R*sqrt(2*(1-cosp));
44
+ if (cg->miscut_angle > 0){
45
+ // Check if intersection with upper bend R-xd is possible
46
+ double Rb = R-xd;
47
+ if (d > fabs(r - Rb)){
48
+ double st_UB = (r*r - Rb*Rb)/(2*d);
49
+ double xt_UB = -sqrt(r*r/2. + Rb*Rb/2. - st_UB*st_UB - d*d/4.);
50
+ double s_UB = (st_UB/d - 0.5)*R*sinp - xt_UB/d*R*(1-cosp);
51
+ if (s_F > s_UB){
52
+ // Upper bend encountered before exit face
53
+ s_F = s_UB;
54
+ x_F = (st_UB/d + 0.5)*R*(1-cosp) + xt_UB/d*R*sinp + R*cosp;
55
+ }
56
+ }
57
+ } else {
58
+ // Check if intersection with lower bend R is possible
59
+ if (d > fabs(R - r)){
60
+ double st_LB = (R*R - r*r)/(2*d);
61
+ double xt_LB = -sqrt(r*r/2. + R*R/2. - st_LB*st_LB - d*d/4.);
62
+ double s_LB = -(st_LB/d + 0.5)*R*sinp + xt_LB/d*R*(1-cosp);
63
+ if (s_F > s_LB){
64
+ // Lower bend encountered before exit face
65
+ s_F = s_LB;
66
+ x_F = -(st_LB/d - 0.5)*R*(1-cosp) - xt_LB/d*R*sinp + R*cosp;
67
+ }
68
+ }
69
+ }
70
+
71
+ // Opening angle of channeling trajectory
72
+ double s = LocalParticle_get_s(part);
73
+ double x = LocalParticle_get_x(part);
74
+ everest->t_P = acos(1 - ( (s_F-s)*(s_F-s) - (x_F-x)*(x_F-x) ) / (2*r*r) );
75
+ }
76
+
77
+
78
+ /*gpufun*/
79
+ void calculate_critical_angle(EverestData restrict everest, LocalParticle* part, CrystalGeometry restrict cg, double pc) {
80
+ // Define typical angles/probabilities for orientation 110
81
+ double eum = everest->coll->eum;
82
+ double ai = everest->coll->ai;
83
+ double eta = everest->coll->eta;
84
+ double t_c0 = sqrt(2.e-9*eta*eum/pc); // Critical angle (rad) for straight crystals // pc is actually beta pc
85
+ double Rcrit = pc/(2.e-6*sqrt(eta)*eum)*ai; // Critical curvature radius [m] // pc is actually beta pc
86
+
87
+ // If Rcritical > R => no channeling is possible (Rc_over_R > 1)
88
+ everest->Rc_over_R = Rcrit / fabs(cg->bending_radius);
89
+ everest->t_c0 = t_c0;
90
+ everest->t_c = t_c0*(1 - everest->Rc_over_R); // Critical angle for curved crystal
91
+
92
+ if (everest->coll->orient == 2) {
93
+ everest->t_c *= 0.98;
94
+ }
95
+ }
96
+
97
+
98
+ /*gpufun*/
99
+ void calculate_VI_parameters(EverestData restrict everest, LocalParticle* part, double pc) {
100
+
101
+ double ratio = everest->Rc_over_R;
102
+ double t_c0 = everest->t_c0;
103
+
104
+ // Correction by sasha drozdin/armen // TODO: pc = energy
105
+ // K=0.0007 is taken based on simulations using CATCH.f (V.Biryukov)
106
+ // TODO: From the paper (arXiv:0808.1486, eq (18), (34)) it seems the power of E should be 0.25
107
+ // Typo in Daniele's thesis in 3.25 (E^0.2 instead of 1/E^0.2)
108
+ everest->Vcapt = 7.e-4*(1./ratio - 0.7)/pow(pc, 0.2);
109
+
110
+ double Ang_rms, Ang_avr;
111
+ double c1 = -3./2.; // Fitting coefficient
112
+ double c2 = 5./3.; // Fitting coefficient
113
+ double c3 = 1.7; // Fitting coefficient
114
+ if (ratio > 1.) {
115
+ // no channeling possibile
116
+ Ang_avr = c1*t_c0*5.e-2/ratio; // Average angle reflection
117
+ Ang_rms = c3*0.42*t_c0*sin(1.4/ratio); // RMS scattering
118
+ everest->Vcapt = 0.; // Probability of VC is zero
119
+ } else if (ratio > 1./3.) {
120
+ // Strongly bent crystal
121
+ Ang_avr = c1*t_c0*(0.1972/ratio - 0.1472); // Average angle reflection
122
+ Ang_rms = c3*0.42*t_c0*sin(0.4713/ratio + 0.85); // RMS scattering
123
+ } else {
124
+ // Rcrit << R
125
+ Ang_avr = c1*t_c0*(1. - c2*ratio); // Average angle for VR
126
+ Ang_rms = c3*t_c0*ratio; // RMS scattering
127
+ }
128
+ if (everest->coll->orient == 2) {
129
+ Ang_avr = Ang_avr*0.93;
130
+ Ang_rms = Ang_rms*1.05;
131
+ }
132
+
133
+ everest->Ang_rms = Ang_rms;
134
+ everest->Ang_avr = Ang_avr;
135
+ }
136
+
137
+ #endif /* XCOLL_EVEREST_CRYSTAL_PARAMETERS_H */