Geant4 11.4.0
Toolkit for the simulation of the passage of particles through matter
Loading...
Searching...
No Matches
G4QSStepper.cc
Go to the documentation of this file.
1//
2// ********************************************************************
3// * License and Disclaimer *
4// * *
5// * The Geant4 software is copyright of the Copyright Holders of *
6// * the Geant4 Collaboration. It is provided under the terms and *
7// * conditions of the Geant4 Software License, included in the file *
8// * LICENSE and available at http://cern.ch/geant4/license . These *
9// * include a list of copyright holders. *
10// * *
11// * Neither the authors of this software system, nor their employing *
12// * institutes,nor the agencies providing financial support for this *
13// * work make any representation or warranty, express or implied, *
14// * regarding this software system or assume any liability for its *
15// * use. Please see the license in the file LICENSE and URL above *
16// * for the full disclaimer and the limitation of liability. *
17// * *
18// * This code implementation is the result of the scientific and *
19// * technical work of the GEANT4 collaboration. *
20// * By using, copying, modifying or distributing the software (or *
21// * any work based on the software) you agree to acknowledge its *
22// * use in resulting scientific publications, and indicate your *
23// * acceptance of all terms of the Geant4 Software license. *
24// ********************************************************************
25//
26// G4QSStepper
27//
28// QSS Integrator Stepper
29//
30// Authors: version 1 - Lucio Santi, Rodrigo Castro (Univ. Buenos Aires), 2018-2021
31// version 2 - Mattias Portnoy (Univ. Buenos Aires), 2024
32// --------------------------------------------------------------------
33
34#include "G4QSStepper.hh"
35#include "G4QSSMessenger.hh"
36#include "G4AutoLock.hh"
37
38#include <algorithm>
39
40namespace
41{
42 // Mutex to lock updating the global ion map
43 G4Mutex qssOrderCheckMutex = G4MUTEX_INITIALIZER;
44}
45
46// ----------------------------------------------------------------------------
47
49 G4int , // num_integration_vars is always 6 -- Needed for ctor in Integration Driver)
50 G4int qssOrder ):
52 // num_integration_vars, num_state_vars, isFSAL)
53 qss_order( qssOrder <= 0 ? G4QSSMessenger::instance()->GetQssOrder() : qssOrder )
54{
55 using std::memset;
56 SetIsQSS(true);
57
58 // Note: current_substep is initialised by 'Substep' constructor
59
60 if( qss_order < 2 || qss_order > 3 )
61 {
63 err_msg << "-G4QSStepper/c-tor: qss_order= " << qss_order << " is not either 2 or 3 ";
64 G4Exception("G4QSStepper::G4QSStepper", "GeomMag-0001", FatalException, err_msg );
65 }
66
67 auto messenger= G4QSSMessenger::instance();
68
69 if( qss_order != messenger->GetQssOrder() )
70 {
71 // BARRIER
72 G4AutoLock lock(qssOrderCheckMutex);
73
74 if( qss_order != messenger->GetQssOrder() )
75 {
76 static std::atomic<G4int> change_of_order= 0;
77 change_of_order++;
78
79 if( change_of_order == 1 )
80 {
82 }
83 else
84 {
86 err_msg << "G4QSSStepper: Trying to change order of QSS stepper(s) for "
87 << change_of_order << " th time -- Maximum allowed is 1 time. "
88 << " Requesting order = " << qss_order
89 << " whereas current (static) value = " << messenger->GetQssOrder()
90 << G4endl;
91 G4Exception("G4QSStepper::G4QSStepper","G4QSS-0002",FatalException, err_msg);
92 }
93 }
94 }
95
96 // Place-holder values -- will be initialised for each particle track
97 fCharge_c2 = fCharge * ( 1.0 / (CLHEP::c_light*CLHEP::c_light) );
98 G4double momentum[3]= { 0.0, 0.0, 0.0 };
99 set_relativistic_coeff(momentum);
100 std::fill_n( fYout, 12, 0.0 );
101}
102
103
104// ----------------------------------------------------------------------------
105
107{
108 G4double momentum2 = momentum[0]*momentum[0] + momentum[1]*momentum[1] + momentum[2]*momentum[2];
109 fGamma = std::sqrt(momentum2/(fRestMass*fRestMass) + 1);
110 G4double mass_times_gamma = fRestMass * fGamma;
111 fMassOverC = mass_times_gamma * (1.0 / CLHEP::c_light);
112 fInv_mass_over_c = CLHEP::c_light * (1.0 / mass_times_gamma);
113 fCoeff = fCharge_c2 / mass_times_gamma;
114}
115
116// ----------------------------------------------------------------------------
117
119{
120 using std::memset;
121
122 substeps.reset();
123
124 // Load values particle's invariants
125 if (fCurrent_track != nullptr)
126 {
127 fCharge = fCurrent_track->GetCharge();
128 fCharge_c2 = fCharge * CLHEP::c_squared; /// Was 89875.5178737;
129 setRestMass( fCurrent_track->GetRestMass() );
130 }
131
132 // y contains postion in first 3 index and momentum on the next 3
134
135 G4double velocity_vector[3];
136 momentum_to_velocity(&y[3], velocity_vector);
137 fVelocity = std::sqrt(velocity_vector[0]*velocity_vector[0] + velocity_vector[1]*velocity_vector[1] + velocity_vector[2]*velocity_vector[2] );
138
139 std::copy_n( y, 3, &current_substep.state_x[DERIVATIVE_0][POSITION_IDX] );
140 std::copy_n( velocity_vector, 3, &current_substep.state_x[DERIVATIVE_0][VELOCITY_IDX] );
141 // Copy into state_q
142 std::copy_n( &current_substep.state_x[DERIVATIVE_0][0], 6, &current_substep.state_q[DERIVATIVE_0][0] );
143
144 for (G4int i = 1; i < qss_order; ++i)
145 {
146 std::fill_n(current_substep.state_q[i], NUMBER_OF_VARIABLES_QSS, 0.0);
147 }
148
149 std::fill_n(current_substep.state_tx, NUMBER_OF_VARIABLES_QSS, 0.0);
150 std::fill_n(current_substep.state_tq, NUMBER_OF_VARIABLES_QSS, 0.0);
151
152 current_substep.t = 0;
153 current_substep.extrapolation_method = qss_order;
154
155 update_field();
156 for (G4int i = 0; i < NUMBER_OF_VARIABLES_QSS; ++i)
157 {
158 dq_vector[i] = std::fmax(dqmin[INDEX_TYPE(i)], dqrel[INDEX_TYPE(i)] * std::fabs(current_substep.state_x[DERIVATIVE_0][i]));
161 }
162}
163
164// ----------------------------------------------------------------------------
165
167{
168 G4double &dq = dq_vector[index];
169 G4double delta_sync_t = INFTY;
170 // polynomial coefficients in increasing order of power, constant, linear, quadratic, etc
171 G4double c, b, a, h;
172
173 a = current_substep.state_x[DERIVATIVE_2][index]/2;
174 b = current_substep.state_x[DERIVATIVE_1][index] - current_substep.state_q[DERIVATIVE_1][index] ;
175 c = current_substep.state_x[DERIVATIVE_0][index] - current_substep.state_q[DERIVATIVE_0][index];
176
177 // third order polynomial. It's a long algorithm but not a complex one
178 if (qss_order == 3 && current_substep.state_x[DERIVATIVE_3][index] != 0.0)
179 {
180 // extra coefficient and h for the cubic polynomial and inclusion of the second order term from q
181 h = current_substep.state_x[DERIVATIVE_3][index]/6;
182 a -= current_substep.state_q[DERIVATIVE_2][index]/2;
183
184 G4double q_cube = fCharge*fCharge*fCharge;
185
186 // special case of | h * t3 | = dq
187 if (a == 0 && b == 0 && c == 0)
188 {
189 delta_sync_t = cbrt(std::fabs(dq/h));
190 }
191 else
192 {
193 a /= h;
194 b /= h;
195 c /= h;
196
197 G4double qLocal = (a * a - 3 * b) * (1.0 / 9.0);
198 G4double r_base = (2*a*a*a - 9*a*b + 27*c)*(1.0/54.0);
199
200 G4double sqrt_q = std::sqrt(qLocal);
201 G4double sqrt_q_cube = std::sqrt(q_cube);
202 G4double a_over_3 = a/3;
203 for (G4double dQ : {dq,-dq})
204 {
205 G4double r = r_base + dQ/(2*h);
206 // three real roots
207 if (r*r < q_cube)
208 {
209 G4double theta = std::acos(r/sqrt_q_cube);
210 G4double t1 = -2*sqrt_q*std::cos((1./3.)*theta) - a_over_3;
211 G4double t2 = -2*sqrt_q*std::cos((1./3.)*(theta+2*CLHEP::pi)) - a_over_3;
212 G4double t3 = -2*sqrt_q*std::cos((1./3.)*(theta-2*CLHEP::pi)) - a_over_3;
213 for (G4double t : {t1,t2,t3})
214 {
215 if (t > 0) { delta_sync_t = std::fmin(delta_sync_t,t); }
216 }
217 }
218 // one real root
219 else
220 {
221 G4double A = -copysign(1,r) * cbrt(std::fabs(r) + std::sqrt(r*r - q_cube));
222 G4double B = A == 0 ? 0 : qLocal/A;
223 G4double t1 = A + B - a_over_3;
224 if (t1 > 0) {delta_sync_t = std::fmin(delta_sync_t,t1);}
225 }
226 }
227 }
228 }
229
230 // first order polynomial
231 else if (qss_order == 1 || a == 0)
232 {
233 // dq = | b * t + c |
234 if (b == 0) { delta_sync_t = INFTY; }
235 // (dq-c)/b > 0 <--> (b > 0 && dq > c) || (b < 0 && dq < c)
236 // so we use dq if any of the cases holds and -dq if not
237 else if ( (b > 0) == (dq > c) ) { delta_sync_t = (dq-c)/b; }
238 else { delta_sync_t = (-dq-c)/b; }
239 }
240 // second order polynomial
241 else
242 {
243 if (b == 0)
244 {
245 // dq = | a_x * t2 + c |
246 // identical to first order case but with sqrt
247 if ((a > 0) == (dq > c)) {delta_sync_t = std::sqrt((dq-c)/a);}
248 else {delta_sync_t = std::sqrt((-dq-c)/a);}
249 }
250 else
251 {
252 // check both discriminants for both dq and - dq
253 G4double a4 = 4*a;
254 G4double a2 = 2*a;
255 G4double discriminator_base = b*b - a4*c;
256 G4double discriminator_difference = a4*dq;
257 G4double discriminator_1 = discriminator_base + discriminator_difference;
258 G4double discriminator_2 = discriminator_base - discriminator_difference;
259 G4double fixed_solution_part = -b/a2;
260
261 // simple trick to combine answers from all 4 solutions
262 for(G4double discriminator : {discriminator_1, discriminator_2})
263 {
264 if (discriminator < 0) { continue; }
265 G4double variable_solution_part = std::sqrt(discriminator)/std::fabs(a2);
266 G4double t_local = fixed_solution_part - variable_solution_part;
267
268 if (t_local <= 0 )
269 {
270 t_local = fixed_solution_part + variable_solution_part;
271 }
272 if (t_local > 0) { delta_sync_t = std::fmin(delta_sync_t,t_local); }
273 }
274 }
275 }
276
277 current_substep.sync_t[index] = current_substep.state_tx[index] + delta_sync_t;
278}
279
280// ----------------------------------------------------------------------------
281
283 const G4double /*dydx*/ [],
284 G4double h,
285 G4double yout[],
286 G4double /* yerr */ [] )
287{
288 using std::memcpy;
289
290 initialize(y);
291
292 const G4int QSS_MAX_SUBSTEPS = G4QSSMessenger::instance()->GetMaxSubsteps();
293
294 G4double t = 0;
295
296 fFinal_t = h/fVelocity;
297 fFinal_t = std::fmin(fFinal_t,INFTY);
298
299 while (t < fFinal_t && t < INFTY && substeps.current_substep_index < QSS_MAX_SUBSTEPS)
300 {
301 substeps.save_substep(&current_substep);
302
303 // get minimum that makes some variable get too far from its quantized version
304 G4int sync_index = get_next_sync_index();
305 t = current_substep.sync_t[sync_index];
306 t = std::fmin(t,fFinal_t);
307 current_substep.t = t;
308
309 // sync both and update their data
310 // update x
311 update_x(sync_index,t);
312
313 // sync q
314 current_substep.state_q[DERIVATIVE_0][sync_index] = current_substep.state_x[DERIVATIVE_0][sync_index];
315 current_substep.state_q[DERIVATIVE_1][sync_index] = current_substep.state_x[DERIVATIVE_1][sync_index];
316 current_substep.state_q[DERIVATIVE_2][sync_index] = current_substep.state_x[DERIVATIVE_2][sync_index];
317
318 current_substep.state_tq[sync_index] = current_substep.state_tx[sync_index];
319
320 dq_vector[sync_index] = std::fmax(dqmin[INDEX_TYPE(sync_index)], dqrel[INDEX_TYPE(sync_index)] * std::fabs(current_substep.state_x[DERIVATIVE_0][sync_index]));
321
322
323 // Somehow this seems to be faster than the one below
324 update_sync_time(sync_index);
325 // the trick belows work but seems to be slower
326 //update_sync_time_one_coefficient(sync_index);
327
328
329 // only update field if we actually changed position, not velocity
330 // previous version called this every time which is unnecessary if field constant, and we bite the bullet if not
331 if (sync_index < VELOCITY_IDX) { update_field(); }
332
333 // we need to update the affected derivates of the other states
334 G4double &tIndex = current_substep.state_tx[sync_index];
335
336
337 // if we update position but magnetic field hasn't change then no other variables are affected!
338 if(sync_index < VELOCITY_IDX && ! fField_changed) { continue; }
339
340 // as qs are in different ts, we need to extrapolate the needed qs
341 // we always need to extrapolate the velocity ones (because the lorentz equation)
342 update_q(VX,tIndex);
343 update_q(VY,tIndex);
344 update_q(VZ,tIndex);
345
346
347 // check which equations are altered by this update according to lorentz eq
348
349 // b-field changed, need to update velocity states derivates
350 if (sync_index < VELOCITY_IDX)
351 {
352 for (G4int i = VELOCITY_IDX; i < 6; ++i)
353 {
354 update_x(i,tIndex);
357 }
358 }
359
360 // velocity changed, need the other velocity states derivates and the corresponding position one
361 else
362 {
363 G4int indexDep1 = (sync_index + 2)%VELOCITY_IDX + VELOCITY_IDX;
364 G4int indexDep2 = (sync_index + 1)%VELOCITY_IDX + VELOCITY_IDX;
365 G4int index_class = sync_index - VELOCITY_IDX;
366 update_q(index_class,tIndex); // not updated before so we need to update it
367 for (G4int i : {indexDep1, indexDep2, index_class})
368 {
369 update_x(i,tIndex);
372 }
373 }
374 }
375 if(substeps.current_substep_index >= QSS_MAX_SUBSTEPS)
376 {
377 fFinal_t = current_substep.t;
378 }
379
380 for (G4int i = 0; i < NUMBER_OF_VARIABLES_QSS; ++i)
381 {
382 update_x(i, fFinal_t);
383 }
384 memcpy(yout, &current_substep.state_x[DERIVATIVE_0], sizeof(QSStateVector));
385
387
388 // fyout is used by interpolation driver, so we have to do this
389 memcpy(fYout,yout,NUMBER_OF_VARIABLES_QSS*sizeof(G4double));
390}
391
392// ----------------------------------------------------------------------------
393
395{
396 G4double target_t = current_substep.t * tau;
397 G4int i = 0;
398 G4double t = current_substep.t * tau;;
399 // linear search
400 if (substeps.current_substep_index < 20)
401 {
402 while(i < substeps.current_substep_index && substeps._substeps[i+1].t <= target_t )
403 {
404 i++;
405 }
406 }
407 // binary search
408 else
409 {
410 G4int high_i = substeps.current_substep_index;
411 G4int low_i = 0;
412 G4int idx = high_i >> 1;
413 while(low_i < high_i-1)
414 {
415 if(target_t < substeps._substeps[idx].t)
416 {
417 high_i = idx;
418 }
419 else
420 {
421 low_i = idx;
422 }
423 idx = (low_i+high_i) >> 1;
424 }
425 i = low_i;
426 }
427
428 extrapolate_all_states_to_t(&substeps._substeps[i], t, yOut);
429
431}
G4TemplateAutoLock< G4Mutex > G4AutoLock
G4double B(G4double temperature)
@ FatalException
void G4Exception(const char *originOfException, const char *exceptionCode, G4ExceptionSeverity severity, const char *description)
std::ostringstream G4ExceptionDescription
G4double QSStateVector[6]
constexpr G4int NUMBER_OF_VARIABLES_QSS
G4TemplateRNGHelper< G4long > * G4TemplateRNGHelper< G4long >::instance
#define G4MUTEX_INITIALIZER
std::mutex G4Mutex
double G4double
Definition G4Types.hh:83
int G4int
Definition G4Types.hh:85
const G4double A[17]
#define G4endl
Definition G4ios.hh:67
G4EquationOfMotion is the abstract base class for the right hand size of the equation of motion of a ...
G4MagIntegratorStepper(G4EquationOfMotion *Equation, G4int numIntegrationVariables, G4int numStateVariables=12, G4bool isFSAL=false)
void SetIsQSS(G4bool val)
G4bool SetQssOrder(G4int order)
static G4QSSMessenger * instance()
G4QSStepper(G4EquationOfMotion *equation, G4int num_integration_vars=6, G4int qssOrder=-1)
void update_x(G4int index, G4double t)
void initialize(const G4double y[])
void update_x_velocity_derivates_using_q(G4int index)
void velocity_to_momentum(G4double *y)
G4int get_next_sync_index()
void update_field()
void Interpolate(G4double tau, G4double yOut[])
void setRestMass(G4double restMass)
void extrapolate_all_states_to_t(Substep *substep, G4double t, G4double *yOut)
void update_q(G4int index, G4double t)
void momentum_to_velocity(const G4double *momentum, G4double *out)
constexpr int INDEX_TYPE(G4int i)
void update_x_derivates_using_q(G4int index)
void set_relativistic_coeff(const G4double *momentum)
void update_sync_time(G4int index)
void Stepper(const G4double y[], const G4double[], G4double h, G4double yout[], G4double[]) override