23void SampleRange(
const double xmin,
const double ymin,
const double xmax,
24 const double ymax, TF2* f,
double& zmin,
double& zmax) {
25 constexpr unsigned int n = 1000;
26 const double dx = xmax - xmin;
27 const double dy = ymax - ymin;
28 zmin = std::numeric_limits<double>::max();
30 for (
unsigned int i = 0; i < n; ++i) {
33 if (z < zmin) zmin =
z;
34 if (z > zmax) zmax =
z;
38void SampleRange(TF1* f,
double& ymin,
double& ymax) {
39 constexpr unsigned int n = 1000;
40 ymin = std::numeric_limits<double>::max();
44 f->GetRange(xmin, xmax);
45 const double dx = xmax - xmin;
46 for (
unsigned int i = 0; i < n; ++i) {
48 if (y < ymin) ymin =
y;
49 if (y > ymax) ymax =
y;
53double Interpolate(
const std::array<double, 1000>& y,
54 const std::array<double, 1000>& x,
const double xx) {
56 const double tol = 1.e-6 *
fabs(
x.back() -
x.front());
57 if (xx < x[0])
return y[0];
58 const auto it1 = std::upper_bound(
x.cbegin(),
x.cend(), xx);
59 if (it1 ==
x.cend())
return y.back();
60 const auto it0 = std::prev(it1);
61 const double dx = (*it1 - *it0);
62 if (dx < tol)
return y[it0 -
x.cbegin()];
63 const double f = (xx - *it0) / dx;
64 return y[it0 -
x.cbegin()] * (1. - f) + f * y[it1 -
x.cbegin()];
75 std::cerr <<
m_className <<
"::SetSensor: Null pointer.\n";
79 m_component =
nullptr;
84 std::cerr <<
m_className <<
"::SetComponent: Null pointer.\n";
92 m_vmin = std::min(vmin, vmax);
93 m_vmax = std::max(vmin, vmax);
94 m_useAutoRange =
false;
98 m_emin = std::min(emin, emax);
99 m_emax = std::max(emin, emax);
100 m_useAutoRange =
false;
104 m_wmin = std::min(wmin, wmax);
105 m_wmax = std::max(wmin, wmax);
106 m_useAutoRange =
false;
110 m_bmin = std::min(bmin, bmax);
111 m_bmax = std::max(bmin, bmax);
112 m_useAutoRange =
false;
116 if (n > 0) m_nContours = n;
120 m_nSamples1d = std::max(4u, n);
124 const unsigned int ny) {
125 m_nSamples2dX = std::max(4u, nx);
126 m_nSamples2dY = std::max(4u, ny);
130 Draw2d(option,
true,
false,
"",
"CONT1Z");
135 std::transform(drawopt.begin(), drawopt.end(),
136 std::back_inserter(opt1), toupper);
137 if (opt1.find(
"CONT") != std::string::npos) {
138 Draw2d(option,
true,
false,
"", drawopt);
140 Draw2d(option,
false,
false,
"", drawopt);
145 const double x1,
const double y1,
const double z1,
146 const std::string& option,
const bool normalised) {
147 DrawProfile(x0, y0, z0, x1, y1, z1, option,
false,
"", normalised);
151 const std::string& option,
152 const std::string& drawopt,
const double t) {
153 Draw2d(option,
false,
true, label, drawopt, t);
157 const std::string& option) {
158 Draw2d(option,
true,
true, label,
"CONT1Z");
162 const double x0,
const double y0,
163 const double z0,
const double x1,
164 const double y1,
const double z1,
165 const std::string& option,
166 const bool normalised) {
167 DrawProfile(x0, y0, z0, x1, y1, z1, option,
true, label, normalised);
171ViewField::Parameter ViewField::GetPar(
const std::string& option,
172 std::string& title,
bool& bfield)
const {
176 std::transform(option.begin(), option.end(),
177 std::back_inserter(opt), toupper);
181 return Parameter::Bmag;
182 }
else if (opt ==
"BX") {
183 title =
"field (x-component)";
185 return Parameter::Bx;
186 }
else if (opt ==
"BY") {
187 title =
"field (y-component)";
189 return Parameter::By;
190 }
else if (opt ==
"BZ") {
191 title =
"field (z-component)";
193 return Parameter::Bz;
194 }
else if (opt ==
"V" || opt ==
"P" || opt ==
"PHI" ||
195 opt.find(
"VOLT") != std::string::npos ||
196 opt.find(
"POT") != std::string::npos) {
198 return Parameter::Potential;
199 }
else if (opt ==
"E" || opt ==
"FIELD" || opt ==
"NORM" ||
200 opt.find(
"MAG") != std::string::npos) {
202 return Parameter::Emag;
203 }
else if (opt.find(
"X") != std::string::npos) {
204 title =
"field (x-component)";
205 return Parameter::Ex;
206 }
else if (opt.find(
"Y") != std::string::npos) {
207 title =
"field (y-component)";
208 return Parameter::Ey;
209 }
else if (opt.find(
"Z") != std::string::npos) {
210 title =
"field (z-component)";
211 return Parameter::Ez;
213 std::cerr <<
m_className <<
"::GetPar: Unknown option (" << option <<
").\n";
215 return Parameter::Potential;
218void ViewField::Draw2d(
const std::string& option,
const bool contour,
219 const bool wfield,
const std::string& electrode,
220 const std::string& drawopt,
const double t) {
221 if (!m_sensor && !m_component) {
223 <<
" Neither sensor nor component are defined.\n";
228 if (!SetPlotLimits())
return;
233 const Parameter par = GetPar(option, title, bfield);
235 auto eval = [
this, par, wfield, bfield, electrode, t](
double* u,
double* ) {
240 return wfield ? Wfield(x, y, z, par, electrode, t) :
241 bfield ? Bfield(
x,
y,
z, par) : Efield(
x,
y,
z, par);
250 double zmin = m_vmin;
251 double zmax = m_vmax;
254 title =
"Contours of the weighting " + title;
256 title =
"Weighting " + title;
258 if (m_useAutoRange) {
261 }
else if (par == Parameter::Potential) {
270 title =
"Contours of the magnetic " + title;
272 title =
"Magnetic " + title;
274 if (m_useAutoRange) {
283 title =
"Contours of the electric " + title;
285 title =
"Electric " + title;
287 if (par == Parameter::Potential) {
288 if (m_useAutoRange) {
290 if (m_samplePotential || !m_component->GetVoltageRange(zmin, zmax)) {
294 }
else if (m_sensor) {
295 if (m_samplePotential || !m_sensor->GetVoltageRange(zmin, zmax)) {
305 if (m_useAutoRange) {
319 std::vector<double> level(m_nContours, 0.);
320 if (m_nContours > 1) {
321 const double step = (zmax - zmin) / (m_nContours - 1.);
322 for (
unsigned int i = 0; i < m_nContours; ++i) {
323 level[i] = zmin + i * step;
326 level[0] = 0.5 * (zmax + zmin);
330 <<
" Number of contours: " << m_nContours <<
"\n";
331 for (
unsigned int i = 0; i < m_nContours; ++i) {
332 std::cout <<
" Level " << i <<
" = " << level[i] <<
"\n";
335 f2.SetContour(m_nContours, level.data());
339 f2.SetNpx(m_nSamples2dX);
340 f2.SetNpy(m_nSamples2dY);
344 f2.SetTitle(labels.c_str());
348 pad->SetTitle(title.c_str());
349 f2.DrawCopy(drawopt.c_str());
350 gPad->SetRightMargin(0.15);
354void ViewField::DrawProfile(
const double x0,
const double y0,
const double z0,
355 const double x1,
const double y1,
const double z1,
356 const std::string& option,
357 const bool wfield,
const std::string& electrode,
358 const bool normalised) {
359 if (!m_sensor && !m_component) {
361 <<
" Neither sensor nor component are defined.\n";
369 if (dx * dx + dy * dy + dz * dz <= 0.) {
371 <<
" Start and end points coincide.\n";
378 const Parameter par = GetPar(option, title, bfield);
382 unsigned int dir = 3;
395 }
else if (!normalised) {
396 t1 =
sqrt(dx * dx + dy * dy + dz * dz);
402 auto eval = [
this, par, wfield, bfield, electrode, dir,
403 x0, y0, z0, dx, dy, dz](
double* u,
double* ) {
405 const double t = u[0];
406 double x = dir == 0 ? t : x0;
407 double y = dir == 1 ? t : y0;
408 double z = dir == 2 ? t : z0;
414 return wfield ? Wfield(x, y, z, par, electrode) :
415 bfield ? Bfield(
x,
y,
z, par) : Efield(
x,
y,
z, par);
419 TF1 f1(fname.c_str(), eval, t0, t1, 0);
421 double fmin = m_vmin;
422 double fmax = m_vmax;
424 title =
"weighting " + title;
425 if (par == Parameter::Potential) {
426 if (m_useAutoRange && m_samplePotential) {
427 SampleRange(&f1, fmin, fmax);
433 if (m_useAutoRange) {
434 SampleRange(&f1, fmin, fmax);
441 title =
"magnetic " + title;
442 if (m_useAutoRange) {
443 SampleRange(&f1, fmin, fmax);
449 title =
"electric " + title;
450 if (par == Parameter::Potential) {
451 if (m_useAutoRange) {
453 if (m_samplePotential || !m_component->GetVoltageRange(fmin, fmax)) {
454 SampleRange(&f1, fmin, fmax);
456 }
else if (m_sensor) {
457 if (m_samplePotential || !m_sensor->GetVoltageRange(fmin, fmax)) {
458 SampleRange(&f1, fmin, fmax);
466 if (m_useAutoRange) {
467 SampleRange(&f1, fmin, fmax);
477 std::string labels =
";normalised distance;";
479 labels =
";#it{x} [cm];";
480 }
else if (dir == 1) {
481 labels =
";#it{y} [cm];";
482 }
else if (dir == 2) {
483 labels =
";#it{z} [cm];";
484 }
else if (!normalised) {
485 labels =
";distance [cm];";
489 if (par == Parameter::Bx) {
491 }
else if (par == Parameter::By) {
493 }
else if (par == Parameter::Bz) {
497 }
else if (par == Parameter::Potential) {
508 if (par != Parameter::Emag) labels +=
",";
509 }
else if (par != Parameter::Emag) {
512 if (par == Parameter::Ex) {
514 }
else if (par == Parameter::Ey) {
516 }
else if (par == Parameter::Ez) {
519 if (wfield || par != Parameter::Emag) labels +=
"}";
526 f1.SetTitle(labels.c_str());
527 f1.SetNpx(m_nSamples1d);
531 title =
"Profile plot of the " + title;
532 pad->SetTitle(title.c_str());
537bool ViewField::SetPlotLimits() {
540 double xmin = 0., ymin = 0., xmax = 0., ymax = 0.;
554 ok =
PlotLimits(m_sensor, xmin, ymin, xmax, ymax);
556 ok =
PlotLimits(m_component, xmin, ymin, xmax, ymax);
567double ViewField::Efield(
const double x,
const double y,
const double z,
568 const Parameter par)
const {
571 double ex = 0., ey = 0., ez = 0., volt = 0.;
573 Medium* medium =
nullptr;
575 m_component->ElectricField(x, y, z, ex, ey, ez, volt, medium, status);
577 m_sensor->ElectricField(x, y, z, ex, ey, ez, volt, medium, status);
579 if (m_useStatus && status != 0)
return m_vBkg;
581 case Parameter::Potential:
583 case Parameter::Emag:
584 return sqrt(ex * ex + ey * ey + ez * ez);
597double ViewField::Wfield(
const double x,
const double y,
const double z,
599 const std::string& electrode,
const double t)
const {
601 if (par == Parameter::Potential) {
604 v = m_sensor->WeightingPotential(x, y, z, electrode);
606 v += m_sensor->DelayedWeightingPotential(x, y, z, t, electrode);
609 v = m_component->WeightingPotential(x, y, z, electrode);
611 v += m_component->DelayedWeightingPotential(x, y, z, t, electrode);
614 return std::max(0., v);
617 double ex = 0., ey = 0., ez = 0.;
619 m_component->WeightingField(x, y, z, ex, ey, ez, electrode);
621 m_sensor->WeightingField(x, y, z, ex, ey, ez, electrode);
625 case Parameter::Emag:
626 return sqrt(ex * ex + ey * ey + ez * ez);
639double ViewField::Bfield(
const double x,
const double y,
const double z,
640 const Parameter par)
const {
643 double bx = 0., by = 0., bz = 0.;
646 m_component->MagneticField(x, y, z, bx, by, bz, status);
648 m_sensor->MagneticField(x, y, z, bx, by, bz, status);
650 if (m_useStatus && status != 0)
return 0.;
652 case Parameter::Bmag:
653 return sqrt(bx * bx + by * by + bz * bz);
667 const std::vector<double>& y0,
668 const std::vector<double>& z0,
669 const bool electron,
const bool axis,
672 if (x0.empty() || y0.empty() || z0.empty())
return;
673 const size_t nLines = x0.size();
674 if (y0.size() != nLines || z0.size() != nLines) {
676 <<
" Mismatch in number of x/y/z coordinates.\n";
680 if (!m_sensor && !m_component) {
682 <<
" Neither sensor nor component are defined.\n";
688 pad->SetTitle(
"Field lines");
690 const bool rangeSet =
RangeSet(pad);
691 if (axis || !rangeSet) {
693 if (!SetPlotLimits()) {
695 <<
" Could not determine the plot limits.\n";
702 frame->GetXaxis()->SetTitle(
LabelX().c_str());
703 frame->GetYaxis()->SetTitle(
LabelY().c_str());
705 }
else if (!rangeSet) {
714 double xmin = 0., ymin = 0., zmin = 0.;
715 double xmax = 0., ymax = 0., zmax = 0.;
716 if (!m_component->GetBoundingBox(xmin, ymin, zmin, xmax, ymax, zmax)) {
728 for (
size_t i = 0; i < nLines; ++i) {
729 std::vector<std::array<float, 3> > xl;
730 if (!drift.
FieldLine(x0[i], y0[i], z0[i], xl, electron))
continue;
737 const double x0,
const double y0,
const double z0,
738 const double x1,
const double y1,
const double z1,
739 std::vector<double>& xf, std::vector<double>& yf,
740 std::vector<double>& zf,
741 const unsigned int nPoints)
const {
744 std::cerr <<
m_className <<
"::EqualFluxIntervals:\n"
745 <<
" Number of flux lines must be > 1.\n";
750 constexpr unsigned int nV = 5;
752 const double xp = (y1 - y0) *
m_plane[2] - (z1 - z0) *
m_plane[1];
753 const double yp = (z1 - z0) *
m_plane[0] - (x1 - x0) *
m_plane[2];
754 const double zp = (x1 - x0) *
m_plane[1] - (y1 - y0) *
m_plane[0];
758 q = m_component->IntegrateFluxLine(x0, y0, z0, x1, y1, z1,
759 xp, yp, zp, 20 * nV, 0);
761 q = m_sensor->IntegrateFluxLine(x0, y0, z0, x1, y1, z1,
762 xp, yp, zp, 20 * nV, 0);
764 const int isign = q > 0 ? +1 : -1;
766 std::cout <<
m_className <<
"::EqualFluxIntervals:\n";
767 std::printf(
" Total flux: %15.e8\n", q);
771 unsigned int nOtherSign = 0;
774 constexpr size_t nSteps = 1000;
775 std::array<double, nSteps> sTab;
776 std::array<double, nSteps> fTab;
777 constexpr double ds = 1. / nSteps;
778 const double dx = (x1 - x0) * ds;
779 const double dy = (y1 - y0) * ds;
780 const double dz = (z1 - z0) * ds;
781 for (
size_t i = 0; i < nSteps; ++i) {
782 const double x = x0 + i * dx;
783 const double y = y0 + i * dy;
784 const double z = z0 + i * dz;
786 q = m_component->IntegrateFluxLine(x, y, z, x + dx, y + dy, z + dz,
787 xp, yp, zp, nV, isign);
789 q = m_sensor->IntegrateFluxLine(x, y, z, x + dx, y + dy, z + dz,
790 xp, yp, zp, nV, isign);
792 sTab[i] = (i + 1) * ds;
795 if (s0 < -0.5) s0 = i * ds;
798 if (q < 0) ++nOtherSign;
802 std::printf(
" Used flux: %15.8e V. Start: %10.3f End: %10.3f\n",
807 std::cerr <<
m_className <<
"::EqualFluxIntervals:\n"
808 <<
" 1-Sided flux integral is not > 0.\n";
810 }
else if (s0 < -0.5 || s1 < -0.5 || s1 <= s0) {
811 std::cerr <<
m_className <<
"::EqualFluxIntervals:\n"
812 <<
" No flux interval without sign change found.\n";
814 }
else if (nOtherSign > 0) {
815 std::cerr <<
m_className <<
"::EqualFluxIntervals:\n"
816 <<
" The flux changes sign over the line.\n";
819 const double scale = (nPoints - 1) / fsum;
820 for (
size_t i = 0; i < nSteps; ++i) fTab[i] *= scale;
823 for (
size_t i = 0; i < nPoints; ++i) {
824 double s = std::min(s1, std::max(s0, Interpolate(sTab, fTab, i)));
825 xf.push_back(x0 + s * (x1 - x0));
826 yf.push_back(y0 + s * (y1 - y0));
827 zf.push_back(z0 + s * (z1 - z0));
833 const double x0,
const double y0,
const double z0,
834 const double x1,
const double y1,
const double z1,
835 std::vector<double>& xf, std::vector<double>& yf,
836 std::vector<double>& zf,
const double interval)
const {
838 if (interval <= 0.) {
839 std::cerr <<
m_className <<
"::FixedFluxIntervals:\n"
840 <<
" Flux interval must be > 0.\n";
845 constexpr unsigned int nV = 5;
847 const double xp = (y1 - y0) *
m_plane[2] - (z1 - z0) *
m_plane[1];
848 const double yp = (z1 - z0) *
m_plane[0] - (x1 - x0) *
m_plane[2];
849 const double zp = (x1 - x0) *
m_plane[1] - (y1 - y0) *
m_plane[0];
853 q = m_component->IntegrateFluxLine(x0, y0, z0, x1, y1, z1,
854 xp, yp, zp, 20 * nV, 0);
856 q = m_sensor->IntegrateFluxLine(x0, y0, z0, x1, y1, z1,
857 xp, yp, zp, 20 * nV, 0);
859 const int isign = q > 0 ? +1 : -1;
861 std::cout <<
m_className <<
"::FixedFluxIntervals:\n";
862 std::printf(
" Total flux: %15.8e V\n", q);
866 unsigned int nOtherSign = 0;
868 constexpr size_t nSteps = 1000;
869 std::array<double, nSteps> sTab;
870 std::array<double, nSteps> fTab;
871 constexpr double ds = 1. / nSteps;
872 const double dx = (x1 - x0) * ds;
873 const double dy = (y1 - y0) * ds;
874 const double dz = (z1 - z0) * ds;
875 for (
size_t i = 0; i < nSteps; ++i) {
876 const double x = x0 + i * dx;
877 const double y = y0 + i * dy;
878 const double z = z0 + i * dz;
880 q = m_component->IntegrateFluxLine(x, y, z, x + dx, y + dy, z + dz,
881 xp, yp, zp, nV, isign);
883 q = m_sensor->IntegrateFluxLine(x, y, z, x + dx, y + dy, z + dz,
884 xp, yp, zp, nV, isign);
886 sTab[i] = (i + 1) * ds;
889 if (s0 < -0.5) s0 = i * ds;
891 if (q < 0) ++nOtherSign;
896 std::printf(
" Used flux: %15.8e V. Start offset: %10.3f\n", fsum, s0);
899 std::cerr <<
m_className <<
"::FixedFluxIntervals:\n"
900 <<
" 1-Sided flux integral is not > 0.\n";
902 }
else if (s0 < -0.5) {
903 std::cerr <<
m_className <<
"::FixedFluxIntervals:\n"
904 <<
" No flux interval without sign change found.\n";
906 }
else if (nOtherSign > 0) {
907 std::cerr <<
m_className <<
"::FixedFluxIntervals:\n"
908 <<
" Warning: The flux changes sign over the line.\n";
913 const double s = Interpolate(sTab, fTab, f);
915 xf.push_back(x0 + s * (x1 - x0));
916 yf.push_back(y0 + s * (y1 - y0));
917 zf.push_back(z0 + s * (z1 - z0));
Abstract base class for components.
bool FieldLine(const double xi, const double yi, const double zi, std::vector< std::array< float, 3 > > &xl, const bool electron=true) const
void SetSensor(Sensor *s)
Set the sensor.
void SetMaximumStepSize(const double ms)
Set (explicitly) the maximum step size that is allowed.
void AddComponent(Component *comp)
Add a component.
bool SetArea(const bool verbose=false)
Set the user area to the default.
std::array< double, 4 > m_plane
static void SetRange(TVirtualPad *pad, const double x0, const double y0, const double x1, const double y1)
void DrawLine(const std::vector< std::array< float, 3 > > &xl, const short col, const short lw)
std::array< std::array< double, 3 >, 3 > m_proj
TPad * GetCanvas()
Retrieve the canvas.
static bool RangeSet(TVirtualPad *)
bool PlotLimits(Sensor *sensor, double &xmin, double &ymin, double &xmax, double &ymax) const
bool PlotLimitsFromUserBox(double &xmin, double &ymin, double &xmax, double &ymax) const
static std::string FindUnusedFunctionName(const std::string &s)
Find an unused function name.
ViewBase()=delete
Default constructor.
void SetMagneticFieldRange(const double bmin, const double bmax)
Set the plot limits for the magnetic field.
void PlotProfileWeightingField(const std::string &label, const double x0, const double y0, const double z0, const double x1, const double y1, const double z1, const std::string &option="v", const bool normalised=true)
void SetElectricFieldRange(const double emin, const double emax)
Set the plot limits for the electric field.
void SetNumberOfSamples2d(const unsigned int nx, const unsigned int ny)
Set the number of points used for drawing 2D functions.
bool FixedFluxIntervals(const double x0, const double y0, const double z0, const double x1, const double y1, const double z1, std::vector< double > &xf, std::vector< double > &yf, std::vector< double > &zf, const double interval=10.) const
Generate points along a line, spaced by a given flux interval.
void SetComponent(Component *c)
Set the component for which to plot the field.
void SetNumberOfSamples1d(const unsigned int n)
Set the number of points used for drawing 1D functions.
void PlotFieldLines(const std::vector< double > &x0, const std::vector< double > &y0, const std::vector< double > &z0, const bool electron=true, const bool axis=true, const short col=kOrange - 3)
Draw electric field lines from a set of starting points.
void PlotWeightingField(const std::string &label, const std::string &option, const std::string &drawopt, const double t=0.)
void SetVoltageRange(const double vmin, const double vmax)
Set the plot limits for the potential.
void PlotProfile(const double x0, const double y0, const double z0, const double x1, const double y1, const double z1, const std::string &option="v", const bool normalised=true)
bool EqualFluxIntervals(const double x0, const double y0, const double z0, const double x1, const double y1, const double z1, std::vector< double > &xf, std::vector< double > &yf, std::vector< double > &zf, const unsigned int nPoints=20) const
Generates point along a line, spaced by equal flux intervals.
void PlotContour(const std::string &option="v")
void Plot(const std::string &option="v", const std::string &drawopt="arr")
void SetNumberOfContours(const unsigned int n)
Set the number of contour levels.
void SetWeightingFieldRange(const double wmin, const double wmax)
Set the plot limits for the weighting field.
void SetSensor(Sensor *s)
Set the sensor for which to plot the field.
void PlotContourWeightingField(const std::string &label, const std::string &option)
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
DoubleAc fabs(const DoubleAc &f)
DoubleAc sqrt(const DoubleAc &f)