13std::string PrintVec(
const std::array<double, 3>& x) {
15 return "(" + std::to_string(x[0]) +
", " + std::to_string(x[1]) +
", " +
16 std::to_string(x[2]) +
")";
19double Mag(
const std::array<double, 3>& x) {
21 return sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
24double Mag(
const double x,
const double y,
const double z) {
26 return sqrt(x * x + y * y + z * z);
29double Mag(
const double x,
const double y) {
31 return sqrt(x * x + y * y);
34double Mag2(
const double x,
const double y) {
43using Vec = std::array<double, 3>;
52 std::cerr << m_className <<
"::SetSensor: Null pointer.\n";
62 std::cerr << m_className <<
"::SetIntegrationAccuracy:\n"
63 <<
" Accuracy must be greater than zero.\n";
70 m_useStepSizeLimit =
true;
72 std::cerr << m_className <<
"::SetMaximumStepSize:\n"
73 <<
" Step size must be greater than zero.\n";
79 std::cerr << m_className <<
"::EnablePlotting: Null pointer.\n";
90 std::cout << m_className <<
"::SetGainFluctuationsFixed: "
91 <<
"Avalanche size set to " << gain <<
".\n";
93 std::cout << m_className <<
"::SetGainFluctuationsFixed:\n "
94 <<
"Avalanche size will be given by "
95 <<
"the integrated Townsend coefficient.\n";
98 m_gainFluctuations = GainFluctuations::None;
105 std::cerr << m_className <<
"::SetGainFluctuationsPolya: "
106 <<
"Shape parameter must be >= 0.\n";
110 std::cout << m_className <<
"::SetGainFluctuationsPolya: "
111 <<
"Mean avalanche size set to " << mean <<
".\n";
113 std::cout << m_className <<
"::SetGainFluctuationsPolya:\n "
114 <<
"Mean avalanche size will be given by "
115 <<
"the integrated Townsend coefficient.\n";
119 m_gainFluctuations = GainFluctuations::Polya;
123 const double z0,
const double t0) {
124 m_particle = Particle::Electron;
125 if (!DriftLine(x0, y0, z0, t0, Particle::Electron, m_t, m_x, m_status)) {
128 std::vector<double> ne(m_t.size(), 1.);
129 std::vector<double> ni(m_t.size(), 0.);
131 if (m_doAvalanche) Avalanche(Particle::Electron, m_x, ne, ni, scale);
133 ComputeSignal(Particle::Electron, scale * m_scaleE, m_t, m_x, ne);
135 if (m_doAvalanche && m_doIonTail) AddIonTail(m_t, m_x, ni, scale);
139bool DriftLineRKF::AddIonTail(
const std::vector<double>& te,
140 const std::vector<std::array<double, 3> >& xe,
141 const std::vector<double>& ni,
142 const double scale) {
144 const unsigned int nPoints = te.size();
145 if (nPoints < 2)
return false;
146 if (ni.size() != nPoints)
return false;
148 for (
unsigned int i = 1; i < nPoints; ++i) {
150 if (scale * ni[i] < 1.)
continue;
154 const auto& x0 = xe[i];
155 std::vector<double> ti;
156 std::vector<std::array<double, 3> > xi;
158 if (!DriftLine(x0[0], x0[1], x0[2], te[i], Particle::Ion, ti, xi, stat)) {
159 std::cerr << m_className <<
"::AddIonTail:\n"
160 <<
" Unable to obtain an ion tail; tail not added.\n";
164 std::cout << m_className <<
"::AddIonTail: Origin = " << PrintVec(x0)
165 <<
", n = " << ti.size() <<
", status = " << stat <<
"\n";
168 ComputeSignal(Particle::Ion, scale * m_scaleI * ni[i], ti, xi, {});
174 const double z0,
const double t0) {
176 m_particle = Particle::Positron;
177 if (!DriftLine(x0, y0, z0, t0, Particle::Positron, m_t, m_x, m_status)) {
181 ComputeSignal(Particle::Positron, m_scaleE, m_t, m_x, {});
188 m_particle = Particle::Hole;
189 if (!DriftLine(x0, y0, z0, t0, Particle::Hole, m_t, m_x, m_status)) {
192 if (m_doSignal) ComputeSignal(Particle::Hole, m_scaleH, m_t, m_x, {});
198 m_particle = Particle::Ion;
199 if (!DriftLine(x0, y0, z0, t0, Particle::Ion, m_t, m_x, m_status)) {
202 if (m_doSignal) ComputeSignal(Particle::Ion, m_scaleI, m_t, m_x, {});
207 const double z0,
const double t0) {
209 m_particle = Particle::NegativeIon;
210 if (!DriftLine(x0, y0, z0, t0, Particle::NegativeIon, m_t, m_x, m_status)) {
214 ComputeSignal(Particle::NegativeIon, m_scaleI, m_t, m_x, {});
219bool DriftLineRKF::DriftLine(
const double xi,
const double yi,
const double zi,
220 const double ti,
const Particle particle,
221 std::vector<double>& ts,
222 std::vector<Vec>& xs,
int& flag) {
248 std::cerr << m_className <<
"::DriftLine: Sensor is not defined.\n";
249 flag = StatusCalculationAbandoned;
254 double xmin = 0., xmax = 0.;
255 double ymin = 0., ymax = 0.;
256 double zmin = 0., zmax = 0.;
257 bool bbox = m_sensor->
GetArea(xmin, ymin, zmin, xmax, ymax, zmax);
260 Vec x0 = {xi, yi, zi};
262 double ex = 0., ey = 0., ez = 0.;
263 double bx = 0., by = 0., bz = 0.;
264 Medium* medium =
nullptr;
265 if (GetField(x0, ex, ey, ez, bx, by, bz, medium) != 0) {
266 std::cerr << m_className <<
"::DriftLine:\n"
267 <<
" No valid field at initial position.\n";
268 flag = StatusLeftDriftMedium;
275 if (particle == Particle::Ion || particle == Particle::NegativeIon) {
277 }
else if (particle == Particle::Electron ||
278 particle == Particle::Positron) {
280 }
else if (particle == Particle::Hole) {
286 constexpr double c10 = 214. / 891.;
287 constexpr double c11 = 1. / 33.;
288 constexpr double c12 = 650. / 891.;
289 constexpr double c20 = 533. / 2106.;
290 constexpr double c22 = 800. / 1053.;
291 constexpr double c23 = -1. / 78.;
293 constexpr double b10 = 1. / 4.;
294 constexpr double b20 = -189. / 800.;
295 constexpr double b21 = 729. / 800.;
296 constexpr double b30 = 214. / 891.;
297 constexpr double b31 = 1. / 33.;
298 constexpr double b32 = 650. / 891.;
301 const double charge = particle == Particle::Electron ? -1 : 1;
304 Vec v0 = {0., 0., 0.};
305 if (!GetVelocity(ex, ey, ez, bx, by, bz, medium, particle, v0)) {
306 std::cerr << m_className <<
"::DriftLine:\n"
307 <<
" Cannot retrieve drift velocity.\n";
311 const double speed0 = Mag(v0);
312 if (speed0 < Small) {
313 std::cerr << m_className <<
"::DriftLine:\n"
314 <<
" Zero velocity at initial position.\n";
319 double h = m_accuracy / speed0;
332 for (
unsigned int i = 0; i < 3; ++i) {
333 x1[i] += h * b10 * v0[i];
337 if (!GetVelocity(x1, particle, v1, stat)) {
338 flag = StatusCalculationAbandoned;
340 }
else if (stat != 0) {
342 std::cout << m_className <<
"::DriftLine: Point 1 outside.\n";
344 if (!Terminate(x0, x1, particle, ts, xs)) {
345 flag = StatusCalculationAbandoned;
353 for (
unsigned int i = 0; i < 3; ++i) {
354 x2[i] += h * (b20 * v0[i] + b21 * v1[i]);
357 if (!GetVelocity(x2, particle, v2, stat)) {
358 flag = StatusCalculationAbandoned;
360 }
else if (stat != 0) {
362 std::cout << m_className <<
"::DriftLine: Point 2 outside.\n";
364 if (!Terminate(x0, x2, particle, ts, xs)) {
365 flag = StatusCalculationAbandoned;
373 for (
unsigned int i = 0; i < 3; ++i) {
374 x3[i] += h * (b30 * v0[i] + b31 * v1[i] + b32 * v2[i]);
377 if (!GetVelocity(x3, particle, v3, stat)) {
378 flag = StatusCalculationAbandoned;
380 }
else if (stat != 0) {
382 std::cout << m_className <<
"::DriftLine: Point 3 outside.\n";
384 if (!Terminate(x0, x3, particle, ts, xs)) {
385 flag = StatusCalculationAbandoned;
392 double xw = 0., yw = 0., zw = 0., rw = 0.;
394 x1[0], x1[1], x1[2], xw, yw, zw,
true, rw) ||
396 x2[0], x2[1], x2[2], xw, yw, zw,
true, rw) ||
398 x3[0], x3[1], x3[2], xw, yw, zw,
true, rw)) {
400 std::cout << m_className <<
"::DriftLine: Crossed wire.\n";
402 if (DriftToWire(xw, yw, rw, particle, ts, xs, stat)) {
404 }
else if (h > Small) {
408 std::cerr << m_className <<
"::DriftLine: Step size too small. Stop.\n";
409 flag = StatusCalculationAbandoned;
414 if (particle != Particle::Ion) {
415 if (m_sensor->
IsInTrapRadius(charge, x1[0], x1[1], x1[2], xw, yw, rw)) {
416 if (!DriftToWire(xw, yw, rw, particle, ts, xs, flag)) {
417 flag = StatusCalculationAbandoned;
421 if (m_sensor->
IsInTrapRadius(charge, x2[0], x2[1], x2[2], xw, yw, rw)) {
422 if (!DriftToWire(xw, yw, rw, particle, ts, xs, flag)) {
423 flag = StatusCalculationAbandoned;
427 if (m_sensor->
IsInTrapRadius(charge, x3[0], x3[1], x3[2], xw, yw, rw)) {
428 if (!DriftToWire(xw, yw, rw, particle, ts, xs, flag)) {
429 flag = StatusCalculationAbandoned;
435 Vec phi1 = {0., 0., 0.};
436 Vec phi2 = {0., 0., 0.};
437 for (
unsigned int i = 0; i < 3; ++i) {
438 phi1[i] = c10 * v0[i] + c11 * v1[i] + c12 * v2[i];
439 phi2[i] = c20 * v0[i] + c22 * v2[i] + c23 * v3[i];
442 const double phi1mag = Mag(phi1);
443 if (phi1mag < Small) {
444 std::cerr << m_className <<
"::DriftLine: Step has zero length. Stop.\n";
445 flag = StatusCalculationAbandoned;
447 }
else if (m_useStepSizeLimit && h * phi1mag > m_maxStepSize) {
449 std::cout << m_className <<
"::DriftLine: Step is considered too long. "
450 <<
"H is reduced.\n";
452 h = 0.5 * m_maxStepSize / phi1mag;
456 if (h *
fabs(phi1[0]) > 0.1 *
fabs(xmax - xmin) ||
457 h *
fabs(phi1[1]) > 0.1 *
fabs(ymax - ymin)) {
460 std::cout << m_className <<
"::DriftLine: Step is considered too long. "
461 <<
"H is divided by two.\n";
465 }
else if (m_rejectKinks && xs.size() > 1) {
466 const unsigned int np = xs.size();
467 const auto&
x = xs[np - 1];
468 const auto& xp = xs[np - 2];
469 if (phi1[0] * (x[0] - xp[0]) + phi1[1] * (x[1] - xp[1]) +
470 phi1[2] * (x[2] - xp[2]) < 0.) {
471 std::cerr << m_className <<
"::DriftLine: Bending angle > 90 degree.\n";
472 flag = StatusSharpKink;
476 if (m_debug) std::cout << m_className <<
"::DriftLine: Step size ok.\n";
478 for (
unsigned int i = 0; i < 3; ++i) x0[i] += h * phi1[i];
481 m_sensor->
ElectricField(x0[0], x0[1], x0[2], ex, ey, ez, medium, stat);
486 std::cout << m_className <<
"::DriftLine: Point outside. Terminate.\n";
488 if (!Terminate(xs.back(), x0, particle, ts, xs)) {
489 flag = StatusCalculationAbandoned;
498 const double dphi =
fabs(phi1[0] - phi2[0]) +
fabs(phi1[1] - phi2[1]) +
499 fabs(phi1[2] - phi2[2]);
501 h =
sqrt(h * m_accuracy / dphi);
503 std::cout << m_className <<
"::DriftLine: Adapting H to " << h <<
".\n";
508 std::cout << m_className <<
"::DriftLine: H increased by factor two.\n";
513 std::cerr << m_className <<
"::DriftLine: Step size is zero. Stop.\n";
514 flag = StatusCalculationAbandoned;
518 if (initCycle > 0 && h < 0.2 * hprev) {
520 std::cout << m_className <<
"::DriftLine: Reinitialise step size.\n";
531 if (h > 10 * hprev) {
534 std::cout << m_className <<
"::DriftLine: H restricted to 10 times "
535 <<
"the previous value.\n";
539 if (h * (
fabs(phi1[0]) +
fabs(phi1[1]) +
fabs(phi1[2])) < m_accuracy) {
540 std::cerr << m_className <<
"::DriftLine: Step size has become smaller "
541 <<
"than int. accuracy. Stop.\n";
542 flag = StatusCalculationAbandoned;
549 for (
const auto& x : m_x) {
553 if (flag == StatusCalculationAbandoned)
return false;
557bool DriftLineRKF::Avalanche(
const Particle particle,
558 const std::vector<Vec>& xs,
559 std::vector<double>& ne,
560 std::vector<double>& ni,
double& scale) {
563 const unsigned int nPoints = xs.size();
564 if (nPoints < 2)
return true;
566 constexpr double tg[6] = {-0.932469514203152028, -0.661209386466264514,
567 -0.238619186083196909, 0.238619186083196909,
568 0.661209386466264514, 0.932469514203152028};
569 constexpr double wg[6] = {0.171324492379170345, 0.360761573048138608,
570 0.467913934572691047, 0.467913934572691047,
571 0.360761573048138608, 0.171324492379170345};
573 ne.assign(nPoints, 1.);
574 ni.assign(nPoints, 0.);
576 bool overflow =
false;
578 for (
unsigned int i = 1; i < nPoints; ++i) {
579 const auto& xp = xs[i - 1];
580 const auto&
x = xs[i];
581 const Vec dx = {
x[0] - xp[0],
x[1] - xp[1],
x[2] - xp[2]};
585 for (
unsigned int j = 0; j < 6; ++j) {
586 const double f = 0.5 * (1. + tg[j]);
588 for (
unsigned int k = 0; k < 3; ++k) xj[k] += f * dx[k];
589 double ex = 0., ey = 0., ez = 0.;
590 double bx = 0., by = 0., bz = 0.;
591 Medium* medium =
nullptr;
592 if (GetField(xj, ex, ey, ez, bx, by, bz, medium) != 0) {
593 std::cerr << m_className <<
"::Avalanche:\n Invalid field at "
594 <<
"drift line point " << i <<
", segment " << j <<
".\n";
598 if (!GetAlpha(ex, ey, ez, bx, by, bz, medium, particle, alp)) {
599 std::cerr << m_className <<
"::Avalanche:\n Cannot retrieve alpha at "
600 <<
"drift line point " << i <<
", segment " << j <<
".\n";
604 if (!GetEta(ex, ey, ez, bx, by, bz, medium, particle, eta)) {
605 std::cerr << m_className <<
"::Avalanche:\n Cannot retrieve eta at "
606 <<
"drift line point " << i <<
", segment " << j <<
".\n";
609 alpsum += wg[j] * alp;
610 etasum += wg[j] * eta;
614 if (alpsum > 1.e-6 && !start) {
616 std::cout << m_className <<
"::Avalanche: Avalanche starts at step "
621 const double d = Mag(dx);
623 constexpr double expmax = 30.;
624 const double logp = log(std::max(1., ne[i - 1]));
625 if (logp + d * (alpsum - etasum) > expmax) {
629 ne[i] = ne[i - 1] *
exp(d * (alpsum - etasum));
631 if (logp + d * alpsum > expmax) {
635 ni[i] = ne[i - 1] * (
exp(d * alpsum) - 1);
639 std::cerr << m_className <<
"::Avalanche:\n "
640 <<
"Warning: Integrating the Townsend coefficients "
641 <<
"would lead to exponential overflow.\n "
642 <<
"Avalanche truncated.\n";
644 const double qe = ne.back();
645 const double qi = std::accumulate(ni.begin(), ni.end(), 0.);
648 !(m_gainFluctuations == GainFluctuations::None && m_gain < 1.)) {
649 const double gain = m_gain > 1. ? m_gain :
GetGain();
651 if (m_gainFluctuations == GainFluctuations::Polya) {
652 for (
unsigned int i = 0; i < 100; ++i) {
656 q1 = std::max(q1, 1.);
659 scale = (q1 + 1.) / (qi + 1.);
662 std::cout << m_className <<
"::Avalanche:\n "
663 <<
"Final number of electrons: " << qe <<
"\n "
664 <<
"Number of ions: " << qi <<
"\n "
665 <<
"Charge scaling factor: " << scale <<
"\n "
666 <<
"Avalanche development:\n Step Electrons Ions\n";
667 for (
unsigned int i = 0; i < nPoints; ++i) {
668 std::printf(
"%6d %15.7f %15.7f\n", i, scale * ne[i], scale * ni[i]);
684 const unsigned int nPoints = m_x.size();
686 if (nPoints < 2)
return 0.;
687 const Particle particle = m_particle;
692 for (
unsigned int i = 0; i < nPoints; ++i) {
694 double ex = 0., ey = 0., ez = 0.;
695 double bx = 0., by = 0., bz = 0.;
697 if (GetField(m_x[i], ex, ey, ez, bx, by, bz, medium) != 0) {
698 std::cerr << m_className <<
"::GetArrivalTimeSpread:\n"
699 <<
" Invalid drift line point " << i <<
".\n";
703 if (!GetVelocity(ex, ey, ez, bx, by, bz, medium, particle, v)) {
704 std::cerr << m_className <<
"::GetArrivalTimeSpread:\n"
705 <<
" Cannot retrieve drift velocity at point " << i <<
".\n";
708 const double speed = Mag(v);
710 std::cerr << m_className <<
"::GetArrivalTimeSpread:\n"
711 <<
" Zero drift velocity at point " << i <<
".\n";
714 double dl = 0., dt = 0.;
715 if (!GetDiffusion(ex, ey, ez, bx, by, bz, medium, particle, dl, dt)) {
716 std::cerr << m_className <<
"::GetArrivalTimeSpread:\n"
717 <<
" Cannot retrieve diffusion at point " << i <<
".\n";
720 const double sigma = dl / speed;
721 const double var = sigma * sigma;
723 const auto& x = m_x[i];
724 const auto& xPrev = m_x[i - 1];
725 const double d = Mag(x[0] - xPrev[0], x[1] - xPrev[1], x[2] - xPrev[2]);
726 crude += 0.5 * d * (var + varPrev);
732 const double tol = eps * crude;
734 for (
unsigned int i = 0; i < nPoints - 1; ++i) {
735 sum += IntegrateDiffusion(m_x[i], m_x[i + 1], particle, tol);
748 const unsigned int nPoints = m_x.size();
750 if (nPoints < 2)
return 1.;
751 const Particle particle = m_particle;
752 if (particle == Particle::Ion)
return 1.;
753 if (m_status == StatusCalculationAbandoned)
return 1.;
757 double alphaPrev = 0.;
758 for (
unsigned int i = 0; i < nPoints; ++i) {
760 double ex = 0., ey = 0., ez = 0.;
761 double bx = 0., by = 0., bz = 0.;
763 if (GetField(m_x[i], ex, ey, ez, bx, by, bz, medium) != 0) {
764 std::cerr << m_className <<
"::GetGain:\n"
765 <<
" Invalid drift line point " << i <<
".\n";
769 if (!GetAlpha(ex, ey, ez, bx, by, bz, medium, particle, alpha)) {
770 std::cerr << m_className <<
"::GetGain:\n"
771 <<
" Cannot retrieve alpha at point " << i <<
".\n";
775 const auto& x = m_x[i];
776 const auto& xPrev = m_x[i - 1];
777 const double d = Mag(x[0] - xPrev[0], x[1] - xPrev[1], x[2] - xPrev[2]);
778 crude += 0.5 * d * (alpha + alphaPrev);
783 if (crude < Small)
return 1.;
786 const double tol = eps * crude;
788 for (
unsigned int i = 0; i < nPoints - 1; ++i) {
789 sum += IntegrateAlpha(m_x[i], m_x[i + 1], particle, tol);
802 const unsigned int nPoints = m_x.size();
804 if (nPoints < 2)
return 1.;
805 const Particle particle = m_particle;
806 if (particle == Particle::Ion)
return 1.;
807 if (m_status == StatusCalculationAbandoned)
return 1.;
812 for (
unsigned int i = 0; i < nPoints; ++i) {
814 double ex = 0., ey = 0., ez = 0.;
815 double bx = 0., by = 0., bz = 0.;
817 if (GetField(m_x[i], ex, ey, ez, bx, by, bz, medium) != 0) {
818 std::cerr << m_className <<
"::GetLoss:\n"
819 <<
" Invalid drift line point " << i <<
".\n";
823 if (!GetEta(ex, ey, ez, bx, by, bz, medium, particle, eta)) {
824 std::cerr << m_className <<
"::GetLoss:\n"
825 <<
" Cannot retrieve eta at point " << i <<
".\n";
829 const auto& x = m_x[i];
830 const auto& xPrev = m_x[i - 1];
831 const double d = Mag(x[0] - xPrev[0], x[1] - xPrev[1], x[2] - xPrev[2]);
832 crude += 0.5 * d * (eta + etaPrev);
838 const double tol = eps * crude;
840 for (
unsigned int i = 0; i < nPoints - 1; ++i) {
841 sum += IntegrateEta(m_x[i], m_x[i + 1], particle, tol);
846int DriftLineRKF::GetField(
const std::array<double, 3>& x,
847 double& ex,
double& ey,
double& ez,
848 double& bx,
double& by,
double& bz,
851 m_sensor->
MagneticField(x[0], x[1], x[2], bx, by, bz, status);
852 m_sensor->
ElectricField(x[0], x[1], x[2], ex, ey, ez, medium, status);
856bool DriftLineRKF::GetVelocity(
const std::array<double, 3>& x,
857 const Particle particle,
858 std::array<double, 3>& v,
int& status)
const {
860 double ex = 0., ey = 0., ez = 0.;
861 double bx = 0., by = 0., bz = 0.;
862 Medium* medium =
nullptr;
864 status = GetField(x, ex, ey, ez, bx, by, bz, medium);
865 if (status != 0)
return true;
866 if (particle == Particle::Electron) {
867 return medium->ElectronVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
868 }
else if (particle == Particle::Ion) {
869 return medium->IonVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
870 }
else if (particle == Particle::Hole) {
871 return medium->HoleVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
872 }
else if (particle == Particle::Positron) {
873 const bool ok = medium->ElectronVelocity(ex, ey, ez, bx, by, bz,
875 for (
unsigned int i = 0; i < 3; ++i) v[i] *= -1;
877 }
else if (particle == Particle::NegativeIon) {
878 const bool ok = medium->IonVelocity(ex, ey, ez, bx, by, bz,
880 for (
unsigned int i = 0; i < 3; ++i) v[i] *= -1;
883 std::cerr << m_className <<
"::GetVelocity:\n"
884 <<
" Cannot retrieve drift velocity at " << PrintVec(x) <<
".\n";
888bool DriftLineRKF::GetVelocity(
const double ex,
const double ey,
889 const double ez,
const double bx,
890 const double by,
const double bz,
891 Medium* medium,
const Particle particle,
892 std::array<double, 3>& v)
const {
894 if (particle == Particle::Electron) {
895 return medium->ElectronVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
896 }
else if (particle == Particle::Ion) {
897 return medium->IonVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
898 }
else if (particle == Particle::Hole) {
899 return medium->HoleVelocity(ex, ey, ez, bx, by, bz, v[0], v[1], v[2]);
900 }
else if (particle == Particle::Positron) {
901 const bool ok = medium->ElectronVelocity(ex, ey, ez, bx, by, bz,
903 for (
unsigned int i = 0; i < 3; ++i) v[i] *= -1;
905 }
else if (particle == Particle::NegativeIon) {
906 const bool ok = medium->IonVelocity(ex, ey, ez, bx, by, bz,
908 for (
unsigned int i = 0; i < 3; ++i) v[i] *= -1;
914bool DriftLineRKF::GetDiffusion(
const double ex,
const double ey,
915 const double ez,
const double bx,
916 const double by,
const double bz,
917 Medium* medium,
const Particle particle,
918 double& dl,
double& dt)
const {
919 if (particle == Particle::Electron || particle == Particle::Positron) {
920 return medium->ElectronDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
921 }
else if (particle == Particle::Ion || particle == Particle::NegativeIon) {
922 return medium->IonDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
923 }
else if (particle == Particle::Hole) {
924 return medium->HoleDiffusion(ex, ey, ez, bx, by, bz, dl, dt);
929bool DriftLineRKF::GetAlpha(
const double ex,
const double ey,
const double ez,
930 const double bx,
const double by,
const double bz,
931 Medium* medium,
const Particle particle,
932 double& alpha)
const {
933 if (particle == Particle::Electron || particle == Particle::Positron) {
934 return medium->ElectronTownsend(ex, ey, ez, bx, by, bz, alpha);
935 }
else if (particle == Particle::Hole) {
936 return medium->HoleTownsend(ex, ey, ez, bx, by, bz, alpha);
941bool DriftLineRKF::GetEta(
const double ex,
const double ey,
const double ez,
942 const double bx,
const double by,
const double bz,
943 Medium* medium,
const Particle particle,
945 if (particle == Particle::Electron) {
946 return medium->ElectronAttachment(ex, ey, ez, bx, by, bz, eta);
947 }
else if (particle == Particle::Hole) {
948 return medium->HoleAttachment(ex, ey, ez, bx, by, bz, eta);
953bool DriftLineRKF::Terminate(
const std::array<double, 3>& xx0,
954 const std::array<double, 3>& xx1,
955 const Particle particle,
956 std::vector<double>& ts,
957 std::vector<Vec>& xs) {
967 Vec vv0 = {0., 0., 0.};
969 if (!GetVelocity(xx0, particle, vv0, status)) {
970 std::cerr << m_className <<
"::Terminate:\n"
971 <<
" Cannot retrieve initial drift velocity.\n";
973 }
else if (status != 0) {
974 std::cerr << m_className <<
"::Terminate:\n"
975 <<
" No valid field at initial point. Program bug!\n";
978 double speed = Mag(vv0);
980 std::cerr << m_className <<
"::Terminate:\n"
981 <<
" Zero velocity at initial position.\n";
990 const unsigned int nBisections = 20;
991 for (
unsigned int i = 0; i < nBisections; ++i) {
994 for (
unsigned int j = 0; j < 3; ++j) {
995 if (
fabs(x1[j] - x0[j]) > 1.e-6 * (
fabs(x0[j]) +
fabs(x1[j]))) {
1002 std::cout << m_className <<
"::Terminate:\n"
1003 <<
" Bisection ended at cycle " << i <<
".\n";
1009 for (
unsigned int j = 0; j < 3; ++j) xm[j] = 0.5 * (x0[j] + x1[j]);
1010 double ex = 0., ey = 0., ez = 0.;
1011 Medium* medium =
nullptr;
1012 m_sensor->
ElectricField(xm[0], xm[1], xm[2], ex, ey, ez, medium, status);
1022 if (!GetVelocity(x0, particle, v0, status) || status != 0) {
1023 std::cerr << m_className <<
"::Terminate:\n"
1024 <<
" Warning: Unable to compute mean velocity at last step.\n";
1026 speed = 0.5 * (speed + Mag(v0));
1029 const double dt = Mag(x0[0] - xx0[0], x0[1] - xx0[1], x0[2] - xx0[2]) / speed;
1031 ts.push_back(ts.back() + dt);
1033 m_status = StatusLeftDriftMedium;
1037bool DriftLineRKF::DriftToWire(
const double xw,
const double yw,
1038 const double rw,
const Particle particle,
1039 std::vector<double>& ts,
1040 std::vector<Vec>& xs,
int& stat) {
1051 double t0 = ts.back() - ts.front();
1053 std::cout << m_className <<
"::DriftToWire:\n Drifting from ("
1054 << x0[0] <<
", " << x0[1] <<
") to wire at ("
1055 << xw <<
", " << yw <<
") with radius " << rw <<
" cm.\n";
1061 if (!GetVelocity(x0, particle, v0, status)) {
1062 std::cerr << m_className <<
"::DriftToWire:\n"
1063 <<
" Cannot retrieve initial drift velocity.\n";
1065 }
else if (status != 0) {
1066 std::cerr << m_className <<
"::DriftToWire:\n"
1067 <<
" No valid field at initial point. Program bug!\n";
1073 double dt = (Mag(xw - x0[0], yw - x0[1]) - rw) / Mag(v0[0], v0[1]);
1075 std::cout << m_className <<
"::DriftToWire: "
1076 <<
"Estimated time needed to reach the wire: " << dt <<
" ns.\n";
1079 constexpr unsigned int nMaxSplit = 10;
1080 unsigned int nSplit = 0;
1082 bool onwire =
false;
1083 const double r2 = rw * rw;
1084 while (!onwire && dt > 1.e-6 * t0) {
1087 for (
unsigned int j = 0; j < 3; ++j) x1[j] += dt * v0[j];
1089 const double xinp0 = (x1[0] - x0[0]) * (xw - x0[0]) +
1090 (x1[1] - x0[1]) * (yw - x0[1]);
1093 std::cerr << m_className <<
"::DriftToWire:\n"
1094 <<
" Particle moves away from the wire. Quit.\n";
1099 const double xinp1 = (x0[0] - x1[0]) * (xw - x1[0]) +
1100 (x0[1] - x1[1]) * (yw - x1[1]);
1102 if (Mag2(xw - x1[0], yw - x1[1]) <= r2) {
1104 if (m_debug) std::cout << m_className <<
"::DriftToWire: Inside.\n";
1107 if (m_debug) std::cout << m_className <<
"::DriftToWire: Wire crossed.\n";
1111 const double dw0 = Mag(xw - x0[0], yw - x0[1]);
1112 x1[0] = xw - (rw + BoundaryDistance) * (xw - x0[0]) / dw0;
1113 x1[1] = yw - (rw + BoundaryDistance) * (yw - x0[1]) / dw0;
1114 dt = Mag(x1[0] - x0[0], x1[1] - x0[1]) / Mag(v0[0], v0[1]);
1115 x1[2] = x0[2] + dt * v0[2];
1119 if (!GetVelocity(x1, particle, v1, status)) {
1120 std::cerr << m_className <<
"::DriftToWire:\n"
1121 <<
" Cannot retrieve drift velocity at end point. Quit.\n";
1123 }
else if (status != 0) {
1124 std::cerr << m_className <<
"::DriftToWire:\n"
1125 <<
" End point is not in a valid drift medium. Quit.\n";
1130 for (
unsigned int j = 0; j < 3; ++j) xm[j] = 0.5 * (x0[j] + x1[j]);
1133 if (!GetVelocity(xm, particle, vm, status)) {
1134 std::cerr << m_className <<
"::DriftToWire:\n"
1135 <<
" Cannot retrieve drift velocity at mid point. Quit.\n";
1137 }
else if (status != 0) {
1138 std::cerr << m_className <<
"::DriftToWire:\n"
1139 <<
" Mid point is not in a valid drift medium. Quit.\n";
1143 const double speed0 = Mag(v0[0], v0[1]);
1144 const double speed1 = Mag(v1[0], v1[1]);
1145 const double speedm = Mag(vm[0], vm[1]);
1146 if (speed0 < Small || speed1 < Small || speedm < Small) {
1147 std::cerr << m_className <<
"::DriftToWire: Zero velocity. Stop.\n";
1150 const double p0 = 1. / speed0;
1151 const double p1 = 1. / speed1;
1152 const double pm = 1. / speedm;
1154 const double d = Mag(x0[0] - x1[0], x0[1] - x1[1]);
1155 const double tol = 1.e-4 * (1. +
fabs(t0));
1156 if (d *
fabs(p0 - 2 * pm + p1) / 3. > tol && nSplit < nMaxSplit) {
1159 std::cout << m_className <<
"::DriftToWire: Reducing step size.\n";
1166 const double t1 = t0 + d * (p0 + 4 * pm + p1) / 6.;
1168 ts.push_back(ts[0] + t1);
1176 double ex = 0., ey = 0., ez = 0.;
1177 Medium* medium =
nullptr;
1178 m_sensor->
ElectricField(xw, yw, 0., ex, ey, ez, medium, stat);
1184 std::cout << m_className <<
"::PrintDriftLine:\n";
1186 std::cout <<
" No drift line present.\n";
1189 if (m_particle == Particle::Electron) {
1190 std::cout <<
" Particle: electron\n";
1191 }
else if (m_particle == Particle::Ion) {
1192 std::cout <<
" Particle: ion\n";
1193 }
else if (m_particle == Particle::Hole) {
1194 std::cout <<
" Particle: hole\n";
1196 std::cout <<
" Particle: unknown\n";
1198 std::cout <<
" Status: " << m_status <<
"\n"
1199 <<
" Step time [ns] "
1200 <<
"x [cm] y [cm] z [cm]\n";
1201 const unsigned int nPoints = m_x.size();
1202 for (
unsigned int i = 0; i < nPoints; ++i) {
1203 std::printf(
"%6d %15.7f %15.7f %15.7f %15.7f\n",
1204 i, m_t[i], m_x[i][0], m_x[i][1], m_x[i][2]);
1216 const auto& p = m_x.back();
1225 double& z,
double& t)
const {
1226 if (i >= m_x.size()) {
1227 std::cerr << m_className <<
"::GetDriftLinePoint: Index out of range.\n";
1231 const auto& p = m_x[i];
1238double DriftLineRKF::IntegrateDiffusion(
const std::array<double, 3>& xi,
1239 const std::array<double, 3>& xe,
1240 const Particle particle,
1243 double ex = 0., ey = 0., ez = 0.;
1244 double bx = 0., by = 0., bz = 0.;
1245 Medium* medium =
nullptr;
1249 if (GetField(x0, ex, ey, ez, bx, by, bz, medium) != 0) {
1250 std::cerr << m_className <<
"::IntegrateDiffusion: Invalid starting point "
1251 << PrintVec(xi) <<
".\n";
1257 if (!GetVelocity(ex, ey, ez, bx, by, bz, medium, particle, v0)) {
1258 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1259 <<
" Cannot retrieve drift velocity at initial point.\n";
1262 double speed0 = Mag(v0);
1263 if (speed0 < Small) {
1264 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1265 <<
" Zero velocity at starting point.\n";
1269 double dl0 = 0., dt0 = 0.;
1270 if (!GetDiffusion(ex, ey, ez, bx, by, bz, medium, particle, dl0, dt0)) {
1271 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1272 <<
" Cannot retrieve diffusion at initial point.\n";
1275 const double sigma0 = dl0 / speed0;
1276 double var0 = sigma0 * sigma0;
1280 if (GetField(x1, ex, ey, ez, bx, by, bz, medium) != 0) {
1281 std::cerr << m_className <<
"::IntegrateDiffusion: Invalid end point "
1282 << PrintVec(xe) <<
".\n";
1287 if (!GetVelocity(ex, ey, ez, bx, by, bz, medium, particle, v1)) {
1288 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1289 <<
" Cannot retrieve drift velocity at end point.\n";
1292 double speed1 = Mag(v1);
1293 if (speed1 < Small) {
1294 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1295 <<
" Zero velocity at end point.\n";
1299 double dl1 = 0., dt1 = 0.;
1300 if (!GetDiffusion(ex, ey, ez, bx, by, bz, medium, particle, dl1, dt1)) {
1301 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1302 <<
" Cannot retrieve diffusion at initial point.\n";
1305 double sigma1 = dl1 / speed1;
1306 double var1 = sigma1 * sigma1;
1308 double integral = 0.;
1312 if (Mag(xe[0] - x0[0], xe[1] - x0[1], xe[2] - x0[2]) < 1.e-6) {
1316 const double d = Mag(x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]);
1320 std::cout << m_className <<
"::IntegrateDiffusion: Small step.\n";
1322 integral += var0 * d;
1329 if (GetField(x1, ex, ey, ez, bx, by, bz, medium) != 0) {
1330 std::cerr << m_className <<
"::IntegrateDiffusion: Invalid end point.\n";
1333 if (!GetVelocity(ex, ey, ez, bx, by, bz, medium, particle, v1)) {
1334 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1335 <<
" Cannot retrieve drift velocity at end point.\n";
1339 if (speed1 < Small) {
1340 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1341 <<
" Zero drift velocity at end point.\n";
1344 if (!GetDiffusion(ex, ey, ez, bx, by, bz, medium, particle, dl1, dt1)) {
1345 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1346 <<
" Cannot retrieve diffusion at end point.\n";
1351 for (
unsigned int i = 0; i < 3; ++i) xm[i] = 0.5 * (x0[i] + x1[i]);
1352 if (GetField(xm, ex, ey, ez, bx, by, bz, medium) != 0) {
1353 std::cerr << m_className <<
"::IntegrateDiffusion: Invalid mid point.\n";
1357 if (!GetVelocity(ex, ey, ez, bx, by, bz, medium, particle, vm)) {
1358 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1359 <<
" Cannot retrieve drift velocity at mid point.\n";
1362 const double speedm = Mag(vm);
1363 if (speedm < Small) {
1364 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1365 <<
" Zero drift velocity at mid point.\n";
1368 double dlm = 0., dtm = 0.;
1369 if (!GetDiffusion(ex, ey, ez, bx, by, bz, medium, particle, dlm, dtm)) {
1370 std::cerr << m_className <<
"::IntegrateDiffusion:\n"
1371 <<
" Cannot retrieve diffusion at mid point.\n";
1374 sigma1 = dl1 / speed1;
1375 var1 = sigma1 * sigma1;
1376 const double sigmam = dlm / speedm;
1377 const double varm = sigmam * sigmam;
1380 if (
fabs(var0 - 2 * varm + var1) *
sqrt(d * 2 / (var0 + var1)) / 6. < tol) {
1382 integral += d * (var0 + 4 * varm + var1) / 6.;
1396double DriftLineRKF::IntegrateAlpha(
const std::array<double, 3>& xi,
1397 const std::array<double, 3>& xe,
1398 const Particle particle,
const double tol) {
1400 double ex = 0., ey = 0., ez = 0.;
1401 double bx = 0., by = 0., bz = 0.;
1402 Medium* medium =
nullptr;
1406 if (GetField(x0, ex, ey, ez, bx, by, bz, medium) != 0) {
1407 std::cerr << m_className <<
"::IntegrateAlpha: Invalid starting point "
1408 << PrintVec(xi) <<
".\n";
1413 if (!GetAlpha(ex, ey, ez, bx, by, bz, medium, particle, alpha0)) {
1414 std::cerr << m_className <<
"::IntegrateAlpha:\n"
1415 <<
" Cannot retrieve Townsend coefficient at initial point.\n";
1420 if (GetField(x1, ex, ey, ez, bx, by, bz, medium) != 0) {
1421 std::cerr << m_className <<
"::IntegrateAlpha: Invalid end point "
1422 << PrintVec(xe) <<
".\n";
1427 if (!GetAlpha(ex, ey, ez, bx, by, bz, medium, particle, alpha1)) {
1428 std::cerr << m_className <<
"::IntegrateAlpha:\n"
1429 <<
" Cannot retrieve Townsend coefficient at end point.\n";
1432 double integral = 0.;
1436 if (Mag(xe[0] - x0[0], xe[1] - x0[1], xe[2] - x0[2]) < 1.e-6) {
1440 const double d = Mag(x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]);
1444 std::cout << m_className <<
"::IntegrateAlpha: Small step.\n";
1446 integral += alpha0 * d;
1453 if (GetField(x1, ex, ey, ez, bx, by, bz, medium) != 0) {
1454 std::cerr << m_className <<
"::IntegrateAlpha: Invalid end point.\n";
1457 if (!GetAlpha(ex, ey, ez, bx, by, bz, medium, particle, alpha1)) {
1458 std::cerr << m_className <<
"::IntegrateAlpha:\n"
1459 <<
" Cannot retrieve Townsend coefficient at end point.\n";
1464 for (
unsigned int i = 0; i < 3; ++i) xm[i] = 0.5 * (x0[i] + x1[i]);
1465 if (GetField(xm, ex, ey, ez, bx, by, bz, medium) != 0) {
1466 std::cerr << m_className <<
"::IntegrateAlpha: Invalid mid point.\n";
1470 if (!GetAlpha(ex, ey, ez, bx, by, bz, medium, particle, alpham)) {
1471 std::cerr << m_className <<
"::IntegrateAlpha:\n"
1472 <<
" Cannot retrieve Townsend coefficient at mid point.\n";
1476 if (d *
fabs(alpha0 - 2 * alpham + alpha1) / 3. < tol) {
1478 integral += d * (alpha0 + 4 * alpham + alpha1) / 6.;
1492double DriftLineRKF::IntegrateEta(
const std::array<double, 3>& xi,
1493 const std::array<double, 3>& xe,
1494 const Particle particle,
const double tol) {
1496 double ex = 0., ey = 0., ez = 0.;
1497 double bx = 0., by = 0., bz = 0.;
1498 Medium* medium =
nullptr;
1502 if (GetField(x0, ex, ey, ez, bx, by, bz, medium) != 0) {
1503 std::cerr << m_className <<
"::IntegrateEta: Invalid starting point "
1504 << PrintVec(xi) <<
".\n";
1509 if (!GetEta(ex, ey, ez, bx, by, bz, medium, particle, eta0)) {
1510 std::cerr << m_className <<
"::IntegrateEta:\n"
1511 <<
" Cannot retrieve att. coefficient at initial point.\n";
1516 if (GetField(x1, ex, ey, ez, bx, by, bz, medium) != 0) {
1517 std::cerr << m_className <<
"::IntegrateEta: Invalid end point "
1518 << PrintVec(xe) <<
".\n";
1523 if (!GetEta(ex, ey, ez, bx, by, bz, medium, particle, eta1)) {
1524 std::cerr << m_className <<
"::IntegrateEta:\n"
1525 <<
" Cannot retrieve att. coefficient at end point.\n";
1528 double integral = 0.;
1532 if (Mag(xe[0] - x0[0], xe[1] - x0[1], xe[2] - x0[2]) < 1.e-6) {
1536 const double d = Mag(x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]);
1540 std::cout << m_className <<
"::IntegrateEta: Small step.\n";
1542 integral += eta0 * d;
1549 if (GetField(x1, ex, ey, ez, bx, by, bz, medium) != 0) {
1550 std::cerr << m_className <<
"::IntegrateEta: Invalid end point.\n";
1553 if (!GetEta(ex, ey, ez, bx, by, bz, medium, particle, eta1)) {
1554 std::cerr << m_className <<
"::IntegrateEta:\n"
1555 <<
" Cannot retrieve att. coefficient at end point.\n";
1560 for (
unsigned int i = 0; i < 3; ++i) xm[i] = 0.5 * (x0[i] + x1[i]);
1561 if (GetField(xm, ex, ey, ez, bx, by, bz, medium) != 0) {
1562 std::cerr << m_className <<
"::IntegrateEta: Invalid mid point.\n";
1566 if (!GetEta(ex, ey, ez, bx, by, bz, medium, particle, etam)) {
1567 std::cerr << m_className <<
"::IntegrateEta:\n"
1568 <<
" Cannot retrieve att. coefficient at mid point.\n";
1572 if (d *
fabs(eta0 - 2 * etam + eta1) / 3. < tol) {
1574 integral += d * (eta0 + 4 * etam + eta1) / 6.;
1588void DriftLineRKF::ComputeSignal(
const Particle particle,
const double scale,
1589 const std::vector<double>& ts,
1590 const std::vector<Vec>& xs,
1591 const std::vector<double>& ne)
const {
1593 const unsigned int nPoints = ts.size();
1594 if (nPoints < 2)
return;
1595 const double q = particle == Particle::Electron ? -1 * scale : scale;
1598 std::vector<std::array<double, 3> > vs;
1599 for (
const auto& x : xs) {
1602 if (!GetVelocity(x, particle, v, stat)) {
1603 std::cerr << m_className <<
"::ComputeSignal:\n"
1604 <<
" Cannot retrieve velocity at " << PrintVec(x) <<
"\n";
1606 vs.push_back(std::move(v));
1608 m_sensor->
AddSignal(q, ts, xs, vs, ne, m_navg);
void DisablePlotting()
Switch off drift line plotting.
bool DriftNegativeIon(const double x0, const double y0, const double z0, const double t0)
double GetLoss(const double eps=1.e-4)
Compute the attachment loss factor for the current drift line.
bool DriftHole(const double x0, const double y0, const double z0, const double t0)
Simulate the drift line of a hole with a given starting point.
bool DriftIon(const double x0, const double y0, const double z0, const double t0)
Simulate the drift line of an ion with a given starting point.
void SetSensor(Sensor *s)
Set the sensor.
void SetIntegrationAccuracy(const double eps)
Set the accuracy of the Runge Kutta Fehlberg drift line integration.
bool DriftElectron(const double x0, const double y0, const double z0, const double t0)
Simulate the drift line of an electron with a given starting point.
void SetGainFluctuationsPolya(const double theta, const double mean=-1.)
double GetGain(const double eps=1.e-4)
Compute the multiplication factor for the current drift line.
void SetGainFluctuationsFixed(const double gain=-1.)
Do not randomize the avalanche size.
bool DriftPositron(const double x0, const double y0, const double z0, const double t0)
void GetDriftLinePoint(const unsigned int i, double &x, double &y, double &z, double &t) const
Get the coordinates and time of a point along the most recent drift line.
void PrintDriftLine() const
Print the trajectory of the most recent drift line.
void EnablePlotting(ViewDrift *view)
Switch on drift line plotting.
DriftLineRKF()
Constructor.
double GetArrivalTimeSpread(const double eps=1.e-4)
void GetEndPoint(double &x, double &y, double &z, double &t, int &st) const
Get the end point and status flag of the most recent drift line.
void SetMaximumStepSize(const double ms)
Set the maximum step size that is allowed. By default, there is no limit.
Abstract base class for media.
void MagneticField(const double x, const double y, const double z, double &bx, double &by, double &bz, int &status)
Get the magnetic field at (x, y, z).
bool IsInTrapRadius(const double q0, const double x0, const double y0, const double z0, double &xw, double &yw, double &rw)
void ElectricField(const double x, const double y, const double z, double &ex, double &ey, double &ez, double &v, Medium *&medium, int &status)
Get the drift field and potential at (x, y, z).
void AddSignal(const double q, const double t0, const double t1, const double x0, const double y0, const double z0, const double x1, const double y1, const double z1, const bool integrateWeightingField, const bool useWeightingPotential=false)
Add the signal from a charge-carrier step.
bool IsWireCrossed(const double x0, const double y0, const double z0, const double x1, const double y1, const double z1, double &xc, double &yc, double &zc, const bool centre, double &rc)
bool GetArea(double &xmin, double &ymin, double &zmin, double &xmax, double &ymax, double &zmax)
Return the current user area.
Visualize drift lines and tracks.
void NewElectronDriftLine(const unsigned int np, int &id, const double x0, const double y0, const double z0)
void AddDriftLinePoint(const unsigned int iL, const double x, const double y, const double z)
void NewHoleDriftLine(const unsigned int np, int &id, const double x0, const double y0, const double z0)
void NewIonDriftLine(const unsigned int np, int &id, const double x0, const double y0, const double z0)
std::array< double, 3 > Vec
double RndmPolya(const double theta)
Draw a Polya distributed random number.
DoubleAc exp(const DoubleAc &f)
DoubleAc fabs(const DoubleAc &f)
DoubleAc sqrt(const DoubleAc &f)