Garfield++ 5.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
TrackPAI.cc
Go to the documentation of this file.
1#include <algorithm>
2#include <cmath>
3#include <fstream>
4#include <iostream>
5
8#include "Garfield/Random.hh"
9#include "Garfield/Sensor.hh"
10#include "Garfield/TrackPAI.hh"
11
12namespace Garfield {
13
15
16bool TrackPAI::NewTrack(const double x0, const double y0, const double z0,
17 const double t0, const double dx0, const double dy0,
18 const double dz0) {
19 m_clusters.clear();
20 m_cluster = 0;
21 // Make sure the sensor has been set.
22 if (!m_sensor) {
23 std::cerr << m_className << "::NewTrack: Sensor is not defined.\n";
24 return false;
25 }
26
27 // Make sure there is an "ionisable" medium at this location.
28 Medium* medium = m_sensor->GetMedium(x0, y0, z0);
29 if (!medium || !medium->IsIonisable()) {
30 std::cerr << m_className << "::NewTrack:\n"
31 << " No ionisable medium at initial position.\n";
32 return false;
33 }
34
35 if (medium->GetName() != m_mediumName ||
36 medium->GetNumberDensity() != m_mediumDensity) {
37 m_isChanged = true;
38 if (!SetupMedium(medium)) {
39 std::cerr << m_className << "::NewTrack:\n Properties of medium "
40 << medium->GetName() << " are not available.\n";
41 return false;
42 }
43 m_mediumName = medium->GetName();
44 m_mediumDensity = medium->GetNumberDensity();
45 }
46
47 if (m_isChanged) {
48 if (!SetupCrossSectionTable()) {
49 std::cerr << m_className << "::NewTrack:\n"
50 << " Calculation of ionisation cross-section failed.\n";
51 return false;
52 }
53 m_isChanged = false;
54 }
55
56 double x = x0;
57 double y = y0;
58 double z = z0;
59 double t = t0;
60 double dx = dx0;
61 double dy = dy0;
62 double dz = dz0;
63 const double d = sqrt(dx * dx + dy * dy + dz * dz);
64 if (d < Small) {
65 // Null vector. Choose a random direction.
66 RndmDirection(dx, dy, dz);
67 } else {
68 // Normalize the direction vector.
69 const double scale = 1. / d;
70 dx *= scale;
71 dy *= scale;
72 dz *= scale;
73 }
74 double ekin = GetKineticEnergy();
75 while (ekin > 0.) {
76 // Draw a step length and propagate the particle.
77 const double step = -m_imfp * log(RndmUniformPos());
78 x += step * dx;
79 y += step * dy;
80 z += step * dz;
81 t += step / m_speed;
82
83 medium = m_sensor->GetMedium(x, y, z);
84 if (!medium || !medium->IsIonisable() ||
85 medium->GetName() != m_mediumName ||
86 medium->GetNumberDensity() != m_mediumDensity) {
87 break;
88 }
89
90 // Sample the energy deposit.
91 std::pair<double, double> edep = SampleEnergyDeposit(RndmUniform());
92 // Update the particle energy.
93 ekin -= edep.first;
94
95 Cluster cluster;
96 cluster.x = x;
97 cluster.y = y;
98 cluster.z = z;
99 cluster.t = t;
100 cluster.energy = edep.first;
101 m_clusters.push_back(std::move(cluster));
102 }
103 return true;
104}
105
106bool TrackPAI::GetCluster(double& xc, double& yc, double& zc,
107 double& tc, int& nc, double& ec, double& extra) {
108 nc = 0;
109 xc = yc = zc = tc = ec = extra = 0.;
110 if (m_clusters.empty() || m_cluster >= m_clusters.size()) return false;
111 const auto& cluster = m_clusters[m_cluster];
112 xc = cluster.x;
113 yc = cluster.y;
114 zc = cluster.z;
115 tc = cluster.t;
116 ec = cluster.energy;
117 nc = 1;
118
119 ++m_cluster;
120 return true;
121}
122
123std::pair<double, double> TrackPAI::SampleEnergyDeposit(const double u) const {
124 if (u > m_cdf.back()) {
125 // Use the free-electron differential cross-section.
126 return std::make_pair(SampleAsymptoticCs(u), 1.);
127 }
128
129 if (u <= m_cdf[0]) return std::make_pair(m_energies[0], 0.);
130 if (u >= 1.) return std::make_pair(m_energies.back(), 0.);
131
132 // Find the energy loss by interpolation
133 // from the cumulative distribution table.
134 const auto begin = m_cdf.cbegin();
135 const auto it1 = std::upper_bound(begin, m_cdf.cend(), u);
136 if (it1 == m_cdf.cbegin()) return std::make_pair(m_energies[0], 0.);
137 const auto it0 = std::prev(it1);
138 const double c0 = *it0;
139 const double c1 = *it1;
140 const double e0 = m_energies[it0 - begin];
141 const double e1 = m_energies[it1 - begin];
142 const double r0 = m_rutherford[it0 - begin];
143 const double r1 = m_rutherford[it1 - begin];
144 if (e0 < 100.) {
145 const double f1 = (u - c0) / (c1 - c0);
146 const double f0 = 1. - f1;
147 return std::make_pair(f0 * e0 + f1 * e1, f0 * r0 + f1 * r1);
148 }
149 const double loge0 = log(e0);
150 const double loge1 = log(e1);
151 const double logc0 = log(c0);
152 const double logc1 = log(c1);
153 const double f1 = (log(u) - logc0) / (logc1 - logc0);
154 const double f0 = 1. - f1;
155 const double edep = exp(f0 * loge0 + f1 * loge1);
156 return std::make_pair(edep, f0 * r0 + f1 * r1);
157}
158
159bool TrackPAI::SetupMedium(Medium* medium) {
160 // Make sure that the medium is defined.
161 if (!medium) {
162 std::cerr << m_className << "::SetupMedium: Null pointer.\n";
163 return false;
164 }
165
166 // Get the density and effective Z.
167 m_electronDensity = medium->GetNumberDensity() * medium->GetAtomicNumber();
168 if (m_electronDensity <= 0.) {
169 std::cerr << m_className << "::SetupMedium:\n"
170 << " Unphysical electron density (" << m_electronDensity
171 << ")\n";
172 return false;
173 }
174
175 // Get the dielectric function.
176 double emin, emax;
177 if (!medium->GetOpticalDataRange(emin, emax)) {
178 std::cerr << m_className << "::SetupMedium:\n";
179 std::cerr << " Could not load optical data for medium " << m_mediumName
180 << ".\n";
181 return false;
182 }
183
184 // Make sure the minimum energy is positive.
185 if (emin < Small) emin = Small;
186
187 // Reset the arrays.
188 m_energies.fill(0.);
189 m_eps1.fill(0.);
190 m_eps2.fill(0.);
191 m_epsInt.fill(0.);
192
193 // Use logarithmically spaced energy steps.
194 const double r = pow(emax / emin, 1. / double(m_nSteps));
195 double eC = 0.5 * emin * (1. + r);
196 for (size_t i = 0; i < m_nSteps; ++i) {
197 double eps1 = 0., eps2 = 0.;
198 medium->GetDielectricFunction(eC, eps1, eps2);
199 m_eps1[i] = eps1;
200 m_eps2[i] = eps2;
201 m_energies[i] = eC;
202 eC *= r;
203 }
204
205 // Compute the integral of loss function times energy.
206 m_epsInt[0] = 0.;
207 double integral = 0.;
208 double f1 = m_energies[0] * LossFunction(m_eps1[0], m_eps2[0]);
209 double f2 = f1;
210 for (size_t i = 1; i < m_nSteps; ++i) {
211 f2 = m_energies[i] * LossFunction(m_eps1[i], m_eps2[i]);
212 const double eM = 0.5 * (m_energies[i - 1] + m_energies[i]);
213 double eps1 = 0., eps2 = 0.;
214 medium->GetDielectricFunction(eM, eps1, eps2);
215 const double fM = eM * LossFunction(eps1, eps2);
216 // Simpson's rule
217 integral += (f1 + 4 * fM + f2) * (m_energies[i] - m_energies[i - 1]) / 6.;
218 m_epsInt[i] = integral;
219 f1 = f2;
220 }
221
222 // Check the consistency of the optical data by means of the TRK sum rule
223 const double trk = 2 * Pi2 * FineStructureConstant * pow(HbarC, 3) *
224 m_electronDensity / ElectronMass;
225 if (fabs(integral - trk) > 0.2 * trk) {
226 std::cerr << m_className << "::SetupMedium:\n"
227 << " Deviation from Thomas-Reiche-Kuhn sum rule by > 20%.\n"
228 << " Optical data are probably incomplete or erroneous!\n";
229 }
230 return true;
231}
232
234 if (m_isChanged) {
235 if (SetupCrossSectionTable()) {
236 m_isChanged = false;
237 } else {
238 std::cerr << m_className << "::GetClusterDensity:\n";
239 std::cerr << " Ionisation cross-section could not be calculated.\n";
240 return 0.;
241 }
242 }
243 return 1. / m_imfp;
244}
245
247
248 if (m_isChanged) {
249 if (SetupCrossSectionTable()) {
250 m_isChanged = false;
251 } else {
252 std::cerr << m_className << "::GetStoppingPower:\n";
253 std::cerr << " Ionisation cross-section could not be calculated.\n";
254 return 0.;
255 }
256 }
257 return m_dedx;
258}
259
260bool TrackPAI::SetupCrossSectionTable() {
261 // TODO!
262 /*
263 if (!m_ready) {
264 std::cerr << m_className << "::SetupCrossSectionTable:\n"
265 << " Medium not set up.\n";
266 return false;
267 }
268 */
269 const double c1 = 2. * Pi2 * FineStructureConstant * pow(HbarC, 3) *
270 m_electronDensity / ElectronMass;
271 const double c2 = m_q * m_q * FineStructureConstant / (m_beta2 * Pi * HbarC);
272
273 // Get the max. allowed energy transfer.
274 m_emax = ComputeMaxTransfer();
275
276 std::ofstream outfile;
277 if (m_debug) outfile.open("dcs.txt", std::ios::out);
278
279 // Compute the differential cross-section.
280 std::vector<double> dcs;
281 m_rutherford.fill(0.);
282 for (size_t i = 0; i < m_nSteps; ++i) {
283 // Define shorthand variables for photon energy and dielectric function.
284 const double egamma = m_energies[i];
285 const double eps1 = m_eps1[i];
286 const double eps2 = m_eps2[i];
287 const double integral = m_epsInt[i];
288
289 // First, calculate the distant-collision terms.
290 double dcsLog = 0., dcsDensity = 0., dcsCher = 0.;
291 if (eps2 > 0.) {
292 // Normal case (loss function > 0).
293 const double lf = LossFunction(eps1, eps2);
294 // Non-relativistic logarithmic term.
295 dcsLog = lf * log(2 * ElectronMass * m_beta2 / egamma);
296 // Relativistic logarithmic term (density effect)
297 const double u = 1. - m_beta2 * eps1;
298 const double v = m_beta2 * eps2;
299 dcsDensity = -0.5 * lf * log(u * u + v * v);
300 // "Cherenkov" term
301 dcsCher = (m_beta2 - eps1 / (eps1 * eps1 + eps2 * eps2)) *
302 (HalfPi - atan(u / v));
303 } else if (eps1 > 1. / m_beta2) {
304 // Imaginary part is zero, only the Cerenkov term contributes.
305 dcsCher = Pi * (m_beta2 - 1. / eps1);
306 }
307
308 // Calculate the close-collision term (quasi-free scattering)
309 double dcsRuth = 0.;
310 double f = 0.;
311 if (egamma > 0. && integral > 0.) {
312 dcsRuth = integral / (egamma * egamma);
313 f = dcsRuth / (dcsLog + dcsDensity + dcsCher);
314 }
315 m_rutherford[i] = f;
316 dcs.push_back(dcsLog + dcsDensity + dcsCher + dcsRuth);
317 // If requested, write the cross-section terms to file.
318 if (m_debug) {
319 outfile << egamma << " " << eps1 << " " << eps2 << " " << dcsLog * c2
320 << " " << dcsDensity * c2 << " " << dcsCher * c2 << " "
321 << dcsRuth * c2 << "\n";
322 }
323 }
324 if (m_debug) outfile.close();
325
326 // Compute the cumulative distribution,
327 // total cross-section and stopping power.
328 m_cdf.fill(0.);
329 m_dedx = 0.;
330 double cs = 0.;
331 for (size_t i = 1; i < m_nSteps; ++i) {
332 const double e0 = m_energies[i - 1];
333 const double e1 = m_energies[i];
334 const double de = e1 - e0;
335 const double dcs0 = dcs[i - 1];
336 const double dcs1 = dcs[i];
337 cs += 0.5 * (dcs0 + dcs1) * de;
338 m_cdf[i] = cs;
339 m_dedx += 0.5 * (dcs0 * e0 + dcs1 * e1) * de;
340 }
341
342 // Add the contribution of high energy transfers to the stopping power
343 // and the total cross-section
344 const double elim = m_energies.back();
345 if (elim < m_emax) {
346 cs += c1 * ComputeCsTail(elim, m_emax);
347 m_dedx += c1 * ComputeDeDxTail(elim, m_emax);
348 } else {
349 std::cerr << m_className << "::SetupCrossSectionTable:\n";
350 std::cerr << " Max. energy transfer lower than optical data range.\n";
351 }
352
353 if (cs <= 0.) {
354 std::cerr << m_className << "::SetupCrossSectionTable:\n";
355 std::cerr << " Total cross-section <= 0.\n";
356 return false;
357 }
358
359 // Normalise the cumulative distribution.
360 for (size_t i = 0; i < m_nSteps; ++i) m_cdf[i] /= cs;
361
362 cs *= c2;
363 m_dedx *= c2;
364
365 // Compute the inelastic mean free path
366 m_imfp = 1. / cs;
367
368 return true;
369}
370
371double TrackPAI::ComputeMaxTransfer() const {
372 if (m_isElectron) {
373 // Max. transfer for electrons is half the kinetic energy.
374 return 0.5 * (m_energy - m_mass);
375 }
376
377 // Other particles.
378 const double bg2 = m_beta2 / (1. - m_beta2);
379 const double mass2 = m_mass * m_mass;
380
381 return 2. * mass2 * ElectronMass * bg2 /
382 (mass2 + ElectronMass * ElectronMass + 2. * m_energy * ElectronMass);
383}
384
385double TrackPAI::ComputeCsTail(const double emin, const double emax) {
386 if (m_isElectron) {
387 // Electrons
388 const double ek = m_energy - m_mass;
389 return 1. / emin - 1. / emax - 2 * emin / (ek * ek) -
390 emin * emin / ((ek - emin) * ek * ek);
391 } else if (fabs(m_mass - ElectronMass) < 0.1) {
392 // Positrons
393 const double ek = m_energy - m_mass;
394 return 1. / emin - 1. / emax + 3 * (emax - emin) / (ek * ek) -
395 (emax - emin) * (ek * (emax + emin) +
396 (emin * emin + emin * emax + emax * emax) / 3.) /
397 pow(ek, 4) -
398 (2. / ek) * log(emax / emin);
399 }
400
401 switch (m_spin) {
402 case 0:
403 // Spin 0
404 return 1. / emin - 1. / emax - m_beta2 * log(emax / emin) / emax;
405 case 1:
406 // Spin 1/2
407 return 1. / emin - 1. / emax - m_beta2 * log(emax / emin) / emax +
408 (emax - emin) / (2 * m_energy * m_energy);
409 case 2: {
410 // Spin 1
411 const double e2 = 2 * m_energy * m_energy;
412 const double ec = m_mass * m_mass / ElectronMass;
413 const double a = 1. / (3 * ec);
414 const double b = (emax - emin);
415 return 1. / emin - 1. / emax + a * b * (emin + e2 + emax) / e2 -
416 m_beta2 * a * b / emax + (a - m_beta2 / emax) * log(emax / emin);
417 }
418 default:
419 break;
420 }
421 // Rutherford type cross-section
422 return 1. / emin - 1. / emax;
423}
424
425double TrackPAI::ComputeDeDxTail(const double emin, const double emax) {
426 if (m_isElectron) {
427 const double ek = m_energy - m_mass;
428 return -log(emin * (ek - emin) / (ek * ek)) +
429 (1. / (8 * (emin - ek) * ek * ek)) *
430 (-4 * pow(emin, 3) + 4 * emin * emin * ek +
431 emin * ek * ek * (17. - 16. * CLog2) +
432 pow(ek, 3) * (-9. + 16. * CLog2));
433 } else if (fabs(m_mass - ElectronMass) < 0.1) {
434 // Positron
435 const double ek = m_energy - m_mass;
436 return log(ek / emin) -
437 (ek - emin) * (ek - emin) *
438 (3. * emin * emin - 2. * emin * ek + 11. * ek * ek) /
439 (12. * pow(ek, 4));
440 }
441
442 switch (m_spin) {
443 case 0:
444 return log(emax / emin) - m_beta2 * (emax - emin) / emax;
445 case 1:
446 // Spin 1/2
447 return log(emax / emin) - m_beta2 * (emax - emin) / emax +
448 (emax * emax - emin * emin) / (2 * m_energy * m_energy);
449 case 2: {
450 // Spin 1
451 double e2 = m_energy * m_energy;
452 double ec = m_mass * m_mass / ElectronMass;
453 return log(emax / emin) + (pow(emax, 3) - pow(emin, 3)) / (9. * e2 * ec) +
454 (emax * emax - emin * emin) / (6. * e2) +
455 (emax - emin) *
456 (2. - (1. + emin / emax + 6 * ec / emax) * m_beta2) /
457 (6. * ec);
458 }
459 default:
460 break;
461 }
462
463 // Rutherford cross-section
464 return log(emax / emin);
465}
466
467double TrackPAI::SampleAsymptoticCs(double u) const {
468 const double emin = m_energies.back();
469 // Rescale the random number
470 u = (u - m_cdf.back()) / (1. - m_cdf.back());
471
472 if (m_isElectron) {
473 return SampleAsymptoticCsElectron(emin, u);
474 } else if (fabs(m_mass - ElectronMass) < 0.1) {
475 return SampleAsymptoticCsPositron(emin, u);
476 }
477
478 switch (m_spin) {
479 case 0:
480 // Spin 0
481 return SampleAsymptoticCsSpinZero(emin, u);
482 case 1:
483 // Spin 1/2
484 return SampleAsymptoticCsSpinHalf(emin, u);
485 case 2:
486 // Spin 1
487 return SampleAsymptoticCsSpinOne(emin, u);
488 default:
489 break;
490 }
491 // Rutherford cross-section (analytic inversion)
492 return emin * m_emax / (u * emin + (1. - u) * m_emax);
493}
494
495double TrackPAI::SampleAsymptoticCsSpinZero(const double emin, double u) const {
496 const double a = emin / m_emax;
497 const double b = m_beta2 * a;
498 u *= (1. - a + b * log(a));
499 double eLow = emin, eUp = m_emax;
500 while (eUp - eLow > 1.) {
501 const double eM = 0.5 * (eUp + eLow);
502 if (u >= 1. - emin / eM - b * log(eM / emin)) {
503 eLow = eM;
504 } else {
505 eUp = eM;
506 }
507 }
508
509 return 0.5 * (eLow + eUp);
510}
511
512double TrackPAI::SampleAsymptoticCsSpinHalf(const double emin, double u) const {
513 const double a = emin / m_emax;
514 const double b = m_beta2 * a;
515 const double c = emin / (2. * m_energy * m_energy);
516 u *= 1. - a + b * log(a) + (m_emax - emin) * c;
517 double eLow = emin, eUp = m_emax;
518 while (eUp - eLow > 1.) {
519 const double eM = 0.5 * (eUp + eLow);
520 if (u >= 1. - emin / eM - b * log(eM / emin) + (eM - emin) * c) {
521 eLow = eM;
522 } else {
523 eUp = eM;
524 }
525 }
526
527 return 0.5 * (eLow + eUp);
528}
529
530double TrackPAI::SampleAsymptoticCsSpinOne(const double emin, double u) const {
531 const double e2 = 2 * m_energy * m_energy;
532 const double ec = m_mass * m_mass / ElectronMass;
533 const double a = 2 * ec / e2 - m_beta2 / m_emax;
534 const double b = 1.5 * ec / emin;
535 const double c = 1. - 1.5 * ec * m_beta2 / m_emax;
536 u *= (m_emax - emin) * (0.5 * (emin + m_emax) / e2 + a + b / m_emax) +
537 c * log(m_emax / emin);
538 double eLow = emin, eUp = m_emax;
539 while (eUp - eLow > 1.) {
540 const double eM = 0.5 * (eUp + eLow);
541 if (u >=
542 (eM - emin) * ((emin + eM) / e2 + a + b / eM) + c * log(eM / emin)) {
543 eLow = eM;
544 } else {
545 eUp = eM;
546 }
547 }
548
549 return 0.5 * (eLow + eUp);
550}
551
552double TrackPAI::SampleAsymptoticCsElectron(const double emin, double u) const {
553 const double ek = m_energy - m_mass;
554 const double ek2 = ek * ek;
555 const double a = ek / (emin * (ek - emin));
556 const double norm = 1. / emin - 0.5 / ek - emin * emin / ((ek - emin) * ek2) -
557 2. * emin / ek2;
558 u *= norm;
559 double eLow = emin, eUp = m_emax;
560 while (eUp - eLow > 1.) {
561 const double eM = 0.5 * (eUp + eLow);
562 if (u >= a - 1. / eM + (eM - emin) / ek2 + 1. / (ek - eM)) {
563 eLow = eM;
564 } else {
565 eUp = eM;
566 }
567 }
568 return 0.5 * (eLow + eUp);
569}
570
571double TrackPAI::SampleAsymptoticCsPositron(const double emin, double u) const {
572 const double ek = m_energy - m_mass;
573 const double ek2 = ek * ek;
574 const double ek3 = ek2 * ek;
575 const double ek4 = 3 * ek3 * ek;
576 const double emin2 = emin * emin;
577 const double a = 1. / emin;
578 const double b = 3. / ek2;
579 const double c = 2. / ek;
580 u *= 1. / emin - 1. / m_emax + 3 * (m_emax - emin) / ek2 -
581 (m_emax - emin) * (m_emax + emin) / ek3 +
582 (m_emax - emin) * (emin * emin + emin * m_emax + m_emax * m_emax) / ek4 -
583 (2. / ek) * log(m_emax / emin);
584 double eLow = emin, eUp = m_emax;
585 while (eUp - eLow > 1.) {
586 const double eM = 0.5 * (eUp + eLow);
587 const double eM2 = eM * eM;
588 if (u >= a - 1. / eM + b * (eM - emin) - (eM2 - emin2) / ek3 +
589 (eM - emin) * (emin2 + emin * eM + eM2) / ek4 -
590 c * log(eM / emin)) {
591 eLow = eM;
592 } else {
593 eUp = eM;
594 }
595 }
596
597 return 0.5 * (eLow + eUp);
598}
599}
Abstract base class for media.
Definition Medium.hh:16
virtual double GetNumberDensity() const
Get the number density [cm-3].
Definition Medium.hh:63
const std::string & GetName() const
Get the medium name/identifier.
Definition Medium.hh:26
bool IsIonisable() const
Is charge deposition by charged particles/photon enabled in this medium?
Definition Medium.hh:81
bool NewTrack(const double x0, const double y0, const double z0, const double t0, const double dx0, const double dy0, const double dz0) override
Definition TrackPAI.cc:16
bool GetCluster(double &xc, double &yc, double &zc, double &tc, int &nc, double &ec, double &extra) override
Definition TrackPAI.cc:106
double GetClusterDensity() override
Definition TrackPAI.cc:233
double GetStoppingPower() override
Get the stopping power (mean energy loss [eV] per cm).
Definition TrackPAI.cc:246
double GetKineticEnergy() const
Return the kinetic energy of the projectile.
Definition Track.hh:62
Sensor * m_sensor
Definition Track.hh:114
bool m_isElectron
Definition Track.hh:111
bool m_isChanged
Definition Track.hh:116
double m_beta2
Definition Track.hh:110
std::string m_className
Definition Track.hh:104
double m_mass
Definition Track.hh:108
Track()=delete
Default constructor.
double m_energy
Definition Track.hh:109
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
Definition Random.hh:14
void RndmDirection(double &dx, double &dy, double &dz, const double length=1.)
Draw a random (isotropic) direction vector.
Definition Random.hh:128
double RndmUniformPos()
Draw a random number uniformly distributed in the range (0, 1).
Definition Random.hh:17
DoubleAc pow(const DoubleAc &f, double p)
Definition DoubleAc.cpp:337
DoubleAc exp(const DoubleAc &f)
Definition DoubleAc.cpp:377
DoubleAc fabs(const DoubleAc &f)
Definition DoubleAc.h:615