Garfield++ 3.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
ComponentAnalyticField.cc
Go to the documentation of this file.
1#include <algorithm>
2#include <cstdio>
3#include <fstream>
4#include <iomanip>
5#include <iostream>
6#include <numeric>
7
8#include <TCanvas.h>
9#include <TGraph.h>
10
13#include "Garfield/Numerics.hh"
14
15namespace {
16
17constexpr double Internal2Newton = 1.e-15 * Garfield::TwoPiEpsilon0 * 100.;
18
19std::pair<std::complex<double>, std::complex<double> > Th1(
20 const std::complex<double>& zeta, const double p1, const double p2) {
21 const std::complex<double> zsin = sin(zeta);
22 const std::complex<double> zcof = 4. * zsin * zsin - 2.;
23 std::complex<double> zu = -p1 - zcof * p2;
24 std::complex<double> zunew = 1. - zcof * zu - p2;
25 const std::complex<double> zterm1 = (zunew + zu) * zsin;
26 zu = -3. * p1 - zcof * 5. * p2;
27 zunew = 1. - zcof * zu - 5. * p2;
28 const std::complex<double> zterm2 = (zunew - zu) * cos(zeta);
29 return std::make_pair(std::move(zterm1), std::move(zterm2));
30}
31
32// Transformation from Cartesian and polar coordinates.
33void Cartesian2Polar(const double x, const double y, double& r,
34 double& theta) {
35 if (x == 0. && y == 0.) {
36 r = theta = 0.;
37 return;
38 }
39 r = sqrt(x * x + y * y);
40 theta = atan2(y, x) * Garfield::RadToDegree;
41}
42
43// Transformation from polar to Cartesian coordinates.
44void Polar2Cartesian(const double r, const double theta, double& x,
45 double& y) {
46 const double thetap = theta * Garfield::DegreeToRad;
47 x = r * cos(thetap);
48 y = r * sin(thetap);
49}
50
51// Transformation (rho, phi) to (r, theta) via the map
52// (r, theta) = (exp(rho), 180 * phi / Pi).
53void Internal2Polar(const double rho, const double phi, double& r,
54 double& theta) {
55 // CFMRTP
56 r = exp(rho);
57 theta = Garfield::RadToDegree * phi;
58}
59
60// Transformation (r, theta) to (rho, phi) via the map
61// (rho, phi) = (log(r), Pi * theta / 180).
62void Polar2Internal(const double r, const double theta, double& rho,
63 double& phi) {
64 // CFMPTR
65 rho = r > 0. ? log(r) : -25.;
66 phi = Garfield::DegreeToRad * theta;
67}
68
69// Transformation (x, y) to (rho, phi) via the conformal map
70// (x, y) = exp(rho, phi)
71void Cartesian2Internal(const double x, const double y, double& rho,
72 double& phi) {
73 // CFMCTR
74 if (x == 0 && y == 0) {
75 rho = -25.;
76 phi = 0.;
77 return;
78 }
79 // const std::complex<double> z = log(std::complex<double>(x, y));
80 // rho = real(z);
81 // phi = imag(z);
82 rho = 0.5 * log(x * x + y * y);
83 phi = atan2(y, x);
84}
85
86// Transformation (rho, phi) to (x, y) via the conformal map
87// (x, y) = exp(rho, phi)
88void Internal2Cartesian(const double rho, const double phi, double& x,
89 double& y) {
90 // CFMRTC
91 // const std::complex<double> z = exp(std::complex<double>(rho, phi));
92 // x = real(z);
93 // y = imag(z);
94 const double r = exp(rho);
95 x = r * cos(phi);
96 y = r * sin(phi);
97}
98
99bool FitDipoleMoment(const std::vector<double>& angle,
100 const std::vector<double>& volt,
101 double& ampdip, double& phidip, const bool dbg) {
102 //-----------------------------------------------------------------------
103 // DIPFIT - Determines the dipole moment of a wire.
104 //-----------------------------------------------------------------------
105
106 // Initial values.
107 phidip = 0.;
108 ampdip = 0.;
109 const unsigned int n = angle.size();
110 // Initial search for a maximum.
111 double phiMax = 0.;
112 double sumMax = 0.;
113 constexpr unsigned int nTry = 100;
114 std::array<double, nTry> phiTry;
115 std::array<double, nTry> sumTry;
116 for (unsigned int i = 0; i < nTry; ++i) {
117 // Make the internal product with a shifted cosine.
118 phiTry[i] = i * Garfield::TwoPi / nTry;
119 sumTry[i] = 0.;
120 for (unsigned int j = 0; j < n; ++j) {
121 sumTry[i] += volt[j] * cos(phiTry[i] - angle[j]);
122 }
123 sumTry[i] *= 2. / n;
124 // See whether this one beats earlier.
125 if (sumTry[i] > sumMax) {
126 phiMax = phiTry[i];
127 sumMax = sumTry[i];
128 }
129 }
130 if (dbg) {
131 std::printf(" Maximum of scan at phi = %12.5f, product = %12.5f\n",
132 phiMax, sumMax);
133 // CALL GRGRPH(phiTry, sumTry, nTry, 'Angle [radians]',
134 // 'Cos projection', 'Search of maximum')
135 }
136 phidip = phiMax;
137 ampdip = sumMax;
138 // Scan in the neighbourbood
139 constexpr double eps = 0.1;
140 double x1 = phiMax - eps;
141 double x2 = phiMax;
142 double x3 = phiMax + eps;
143 double f1 = 0.;
144 double f2 = sumMax;
145 double f3 = 0.;
146 for (unsigned int j = 0; j < n; ++j) {
147 f1 += volt[j] * cos(x1 - angle[j]);
148 f3 += volt[j] * cos(x3 - angle[j]);
149 }
150 f1 *= 2. / n;
151 f3 *= 2. / n;
152 // Refine the estimate by parabolic extremum search.
153 const double epsf = 1.e-3 * sumMax;
154 constexpr double epsx = 1.e-3 * Garfield::TwoPi;
155 constexpr unsigned int nMaxIter = 10;
156 for (unsigned int i = 0; i < nMaxIter; ++i) {
157 if (dbg) std::cout << " Start of iteration " << i << ".\n";
158 // Estimate parabolic extremum.
159 const double det = (f1 - f2) * x3 + (f3 - f1) * x2 + (f2 - f3) * x1;
160 if (std::abs(det) <= 0.) {
161 std::cerr << " Warning: Determinant = 0; parabolic search stopped.\n";
162 phidip = x2;
163 ampdip = f2;
164 return false;
165 }
166 const double xp = ((f1 - f2) * x3 * x3 + (f3 - f1) * x2 * x2 +
167 (f2 - f3) * x1 * x1) / (2 * det);
168 double fp = 0.;
169 for (unsigned int j = 0; j < n; ++j) {
170 fp += volt[j] * cos(xp - angle[j]);
171 }
172 fp *= 2. / n;
173 // Debugging output.
174 if (dbg) {
175 std::printf(" Point 1: x = %15.8f f = %15.8f\n", x1, f1);
176 std::printf(" Point 2: x = %15.8f f = %15.8f\n", x2, f2);
177 std::printf(" Point 3: x = %15.8f f = %15.8f\n", x3, f3);
178 std::printf(" Parabola: x = %15.8f f = %15.8f\n", xp, fp);
179 }
180 // Check that the new estimate doesn't coincide with an old point.
181 const double tol = epsx * (epsx + std::abs(xp));
182 if (fabs(xp - x1) < tol || fabs(xp - x2) < tol || fabs(xp - x3) < tol) {
183 if (dbg) {
184 std::cout << " Location convergence criterion satisfied.\n";
185 }
186 phidip = xp;
187 ampdip = fp;
188 return true;
189 }
190 // Check convergence.
191 if (std::abs(fp - f1) < epsf * (std::abs(fp) + std::abs(f1) + epsf)) {
192 if (dbg) {
193 std::cout << " Function value convergence criterion satisfied.\n";
194 }
195 phidip = xp;
196 ampdip = fp;
197 return true;
198 }
199 // Store the value in the table.
200 if (fp > f1) {
201 f3 = f2;
202 x3 = x2;
203 f2 = f1;
204 x2 = x1;
205 f1 = fp;
206 x1 = xp;
207 } else if (fp > f2) {
208 f3 = f2;
209 x3 = x2;
210 f2 = fp;
211 x2 = xp;
212 } else if (fp > f3) {
213 f3 = fp;
214 x3 = xp;
215 } else {
216 std::cerr << " Warning: Parabolic extremum is worse "
217 << "than current optimum; search stopped.\n";
218 std::printf(" Point 1: x = %15.8f f = %15.8f\n", x1, f1);
219 std::printf(" Point 2: x = %15.8f f = %15.8f\n", x2, f2);
220 std::printf(" Point 3: x = %15.8f f = %15.8f\n", x3, f3);
221 std::printf(" Parabola: x = %15.8f f = %15.8f\n", xp, fp);
222 phidip = x2;
223 ampdip = f2;
224 return false;
225 }
226 }
227 // No convergence.
228 std::cerr << " Warning: No convergence after maximum number of steps.\n"
229 << " Current extremum f = " << f2 << "\n"
230 << " Found for x = " << x2 << "\n";
231 phidip = x2;
232 ampdip = f2;
233 return false;
234}
235
236} // namespace
237
238namespace Garfield {
239
241 m_className = "ComponentAnalyticField";
242 CellInit();
243}
244
245Medium* ComponentAnalyticField::GetMedium(const double xin, const double yin,
246 const double zin) {
247 if (m_geometry) return m_geometry->GetMedium(xin, yin, zin);
248
249 // Make sure the cell is prepared.
250 if (!m_cellset && !Prepare()) return nullptr;
251
252 double xpos = xin, ypos = yin;
253 if (m_polar) Cartesian2Internal(xin, yin, xpos, ypos);
254 // In case of periodicity, move the point into the basic cell.
255 if (m_perx) {
256 xpos -= m_sx * int(round(xin / m_sx));
257 }
258 double arot = 0.;
259 if (m_pery && m_tube) {
260 Cartesian2Polar(xin, yin, xpos, ypos);
261 arot = RadToDegree * m_sy * int(round(DegreeToRad * ypos / m_sy));
262 ypos -= arot;
263 Polar2Cartesian(xpos, ypos, xpos, ypos);
264 } else if (m_pery) {
265 ypos -= m_sy * int(round(ypos / m_sy));
266 }
267
268 // Move the point to the correct side of the plane.
269 if (m_perx && m_ynplan[0] && xpos <= m_coplan[0]) xpos += m_sx;
270 if (m_perx && m_ynplan[1] && xpos >= m_coplan[1]) xpos -= m_sx;
271 if (m_pery && m_ynplan[2] && ypos <= m_coplan[2]) ypos += m_sy;
272 if (m_pery && m_ynplan[3] && ypos >= m_coplan[3]) ypos -= m_sy;
273
274 // In case (xpos, ypos) is located behind a plane there is no field.
275 if (m_tube) {
276 if (!InTube(xpos, ypos, m_cotube, m_ntube)) return nullptr;
277 } else {
278 if ((m_ynplan[0] && xpos < m_coplan[0]) ||
279 (m_ynplan[1] && xpos > m_coplan[1]) ||
280 (m_ynplan[2] && ypos < m_coplan[2]) ||
281 (m_ynplan[3] && ypos > m_coplan[3])) {
282 return nullptr;
283 }
284 }
285
286 // If (xpos, ypos) is within a wire, there is no field either.
287 for (const auto& wire : m_w) {
288 double dx = xpos - wire.x;
289 double dy = ypos - wire.y;
290 // Correct for periodicities.
291 if (m_perx) dx -= m_sx * int(round(dx / m_sx));
292 if (m_pery) dy -= m_sy * int(round(dy / m_sy));
293 // Check the actual position.
294 if (dx * dx + dy * dy < wire.r * wire.r) return nullptr;
295 }
296 return m_medium;
297}
298
299bool ComponentAnalyticField::GetVoltageRange(double& pmin, double& pmax) {
300 // Make sure the cell is prepared.
301 if (!m_cellset && !Prepare()) {
302 std::cerr << m_className << "::GetVoltageRange: Cell not set up.\n";
303 return false;
304 }
305
306 pmin = m_vmin;
307 pmax = m_vmax;
308 return true;
309}
310
311bool ComponentAnalyticField::GetBoundingBox(double& x0, double& y0, double& z0,
312 double& x1, double& y1,
313 double& z1) {
314 // If a geometry is present, try to get the bounding box from there.
315 if (m_geometry) {
316 if (m_geometry->GetBoundingBox(x0, y0, z0, x1, y1, z1)) return true;
317 }
318 // Otherwise, return the cell dimensions.
319 if (!m_cellset && !Prepare()) return false;
320 if (m_polar) {
321 double rmax, thetamax;
322 Internal2Polar(m_xmax, m_ymax, rmax, thetamax);
323 x0 = -rmax;
324 y0 = -rmax;
325 x1 = +rmax;
326 y1 = +rmax;
327 } else {
328 x0 = m_xmin;
329 y0 = m_ymin;
330 x1 = m_xmax;
331 y1 = m_ymax;
332 }
333 z0 = m_zmin;
334 z1 = m_zmax;
335 return true;
336}
337
339 //-----------------------------------------------------------------------
340 // CELPRT - Subroutine printing all available information on the cell.
341 //-----------------------------------------------------------------------
342
343 // Make sure the cell is prepared.
344 if (!m_cellset && !Prepare()) {
345 std::cerr << m_className << "::PrintCell: Cell not set up.\n";
346 return;
347 }
348 std::cout << m_className
349 << "::PrintCell: Cell identification: " << GetCellType() << "\n";
350 // Print positions of wires, applied voltages and resulting charges.
351 if (!m_w.empty()) {
352 std::cout << " Table of the wires\n";
353 if (m_polar) {
354 std::cout << " Nr Diameter r phi Voltage";
355 } else {
356 std::cout << " Nr Diameter x y Voltage";
357 }
358 std::cout << " Charge Tension Length Density Label\n";
359 if (m_polar) {
360 std::cout << " [micron] [cm] [deg] [Volt]";
361 } else {
362 std::cout << " [micron] [cm] [cm] [Volt]";
363 }
364 std::cout << " [pC/cm] [g] [cm] [g/cm3]\n";
365 for (unsigned int i = 0; i < m_nWires; ++i) {
366 const auto& w = m_w[i];
367 double xw = w.x;
368 double yw = w.y;
369 double dw = 2 * w.r;
370 if (m_polar) {
371 Internal2Polar(w.x, w.y, xw, yw);
372 dw *= xw;
373 }
374 std::printf(
375 "%4d %9.2f %9.4f %9.4f %9.3f %12.4f %9.2f %9.2f %9.2f \"%s\"\n", i,
376 1.e4 * dw, xw, yw, w.v, w.e * TwoPiEpsilon0 * 1.e-3, w.tension,
377 w.u, w.density, w.type.c_str());
378 }
379 }
380 // Print information on the tube if present.
381 if (m_tube) {
382 std::string shape;
383 if (m_ntube == 0) {
384 shape = "Circular";
385 } else if (m_ntube == 3) {
386 shape = "Triangular";
387 } else if (m_ntube == 4) {
388 shape = "Square";
389 } else if (m_ntube == 5) {
390 shape = "Pentagonal";
391 } else if (m_ntube == 6) {
392 shape = "Hexagonal";
393 } else if (m_ntube == 7) {
394 shape = "Heptagonal";
395 } else if (m_ntube == 8) {
396 shape = "Octagonal";
397 } else {
398 shape = "Polygonal with " + std::to_string(m_ntube) + " corners";
399 }
400 std::cout << " Enclosing tube\n"
401 << " Potential: " << m_vttube << " V\n"
402 << " Radius: " << m_cotube << " cm\n"
403 << " Shape: " << shape << "\n"
404 << " Label: " << m_planes[4].type << "\n";
405 }
406 // Print data on the equipotential planes.
407 if (m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3]) {
408 std::cout << " Equipotential planes\n";
409 // First those at const x or r.
410 const std::string xr = m_polar ? "r" : "x";
411 if (m_ynplan[0] && m_ynplan[1]) {
412 std::cout << " There are two planes at constant " << xr << ":\n";
413 } else if (m_ynplan[0] || m_ynplan[1]) {
414 std::cout << " There is one plane at constant " << xr << ":\n";
415 }
416 for (unsigned int i = 0; i < 2; ++i) {
417 if (!m_ynplan[i]) continue;
418 if (m_polar) {
419 std::cout << " r = " << exp(m_coplan[i]) << " cm, ";
420 } else {
421 std::cout << " x = " << m_coplan[i] << " cm, ";
422 }
423 if (fabs(m_vtplan[i]) > 1.e-4) {
424 std::cout << "potential = " << m_vtplan[i] << " V, ";
425 } else {
426 std::cout << "earthed, ";
427 }
428 const auto& plane = m_planes[i];
429 if (plane.type.empty() && plane.type != "?") {
430 std::cout << "label = " << plane.type << ", ";
431 }
432 const unsigned int nStrips = plane.strips1.size() + plane.strips2.size();
433 const unsigned int nPixels = plane.pixels.size();
434 if (nStrips == 0 && nPixels == 0) {
435 std::cout << "no strips or pixels.\n";
436 } else if (nPixels == 0) {
437 std::cout << nStrips << " strips.\n";
438 } else if (nStrips == 0) {
439 std::cout << nPixels << " pixels.\n";
440 } else {
441 std::cout << nStrips << " strips, " << nPixels << " pixels.\n";
442 }
443 for (const auto& strip : plane.strips2) {
444 std::cout << " ";
445 if (m_polar) {
446 double gap = i == 0 ? expm1(strip.gap) : -expm1(-strip.gap);
447 gap *= exp(m_coplan[i]);
448 std::cout << RadToDegree * strip.smin << " < phi < "
449 << RadToDegree * strip.smax
450 << " degrees, gap = " << gap << " cm";
451 } else {
452 std::cout << strip.smin << " < y < " << strip.smax
453 << " cm, gap = " << strip.gap << " cm";
454 }
455 if (!strip.type.empty() && strip.type != "?") {
456 std::cout << " (\"" << strip.type << "\")";
457 }
458 std::cout << "\n";
459 }
460 for (const auto& strip : plane.strips1) {
461 std::cout << " " << strip.smin << " < z < " << strip.smax;
462 if (m_polar) {
463 double gap = i == 0 ? expm1(strip.gap) : -expm1(-strip.gap);
464 gap *= exp(m_coplan[i]);
465 std::cout << " cm, gap = " << gap << " cm";
466 } else {
467 std::cout << " cm, gap = " << strip.gap << " cm";
468 }
469 if (!strip.type.empty() && strip.type != "?") {
470 std::cout << " (\"" << strip.type << "\")";
471 }
472 std::cout << "\n";
473 }
474 for (const auto& pix : plane.pixels) {
475 std::cout << " ";
476 if (m_polar) {
477 std::cout << RadToDegree * pix.smin << " < phi < "
478 << RadToDegree * pix.smax << " degrees, ";
479 } else {
480 std::cout << pix.smin << " < y < " << pix.smax << " cm, ";
481 }
482 std::cout << pix.zmin << " < z < " << pix.zmax << " cm, gap = ";
483 if (m_polar) {
484 double gap = i == 0 ? expm1(pix.gap) : -expm1(-pix.gap);
485 gap *= exp(m_coplan[i]);
486 std::cout << gap << " cm";
487 } else {
488 std::cout << pix.gap << " cm";
489 }
490 if (!pix.type.empty() && pix.type != "?") {
491 std::cout << " (\"" << pix.type << "\")";
492 }
493 std::cout << "\n";
494 }
495 }
496 // Next the planes at constant y or phi
497 const std::string yphi = m_polar ? "phi" : "y";
498 if (m_ynplan[2] && m_ynplan[3]) {
499 std::cout << " There are two planes at constant " << yphi << ":\n";
500 } else if (m_ynplan[2] || m_ynplan[3]) {
501 std::cout << " There is one plane at constant " << yphi << ":\n";
502 }
503 for (unsigned int i = 2; i < 4; ++i) {
504 if (!m_ynplan[i]) continue;
505 if (m_polar) {
506 std::cout << " phi = " << RadToDegree * m_coplan[i] << " degrees, ";
507 } else {
508 std::cout << " y = " << m_coplan[i] << " cm, ";
509 }
510 if (fabs(m_vtplan[i]) > 1.e-4) {
511 std::cout << "potential = " << m_vtplan[i] << " V, ";
512 } else {
513 std::cout << "earthed, ";
514 }
515 const auto& plane = m_planes[i];
516 if (plane.type.empty() && plane.type != "?") {
517 std::cout << "label = " << plane.type << ", ";
518 }
519 const unsigned int nStrips = plane.strips1.size() + plane.strips2.size();
520 const unsigned int nPixels = plane.pixels.size();
521 if (nStrips == 0 && nPixels == 0) {
522 std::cout << "no strips or pixels.\n";
523 } else if (nPixels == 0) {
524 std::cout << nStrips << " strips.\n";
525 } else if (nStrips == 0) {
526 std::cout << nPixels << " pixels.\n";
527 } else {
528 std::cout << nStrips << " strips, " << nPixels << " pixels.\n";
529 }
530 for (const auto& strip : plane.strips2) {
531 std::cout << " ";
532 if (m_polar) {
533 std::cout << exp(strip.smin) << " < r < " << exp(strip.smax)
534 << " cm, gap = " << RadToDegree * strip.gap << " degrees";
535 } else {
536 std::cout << strip.smin << " < x < " << strip.smax
537 << " cm, gap = " << strip.gap << " cm";
538 }
539 if (!strip.type.empty() && strip.type != "?") {
540 std::cout << " (\"" << strip.type << "\")";
541 }
542 std::cout << "\n";
543 }
544 for (const auto& strip : plane.strips1) {
545 std::cout << " " << strip.smin << " < z < " << strip.smax;
546 if (m_polar) {
547 std::cout << " cm, gap = " << RadToDegree * strip.gap << " degrees";
548 } else {
549 std::cout << " cm, gap = " << strip.gap << " cm";
550 }
551 if (!strip.type.empty() && strip.type != "?") {
552 std::cout << " (\"" << strip.type << "\")";
553 }
554 std::cout << "\n";
555 }
556 for (const auto& pix : plane.pixels) {
557 std::cout << " ";
558 if (m_polar) {
559 std::cout << exp(pix.smin) << " < r < " << exp(pix.smax) << " cm, ";
560 } else {
561 std::cout << pix.smin << " < x < " << pix.smax << " cm, ";
562 }
563 std::cout << pix.zmin << " < z < " << pix.zmax << " cm, gap = ";
564 if (m_polar) {
565 std::cout << RadToDegree * pix.gap << " degrees";
566 } else {
567 std::cout << pix.gap << " cm";
568 }
569 if (!pix.type.empty() && pix.type != "?") {
570 std::cout << " (\"" << pix.type << "\")";
571 }
572 std::cout << "\n";
573 }
574 }
575 }
576 // Print the type of periodicity.
577 std::cout << " Periodicity\n";
578 if (m_perx) {
579 std::cout << " The cell is repeated every ";
580 if (m_polar) {
581 std::cout << exp(m_sx) << " cm in r.\n";
582 } else {
583 std::cout << m_sx << " cm in x.\n";
584 }
585 } else {
586 if (m_polar) {
587 std::cout << " The cell is not periodic in r.\n";
588 } else {
589 std::cout << " The cell has no translation periodicity in x.\n";
590 }
591 }
592 if (m_pery) {
593 std::cout << " The cell is repeated every ";
594 if (m_polar) {
595 std::cout << RadToDegree * m_sy << " degrees in phi.\n";
596 } else {
597 std::cout << m_sy << " cm in y.\n";
598 }
599 } else {
600 if (m_polar) {
601 std::cout << " The cell is not periodic in phi.\n";
602 } else {
603 std::cout << " The cell has no translation periodicity in y.\n";
604 }
605 }
606 std::cout << " Other data\n";
607 std::cout << " Gravity vector: (" << m_down[0] << ", " << m_down[1]
608 << ", " << m_down[2] << ") [g].\n";
609 std::cout << " Cell dimensions:\n ";
610 if (!m_polar) {
611 std::cout << m_xmin << " < x < " << m_xmax << " cm, " << m_ymin << " < y < "
612 << m_ymax << " cm.\n";
613 } else {
614 double xminp, yminp;
615 Internal2Polar(m_xmin, m_ymin, xminp, yminp);
616 double xmaxp, ymaxp;
617 Internal2Polar(m_xmax, m_ymax, xmaxp, ymaxp);
618 std::cout << xminp << " < r < " << xmaxp << " cm, " << yminp << " < phi < "
619 << ymaxp << " degrees.\n";
620 }
621 std::cout << " Potential range:\n " << m_vmin << " < V < " << m_vmax
622 << " V.\n";
623 // Print voltage shift in case no equipotential planes are present.
624 if (!(m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3] || m_tube)) {
625 std::cout << " All voltages have been shifted by " << m_v0
626 << " V to avoid net wire charge.\n";
627 } else {
628 // Print the net charge on wires.
629 double sum = 0.;
630 for (const auto& w : m_w) sum += w.e;
631 std::cout << " The net charge on the wires is "
632 << 1.e-3 * TwoPiEpsilon0 * sum << " pC/cm.\n";
633 }
634}
635
636bool ComponentAnalyticField::IsWireCrossed(const double xx0, const double yy0,
637 const double z0, const double xx1,
638 const double yy1, const double z1,
639 double& xc, double& yc, double& zc,
640 const bool centre, double& rc) {
641 xc = xx0;
642 yc = yy0;
643 zc = z0;
644
645 if (m_w.empty()) return false;
646
647 double x0 = xx0;
648 double y0 = yy0;
649 double x1 = xx1;
650 double y1 = yy1;
651 if (m_polar) {
652 Cartesian2Internal(xx0, yy0, x0, y0);
653 Cartesian2Internal(xx1, yy1, x1, y1);
654 }
655 const double dx = x1 - x0;
656 const double dy = y1 - y0;
657 const double d2 = dx * dx + dy * dy;
658 // Check that the step length is non-zero.
659 if (d2 < Small) return false;
660 const double invd2 = 1. / d2;
661
662 // Check if a whole period has been crossed.
663 if ((m_perx && fabs(dx) >= m_sx) || (m_pery && fabs(dy) >= m_sy)) {
664 std::cerr << m_className << "::IsWireCrossed:\n"
665 << " Particle crossed more than one period.\n";
666 return false;
667 }
668
669 // Both coordinates are assumed to be located inside
670 // the drift area and inside a drift medium.
671 // This should have been checked before this call.
672
673 const double xm = 0.5 * (x0 + x1);
674 const double ym = 0.5 * (y0 + y1);
675 double dMin2 = 0.;
676 for (const auto& wire : m_w) {
677 double xw = wire.x;
678 if (m_perx) {
679 xw += m_sx * int(round((xm - xw) / m_sx));
680 }
681 double yw = wire.y;
682 if (m_pery) {
683 yw += m_sy * int(round((ym - yw) / m_sy));
684 }
685 // Calculate the smallest distance between track and wire.
686 const double xIn0 = dx * (xw - x0) + dy * (yw - y0);
687 // Check if the minimum is located before (x0, y0).
688 if (xIn0 < 0.) continue;
689 const double xIn1 = -(dx * (xw - x1) + dy * (yw - y1));
690 // Check if the minimum is located behind (x1, y1).
691 if (xIn1 < 0.) continue;
692 // Minimum is located between (x0, y0) and (x1, y1).
693 const double xw0 = xw - x0;
694 const double xw1 = xw - x1;
695 const double yw0 = yw - y0;
696 const double yw1 = yw - y1;
697 const double dw02 = xw0 * xw0 + yw0 * yw0;
698 const double dw12 = xw1 * xw1 + yw1 * yw1;
699 if (xIn1 * xIn1 * dw02 > xIn0 * xIn0 * dw12) {
700 dMin2 = dw02 - xIn0 * xIn0 * invd2;
701 } else {
702 dMin2 = dw12 - xIn1 * xIn1 * invd2;
703 }
704 // Add in the times nTrap to account for the trap radius.
705 const double r2 = wire.r * wire.r;
706 if (dMin2 < r2) {
707 // Wire has been crossed.
708 if (centre) {
709 if (m_polar) {
710 Internal2Cartesian(xw, yw, xc, yc);
711 } else {
712 xc = xw;
713 yc = yw;
714 }
715 } else {
716 // Find the point of intersection.
717 const double p = -xIn0 * invd2;
718 const double q = (dw02 - r2) * invd2;
719 const double s = sqrt(p * p - q);
720 const double t = std::min(-p + s, -p - s);
721 if (m_polar) {
722 Internal2Cartesian(x0 + t * dx, y0 + t * dy, xc, yc);
723 } else {
724 xc = x0 + t * dx;
725 yc = y0 + t * dy;
726 }
727 zc = z0 + t * (z1 - z0);
728 }
729 rc = wire.r;
730 if (m_polar) rc *= exp(wire.x);
731 return true;
732 }
733 }
734 return false;
735}
736
737bool ComponentAnalyticField::IsInTrapRadius(const double qin, const double xin,
738 const double yin, const double zin,
739 double& xw, double& yw,
740 double& rw) {
741
742 double x0 = xin;
743 double y0 = yin;
744 if (m_polar) {
745 Cartesian2Internal(xin, yin, x0, y0);
746 }
747 // In case of periodicity, move the point into the basic cell.
748 int nX = 0, nY = 0, nPhi = 0;
749 if (m_perx) {
750 nX = int(round(x0 / m_sx));
751 x0 -= m_sx * nX;
752 }
753 if (m_pery && m_tube) {
754 Cartesian2Polar(xin, yin, x0, y0);
755 nPhi = int(round(DegreeToRad * y0 / m_sy));
756 y0 -= RadToDegree * m_sy * nPhi;
757 Polar2Cartesian(x0, y0, x0, y0);
758 } else if (m_pery) {
759 nY = int(round(y0 / m_sy));
760 y0 -= m_sy * nY;
761 }
762 // Move the point to the correct side of the plane.
763 std::array<bool, 4> shift = {false, false, false, false};
764 if (m_perx && m_ynplan[0] && x0 <= m_coplan[0]) {
765 x0 += m_sx;
766 shift[0] = true;
767 }
768 if (m_perx && m_ynplan[1] && x0 >= m_coplan[1]) {
769 x0 -= m_sx;
770 shift[1] = true;
771 }
772 if (m_pery && m_ynplan[2] && y0 <= m_coplan[2]) {
773 y0 += m_sy;
774 shift[2] = true;
775 }
776 if (m_pery && m_ynplan[3] && y0 >= m_coplan[3]) {
777 y0 -= m_sy;
778 shift[3] = true;
779 }
780
781 for (const auto& wire : m_w) {
782 // Skip wires with the wrong charge.
783 if (qin * wire.e > 0.) continue;
784 const double dxw0 = wire.x - x0;
785 const double dyw0 = wire.y - y0;
786 const double r2 = dxw0 * dxw0 + dyw0 * dyw0;
787 const double rTrap = wire.r * wire.nTrap;
788 if (r2 < rTrap * rTrap) {
789 xw = wire.x;
790 yw = wire.y;
791 rw = wire.r;
792 if (shift[0]) xw -= m_sx;
793 if (shift[1]) xw += m_sx;
794 if (shift[2]) yw -= m_sy;
795 if (shift[3]) yw += m_sy;
796 if (m_pery && m_tube) {
797 double rhow, phiw;
798 Cartesian2Polar(xw, yw, rhow, phiw);
799 phiw += RadToDegree * m_sy * nPhi;
800 Polar2Cartesian(rhow, phiw, xw, yw);
801 } else if (m_pery) {
802 y0 += m_sy * nY;
803 }
804 if (m_perx) xw += m_sx * nX;
805 if (m_polar) {
806 Internal2Cartesian(xw, yw, xw, yw);
807 rw *= exp(wire.x);
808 }
809 if (m_debug) {
810 std::cout << m_className << "::IsInTrapRadius: (" << xin << ", "
811 << yin << ", " << zin << ")" << " within trap radius.\n";
812 }
813 return true;
814 }
815 }
816
817 return false;
818}
819
820void ComponentAnalyticField::AddWire(const double x, const double y,
821 const double diameter,
822 const double voltage,
823 const std::string& label,
824 const double length, const double tension,
825 double rho, const int ntrap) {
826 // Check if the provided parameters make sense.
827 if (diameter <= 0.) {
828 std::cerr << m_className << "::AddWire: Unphysical wire diameter.\n";
829 return;
830 }
831
832 if (tension <= 0.) {
833 std::cerr << m_className << "::AddWire: Unphysical wire tension.\n";
834 return;
835 }
836
837 if (rho <= 0.) {
838 std::cerr << m_className << "::AddWire: Unphysical wire density.\n";
839 return;
840 }
841
842 if (length <= 0.) {
843 std::cerr << m_className << "::AddWire: Unphysical wire length.\n";
844 return;
845 }
846
847 if (ntrap <= 0) {
848 std::cerr << m_className << "::AddWire: Nbr. of trap radii must be > 0.\n";
849 return;
850 }
851
852 if (m_polar && x <= diameter) {
853 std::cerr << m_className << "::AddWire: Wire is too close to the origin.\n";
854 return;
855 }
856
857 // Create a new wire.
858 Wire wire;
859 if (m_polar) {
860 double r = 0., phi = 0.;
861 Polar2Internal(x, y, r, phi);
862 wire.x = r;
863 wire.y = phi;
864 wire.r = 0.5 * diameter / x;
865 } else {
866 wire.x = x;
867 wire.y = y;
868 wire.r = 0.5 * diameter;
869 }
870 wire.v = voltage;
871 wire.u = length;
872 wire.type = label;
873 wire.e = 0.;
874 wire.ind = -1;
875 wire.nTrap = ntrap;
876 wire.tension = tension;
877 wire.density = rho;
878 // Add the wire to the list
879 m_w.push_back(std::move(wire));
880 ++m_nWires;
881
882 // Force recalculation of the capacitance and signal matrices.
883 m_cellset = false;
884 m_sigset = false;
885}
886
887void ComponentAnalyticField::AddTube(const double radius, const double voltage,
888 const int nEdges,
889 const std::string& label) {
890 // Check if the provided parameters make sense.
891 if (radius <= 0.0) {
892 std::cerr << m_className << "::AddTube: Unphysical tube dimension.\n";
893 return;
894 }
895
896 if (nEdges < 3 && nEdges != 0) {
897 std::cerr << m_className << "::AddTube: Unphysical number of tube edges ("
898 << nEdges << ")\n";
899 return;
900 }
901
902 // If there is already a tube defined, print a warning message.
903 if (m_tube) {
904 std::cout << m_className << "::AddTube:\n"
905 << " Warning: Existing tube settings will be overwritten.\n";
906 }
907
908 // Set the coordinate system.
909 m_tube = true;
910 m_polar = false;
911
912 // Set the tube parameters.
913 m_cotube = radius;
914 m_cotube2 = radius * radius;
915 m_vttube = voltage;
916
917 m_ntube = nEdges;
918
919 m_planes[4].type = label;
920 m_planes[4].ind = -1;
921
922 // Force recalculation of the capacitance and signal matrices.
923 m_cellset = false;
924 m_sigset = false;
925}
926
927void ComponentAnalyticField::AddPlaneX(const double x, const double v,
928 const std::string& label) {
929 if (m_polar) {
930 std::cerr << m_className << "::AddPlaneX:\n"
931 << " Not compatible with polar coordinates; ignored.\n";
932 return;
933 }
934 if (m_ynplan[0] && m_ynplan[1]) {
935 std::cerr << m_className << "::AddPlaneX:\n"
936 << " Cannot have more than two planes at constant x.\n";
937 return;
938 }
939
940 if (m_ynplan[0]) {
941 m_ynplan[1] = true;
942 m_coplan[1] = x;
943 m_vtplan[1] = v;
944 m_planes[1].type = label;
945 m_planes[1].ind = -1;
946 } else {
947 m_ynplan[0] = true;
948 m_coplan[0] = x;
949 m_vtplan[0] = v;
950 m_planes[0].type = label;
951 m_planes[0].ind = -1;
952 }
953
954 // Force recalculation of the capacitance and signal matrices.
955 m_cellset = false;
956 m_sigset = false;
957}
958
959void ComponentAnalyticField::AddPlaneY(const double y, const double v,
960 const std::string& label) {
961 if (m_polar) {
962 std::cerr << m_className << "::AddPlaneY:\n"
963 << " Not compatible with polar coordinates; ignored.\n";
964 return;
965 }
966 if (m_ynplan[2] && m_ynplan[3]) {
967 std::cerr << m_className << "::AddPlaneY:\n"
968 << " Cannot have more than two planes at constant y.\n";
969 return;
970 }
971
972 if (m_ynplan[2]) {
973 m_ynplan[3] = true;
974 m_coplan[3] = y;
975 m_vtplan[3] = v;
976 m_planes[3].type = label;
977 m_planes[3].ind = -1;
978 } else {
979 m_ynplan[2] = true;
980 m_coplan[2] = y;
981 m_vtplan[2] = v;
982 m_planes[2].type = label;
983 m_planes[2].ind = -1;
984 }
985
986 // Force recalculation of the capacitance and signal matrices.
987 m_cellset = false;
988 m_sigset = false;
989}
990
991void ComponentAnalyticField::AddPlaneR(const double r, const double v,
992 const std::string& label) {
993 if (!m_polar) {
994 std::cerr << m_className << "::AddPlaneR:\n"
995 << " Not compatible with Cartesian coordinates; ignored.\n";
996 return;
997 }
998 if (r <= 0.) {
999 std::cerr << m_className << "::AddPlaneR:\n"
1000 << " Radius must be larger than zero; plane ignored.\n";
1001 return;
1002 }
1003
1004 if (m_ynplan[0] && m_ynplan[1]) {
1005 std::cerr << m_className << "::AddPlaneR:\n"
1006 << " Cannot have more than two circular planes.\n";
1007 return;
1008 }
1009
1010 if (m_ynplan[0]) {
1011 m_ynplan[1] = true;
1012 m_coplan[1] = log(r);
1013 m_vtplan[1] = v;
1014 m_planes[1].type = label;
1015 m_planes[1].ind = -1;
1016 } else {
1017 m_ynplan[0] = true;
1018 m_coplan[0] = log(r);
1019 m_vtplan[0] = v;
1020 m_planes[0].type = label;
1021 m_planes[0].ind = -1;
1022 }
1023
1024 // Force recalculation of the capacitance and signal matrices.
1025 m_cellset = false;
1026 m_sigset = false;
1027}
1028
1029void ComponentAnalyticField::AddPlanePhi(const double phi, const double v,
1030 const std::string& label) {
1031 if (!m_polar) {
1032 std::cerr << m_className << "::AddPlanePhi:\n"
1033 << " Not compatible with Cartesian coordinates; ignored.\n";
1034 return;
1035 }
1036 if (m_ynplan[2] && m_ynplan[3]) {
1037 std::cerr << m_className << "::AddPlanePhi:\n"
1038 << " Cannot have more than two planes at constant phi.\n";
1039 return;
1040 }
1041
1042 if (m_ynplan[2]) {
1043 m_ynplan[3] = true;
1044 m_coplan[3] = phi * DegreeToRad;
1045 m_vtplan[3] = v;
1046 m_planes[3].type = label;
1047 m_planes[3].ind = -1;
1048 } else {
1049 m_ynplan[2] = true;
1050 m_coplan[2] = phi * DegreeToRad;
1051 m_vtplan[2] = v;
1052 m_planes[2].type = label;
1053 m_planes[2].ind = -1;
1054 // Switch off default periodicity.
1055 if (m_pery && std::abs(m_sy - TwoPi) < 1.e-4) {
1056 m_pery = false;
1057 }
1058 }
1059
1060 // Force recalculation of the capacitance and signal matrices.
1061 m_cellset = false;
1062 m_sigset = false;
1063}
1064
1065void ComponentAnalyticField::AddStripOnPlaneX(const char dir, const double x,
1066 const double smin,
1067 const double smax,
1068 const std::string& label,
1069 const double gap) {
1070 if (m_polar || (!m_ynplan[0] && !m_ynplan[1])) {
1071 std::cerr << m_className << "::AddStripOnPlaneX:\n"
1072 << " There are no planes at constant x.\n";
1073 return;
1074 }
1075
1076 if (dir != 'y' && dir != 'Y' && dir != 'z' && dir != 'Z') {
1077 std::cerr << m_className << "::AddStripOnPlaneX:\n"
1078 << " Invalid direction (" << dir << ").\n"
1079 << " Only strips in y or z direction are possible.\n";
1080 return;
1081 }
1082
1083 if (fabs(smax - smin) < Small) {
1084 std::cerr << m_className << "::AddStripOnPlaneX:\n"
1085 << " Strip width must be greater than zero.\n";
1086 return;
1087 }
1088
1089 Strip newStrip;
1090 newStrip.type = label;
1091 newStrip.ind = -1;
1092 newStrip.smin = std::min(smin, smax);
1093 newStrip.smax = std::max(smin, smax);
1094 newStrip.gap = gap > Small ? gap : -1.;
1095
1096 int iplane = 0;
1097 if (m_ynplan[1]) {
1098 const double d0 = fabs(m_coplan[0] - x);
1099 const double d1 = fabs(m_coplan[1] - x);
1100 if (d1 < d0) iplane = 1;
1101 }
1102
1103 if (dir == 'y' || dir == 'Y') {
1104 m_planes[iplane].strips1.push_back(std::move(newStrip));
1105 } else {
1106 m_planes[iplane].strips2.push_back(std::move(newStrip));
1107 }
1108}
1109
1110void ComponentAnalyticField::AddStripOnPlaneY(const char dir, const double y,
1111 const double smin,
1112 const double smax,
1113 const std::string& label,
1114 const double gap) {
1115 if (m_polar || (!m_ynplan[2] && !m_ynplan[3])) {
1116 std::cerr << m_className << "::AddStripOnPlaneY:\n"
1117 << " There are no planes at constant y.\n";
1118 return;
1119 }
1120
1121 if (dir != 'x' && dir != 'X' && dir != 'z' && dir != 'Z') {
1122 std::cerr << m_className << "::AddStripOnPlaneY:\n"
1123 << " Invalid direction (" << dir << ").\n"
1124 << " Only strips in x or z direction are possible.\n";
1125 return;
1126 }
1127
1128 if (fabs(smax - smin) < Small) {
1129 std::cerr << m_className << "::AddStripOnPlaneY:\n"
1130 << " Strip width must be greater than zero.\n";
1131 return;
1132 }
1133
1134 Strip newStrip;
1135 newStrip.type = label;
1136 newStrip.ind = -1;
1137 newStrip.smin = std::min(smin, smax);
1138 newStrip.smax = std::max(smin, smax);
1139 newStrip.gap = gap > Small ? gap : -1.;
1140
1141 int iplane = 2;
1142 if (m_ynplan[3]) {
1143 const double d2 = fabs(m_coplan[2] - y);
1144 const double d3 = fabs(m_coplan[3] - y);
1145 if (d3 < d2) iplane = 3;
1146 }
1147
1148 if (dir == 'x' || dir == 'X') {
1149 m_planes[iplane].strips1.push_back(std::move(newStrip));
1150 } else {
1151 m_planes[iplane].strips2.push_back(std::move(newStrip));
1152 }
1153}
1154
1155void ComponentAnalyticField::AddStripOnPlaneR(const char dir, const double r,
1156 const double smin,
1157 const double smax,
1158 const std::string& label,
1159 const double gap) {
1160 if (!m_polar || (!m_ynplan[0] && !m_ynplan[1])) {
1161 std::cerr << m_className << "::AddStripOnPlaneR:\n"
1162 << " There are no planes at constant r.\n";
1163 return;
1164 }
1165
1166 if (dir != 'p' && dir != 'P' && dir != 'z' && dir != 'Z') {
1167 std::cerr << m_className << "::AddStripOnPlaneR:\n"
1168 << " Invalid direction (" << dir << ").\n"
1169 << " Only strips in p(hi) or z direction are possible.\n";
1170 return;
1171 }
1172
1173 if (fabs(smax - smin) < Small) {
1174 std::cerr << m_className << "::AddStripOnPlaneR:\n"
1175 << " Strip width must be greater than zero.\n";
1176 return;
1177 }
1178
1179 Strip newStrip;
1180 newStrip.type = label;
1181 newStrip.ind = -1;
1182 if (dir == 'z' || dir == 'Z') {
1183 const double phimin = smin * DegreeToRad;
1184 const double phimax = smax * DegreeToRad;
1185 newStrip.smin = std::min(phimin, phimax);
1186 newStrip.smax = std::max(phimin, phimax);
1187 } else {
1188 newStrip.smin = std::min(smin, smax);
1189 newStrip.smax = std::max(smin, smax);
1190 }
1191 newStrip.gap = gap > Small ? gap : -1.;
1192
1193 int iplane = 0;
1194 if (m_ynplan[1]) {
1195 const double rho = r > 0. ? log(r) : -25.;
1196 const double d0 = fabs(m_coplan[0] - rho);
1197 const double d1 = fabs(m_coplan[1] - rho);
1198 if (d1 < d0) iplane = 1;
1199 }
1200
1201 if (dir == 'p' || dir == 'P') {
1202 m_planes[iplane].strips1.push_back(std::move(newStrip));
1203 } else {
1204 m_planes[iplane].strips2.push_back(std::move(newStrip));
1205 }
1206}
1207
1209 const double phi,
1210 const double smin,
1211 const double smax,
1212 const std::string& label,
1213 const double gap) {
1214 if (!m_polar || (!m_ynplan[2] && !m_ynplan[3])) {
1215 std::cerr << m_className << "::AddStripOnPlanePhi:\n"
1216 << " There are no planes at constant phi.\n";
1217 return;
1218 }
1219
1220 if (dir != 'r' && dir != 'R' && dir != 'z' && dir != 'Z') {
1221 std::cerr << m_className << "::AddStripOnPlanePhi:\n"
1222 << " Invalid direction (" << dir << ").\n"
1223 << " Only strips in r or z direction are possible.\n";
1224 return;
1225 }
1226
1227 if (fabs(smax - smin) < Small) {
1228 std::cerr << m_className << "::AddStripOnPlanePhi:\n"
1229 << " Strip width must be greater than zero.\n";
1230 return;
1231 }
1232
1233 Strip newStrip;
1234 newStrip.type = label;
1235 newStrip.ind = -1;
1236 if (dir== 'z' || dir == 'Z') {
1237 if (smin < Small || smax < Small) {
1238 std::cerr << m_className << "::AddStripOnPlanePhi:\n"
1239 << " Radius must be greater than zero.\n";
1240 return;
1241 }
1242 const double rhomin = log(smin);
1243 const double rhomax = log(smax);
1244 newStrip.smin = std::min(rhomin, rhomax);
1245 newStrip.smax = std::max(rhomin, rhomax);
1246 } else {
1247 newStrip.smin = std::min(smin, smax);
1248 newStrip.smax = std::max(smin, smax);
1249 }
1250 newStrip.gap = gap > Small ? DegreeToRad * gap : -1.;
1251
1252 int iplane = 2;
1253 if (m_ynplan[3]) {
1254 const double d2 = fabs(m_coplan[2] - phi * DegreeToRad);
1255 const double d3 = fabs(m_coplan[3] - phi * DegreeToRad);
1256 if (d3 < d2) iplane = 3;
1257 }
1258
1259 if (dir == 'r' || dir == 'R') {
1260 m_planes[iplane].strips1.push_back(std::move(newStrip));
1261 } else {
1262 m_planes[iplane].strips2.push_back(std::move(newStrip));
1263 }
1264}
1265
1266
1268 const double x, const double ymin, const double ymax, const double zmin,
1269 const double zmax, const std::string& label, const double gap) {
1270 if (m_polar || (!m_ynplan[0] && !m_ynplan[1])) {
1271 std::cerr << m_className << "::AddPixelOnPlaneX:\n"
1272 << " There are no planes at constant x.\n";
1273 return;
1274 }
1275
1276 if (fabs(ymax - ymin) < Small || fabs(zmax - zmin) < Small) {
1277 std::cerr << m_className << "::AddPixelOnPlaneX:\n"
1278 << " Pixel width must be greater than zero.\n";
1279 return;
1280 }
1281
1282 Pixel newPixel;
1283 newPixel.type = label;
1284 newPixel.ind = -1;
1285 newPixel.smin = std::min(ymin, ymax);
1286 newPixel.smax = std::max(ymin, ymax);
1287 newPixel.zmin = std::min(zmin, zmax);
1288 newPixel.zmax = std::max(zmin, zmax);
1289 newPixel.gap = gap > Small ? gap : -1.;
1290
1291 int iplane = 0;
1292 if (m_ynplan[1]) {
1293 const double d0 = fabs(m_coplan[0] - x);
1294 const double d1 = fabs(m_coplan[1] - x);
1295 if (d1 < d0) iplane = 1;
1296 }
1297
1298 m_planes[iplane].pixels.push_back(std::move(newPixel));
1299}
1300
1302 const double y, const double xmin, const double xmax, const double zmin,
1303 const double zmax, const std::string& label, const double gap) {
1304 if (m_polar || (!m_ynplan[2] && !m_ynplan[3])) {
1305 std::cerr << m_className << "::AddPixelOnPlaneY:\n"
1306 << " There are no planes at constant y.\n";
1307 return;
1308 }
1309
1310 if (fabs(xmax - xmin) < Small || fabs(zmax - zmin) < Small) {
1311 std::cerr << m_className << "::AddPixelOnPlaneY:\n"
1312 << " Pixel width must be greater than zero.\n";
1313 return;
1314 }
1315
1316 Pixel newPixel;
1317 newPixel.type = label;
1318 newPixel.ind = -1;
1319 newPixel.smin = std::min(xmin, xmax);
1320 newPixel.smax = std::max(xmin, xmax);
1321 newPixel.zmin = std::min(zmin, zmax);
1322 newPixel.zmax = std::max(zmin, zmax);
1323 newPixel.gap = gap > Small ? gap : -1.;
1324
1325 int iplane = 2;
1326 if (m_ynplan[3]) {
1327 const double d0 = fabs(m_coplan[2] - y);
1328 const double d1 = fabs(m_coplan[3] - y);
1329 if (d1 < d0) iplane = 3;
1330 }
1331
1332 m_planes[iplane].pixels.push_back(std::move(newPixel));
1333}
1334
1336 const double r, const double phimin, const double phimax,
1337 const double zmin, const double zmax, const std::string& label,
1338 const double gap) {
1339 if (!m_polar || (!m_ynplan[0] && !m_ynplan[1])) {
1340 std::cerr << m_className << "::AddPixelOnPlaneR:\n"
1341 << " There are no planes at constant r.\n";
1342 return;
1343 }
1344
1345 if (fabs(phimax - phimin) < Small || fabs(zmax - zmin) < Small) {
1346 std::cerr << m_className << "::AddPixelOnPlaneR:\n"
1347 << " Pixel width must be greater than zero.\n";
1348 return;
1349 }
1350
1351 Pixel newPixel;
1352 newPixel.type = label;
1353 newPixel.ind = -1;
1354 const double smin = phimin * DegreeToRad;
1355 const double smax = phimax * DegreeToRad;
1356 newPixel.smin = std::min(smin, smax);
1357 newPixel.smax = std::max(smin, smax);
1358 newPixel.zmin = std::min(zmin, zmax);
1359 newPixel.zmax = std::max(zmin, zmax);
1360 newPixel.gap = gap > Small ? gap : -1.;
1361
1362 int iplane = 0;
1363 if (m_ynplan[1]) {
1364 const double rho = r > 0. ? log(r) : -25.;
1365 const double d0 = fabs(m_coplan[0] - rho);
1366 const double d1 = fabs(m_coplan[1] - rho);
1367 if (d1 < d0) iplane = 1;
1368 }
1369
1370 m_planes[iplane].pixels.push_back(std::move(newPixel));
1371}
1372
1374 const double phi, const double rmin, const double rmax,
1375 const double zmin, const double zmax, const std::string& label,
1376 const double gap) {
1377 if (!m_polar || (!m_ynplan[2] && !m_ynplan[3])) {
1378 std::cerr << m_className << "::AddPixelOnPlanePhi:\n"
1379 << " There are no planes at constant phi.\n";
1380 return;
1381 }
1382
1383 if (fabs(rmax - rmin) < Small || fabs(zmax - zmin) < Small) {
1384 std::cerr << m_className << "::AddPixelOnPlanePhi:\n"
1385 << " Pixel width must be greater than zero.\n";
1386 return;
1387 }
1388 if (rmin < Small || rmax < Small) {
1389 std::cerr << m_className << "::AddPixelOnPlanePhi:\n"
1390 << " Radius must be greater than zero.\n";
1391 return;
1392 }
1393 Pixel newPixel;
1394 newPixel.type = label;
1395 newPixel.ind = -1;
1396 const double smin = log(rmin);
1397 const double smax = log(rmax);
1398 newPixel.smin = std::min(smin, smax);
1399 newPixel.smax = std::max(smin, smax);
1400 newPixel.zmin = std::min(zmin, zmax);
1401 newPixel.zmax = std::max(zmin, zmax);
1402 newPixel.gap = gap > Small ? DegreeToRad * gap : -1.;
1403
1404 int iplane = 2;
1405 if (m_ynplan[3]) {
1406 const double d0 = fabs(m_coplan[2] - phi * DegreeToRad);
1407 const double d1 = fabs(m_coplan[3] - phi * DegreeToRad);
1408 if (d1 < d0) iplane = 3;
1409 }
1410
1411 m_planes[iplane].pixels.push_back(std::move(newPixel));
1412}
1413
1415
1416 m_cellset = false;
1417 m_sigset = false;
1418 m_dipole = on;
1419}
1420
1422 if (m_polar) {
1423 std::cerr << m_className << "::SetPeriodicityX:\n"
1424 << " Cannot use x-periodicity with polar coordinates.\n";
1425 return;
1426 }
1427 if (s < Small) {
1428 std::cerr << m_className << "::SetPeriodicityX:\n"
1429 << " Periodic length must be greater than zero.\n";
1430 return;
1431 }
1432
1433 m_periodic[0] = true;
1434 m_sx = s;
1435 UpdatePeriodicity();
1436}
1437
1439 if (m_polar) {
1440 std::cerr << m_className << "::SetPeriodicityY:\n"
1441 << " Cannot use y-periodicity with polar coordinates.\n";
1442 return;
1443 }
1444 if (s < Small) {
1445 std::cerr << m_className << "::SetPeriodicityY:\n"
1446 << " Periodic length must be greater than zero.\n";
1447 return;
1448 }
1449
1450 m_periodic[1] = true;
1451 m_sy = s;
1452 UpdatePeriodicity();
1453}
1454
1456 if (!m_polar && !m_tube) {
1457 std::cerr << m_className << "::SetPeriodicityPhi:\n"
1458 << " Cannot use phi-periodicity with Cartesian coordinates.\n";
1459 return;
1460 }
1461 if (std::abs(360. - s * int(360. / s)) > 1.e-4) {
1462 std::cerr << m_className << "::SetPeriodicityPhi:\n"
1463 << " Phi periods must divide 360; ignored.\n";
1464 return;
1465 }
1466
1467 m_periodic[1] = true;
1468 m_sy = DegreeToRad * s;
1469 m_mtube = int(360. / s);
1470 UpdatePeriodicity();
1471}
1472
1474 if (!m_periodic[0] || m_polar) {
1475 s = 0.;
1476 return false;
1477 }
1478
1479 s = m_sx;
1480 return true;
1481}
1482
1484 if (!m_periodic[1] || m_polar) {
1485 s = 0.;
1486 return false;
1487 }
1488
1489 s = m_sy;
1490 return true;
1491}
1492
1494 if (!m_periodic[1] || (!m_polar && !m_tube)) {
1495 s = 0.;
1496 return false;
1497 }
1498
1499 s = RadToDegree * m_sy;
1500 return true;
1501}
1502
1503void ComponentAnalyticField::UpdatePeriodicity() {
1504 // Check if the settings have actually changed.
1505 if (m_perx && !m_periodic[0]) {
1506 m_perx = false;
1507 m_cellset = false;
1508 m_sigset = false;
1509 } else if (!m_perx && m_periodic[0]) {
1510 if (m_sx < Small) {
1511 std::cerr << m_className << "::UpdatePeriodicity:\n";
1512 std::cerr << " Periodicity in x direction was enabled"
1513 << " but periodic length is not set.\n";
1514 } else {
1515 m_perx = true;
1516 m_cellset = false;
1517 m_sigset = false;
1518 }
1519 }
1520
1521 if (m_pery && !m_periodic[1]) {
1522 m_pery = false;
1523 m_cellset = false;
1524 m_sigset = false;
1525 } else if (!m_pery && m_periodic[1]) {
1526 if (m_sy < Small) {
1527 std::cerr << m_className << "::UpdatePeriodicity:\n";
1528 std::cerr << " Periodicity in y direction was enabled"
1529 << " but periodic length is not set.\n";
1530 } else {
1531 m_pery = true;
1532 m_cellset = false;
1533 m_sigset = false;
1534 }
1535 }
1536
1537 // Check if symmetries other than x/y periodicity have been requested
1538 if (m_periodic[2]) {
1539 std::cerr << m_className << "::UpdatePeriodicity:\n"
1540 << " Periodicity in z is not possible.\n";
1541 }
1542
1544 std::cerr << m_className << "::UpdatePeriodicity:\n"
1545 << " Mirror periodicity is not possible.\n";
1546 }
1547
1549 std::cerr << m_className << "::UpdatePeriodicity:\n"
1550 << " Axial periodicity is not possible.\n";
1551 }
1552
1555 std::cerr << m_className << "::UpdatePeriodicity:\n"
1556 << " Rotation symmetry is not possible.\n";
1557 }
1558}
1559
1561 if (m_polar) {
1562 std::cout << m_className << "::SetCartesianCoordinates:\n "
1563 << "Switching to Cartesian coordinates; resetting the cell.\n";
1564 CellInit();
1565 }
1566 m_polar = false;
1567}
1568
1570 if (!m_polar) {
1571 std::cout << m_className << "::SetPolarCoordinates:\n "
1572 << "Switching to polar coordinates; resetting the cell.\n";
1573 CellInit();
1574 }
1575 m_polar = true;
1576 // Set default phi period.
1577 m_pery = true;
1578 m_sy = TwoPi;
1579}
1580
1581void ComponentAnalyticField::AddCharge(const double x, const double y,
1582 const double z, const double q) {
1583 // Convert from fC to internal units (division by 4 pi epsilon0).
1584 Charge3d charge;
1585 charge.x = x;
1586 charge.y = y;
1587 charge.z = z;
1588 charge.e = q / FourPiEpsilon0;
1589 m_ch3d.push_back(std::move(charge));
1590}
1591
1593 m_ch3d.clear();
1594 m_nTermBessel = 10;
1595 m_nTermPoly = 100;
1596}
1597
1599 std::cout << m_className << "::PrintCharges:\n";
1600 if (m_ch3d.empty()) {
1601 std::cout << " No charges present.\n";
1602 return;
1603 }
1604 std::cout << " x [cm] y [cm] z [cm] charge [fC]\n";
1605 for (const auto& charge : m_ch3d) {
1606 std::cout << " " << std::setw(9) << charge.x << " " << std::setw(9)
1607 << charge.y << " " << std::setw(9) << charge.z << " "
1608 << std::setw(11) << charge.e * FourPiEpsilon0 << "\n";
1609 }
1610}
1611
1613 if (m_polar) {
1614 return 0;
1615 } else if (m_ynplan[0] && m_ynplan[1]) {
1616 return 2;
1617 } else if (m_ynplan[0] || m_ynplan[1]) {
1618 return 1;
1619 }
1620 return 0;
1621}
1622
1624 if (m_polar) {
1625 return 0;
1626 } else if (m_ynplan[2] && m_ynplan[3]) {
1627 return 2;
1628 } else if (m_ynplan[2] || m_ynplan[3]) {
1629 return 1;
1630 }
1631 return 0;
1632}
1633
1635 if (!m_polar) {
1636 return 0;
1637 } else if (m_ynplan[0] && m_ynplan[1]) {
1638 return 2;
1639 } else if (m_ynplan[0] || m_ynplan[1]) {
1640 return 1;
1641 }
1642 return 0;
1643}
1644
1646 if (!m_polar) {
1647 return 0;
1648 } else if (m_ynplan[2] && m_ynplan[3]) {
1649 return 2;
1650 } else if (m_ynplan[2] || m_ynplan[3]) {
1651 return 1;
1652 }
1653 return 0;
1654}
1655
1656bool ComponentAnalyticField::GetWire(const unsigned int i, double& x, double& y,
1657 double& diameter, double& voltage,
1658 std::string& label, double& length,
1659 double& charge, int& ntrap) const {
1660 if (i >= m_nWires) {
1661 std::cerr << m_className << "::GetWire: Index out of range.\n";
1662 return false;
1663 }
1664
1665 if (m_polar) {
1666 double r = 0., theta = 0.;
1667 Internal2Polar(m_w[i].x, m_w[i].y, r, theta);
1668 x = r;
1669 y = theta;
1670 diameter = 2 * m_w[i].r * r;
1671 } else {
1672 x = m_w[i].x;
1673 y = m_w[i].y;
1674 diameter = 2 * m_w[i].r;
1675 }
1676 voltage = m_w[i].v;
1677 label = m_w[i].type;
1678 length = m_w[i].u;
1679 charge = m_w[i].e;
1680 ntrap = m_w[i].nTrap;
1681 return true;
1682}
1683
1684bool ComponentAnalyticField::GetPlaneX(const unsigned int i, double& x,
1685 double& voltage,
1686 std::string& label) const {
1687 if (m_polar || i >= 2 || (i == 1 && !m_ynplan[1])) {
1688 std::cerr << m_className << "::GetPlaneX: Index out of range.\n";
1689 return false;
1690 }
1691
1692 x = m_coplan[i];
1693 voltage = m_vtplan[i];
1694 label = m_planes[i].type;
1695 return true;
1696}
1697
1698bool ComponentAnalyticField::GetPlaneY(const unsigned int i, double& y,
1699 double& voltage,
1700 std::string& label) const {
1701 if (m_polar || i >= 2 || (i == 1 && !m_ynplan[3])) {
1702 std::cerr << m_className << "::GetPlaneY: Index out of range.\n";
1703 return false;
1704 }
1705
1706 y = m_coplan[i + 2];
1707 voltage = m_vtplan[i + 2];
1708 label = m_planes[i + 2].type;
1709 return true;
1710}
1711
1712bool ComponentAnalyticField::GetPlaneR(const unsigned int i, double& r,
1713 double& voltage,
1714 std::string& label) const {
1715 if (!m_polar || i >= 2 || (i == 1 && !m_ynplan[1])) {
1716 std::cerr << m_className << "::GetPlaneR: Index out of range.\n";
1717 return false;
1718 }
1719
1720 r = exp(m_coplan[i]);
1721 voltage = m_vtplan[i];
1722 label = m_planes[i].type;
1723 return true;
1724}
1725
1726bool ComponentAnalyticField::GetPlanePhi(const unsigned int i, double& phi,
1727 double& voltage,
1728 std::string& label) const {
1729 if (!m_polar || i >= 2 || (i == 1 && !m_ynplan[3])) {
1730 std::cerr << m_className << "::GetPlanePhi: Index out of range.\n";
1731 return false;
1732 }
1733
1734 phi = RadToDegree * m_coplan[i + 2];
1735 voltage = m_vtplan[i + 2];
1736 label = m_planes[i + 2].type;
1737 return true;
1738}
1739
1740bool ComponentAnalyticField::GetTube(double& r, double& voltage, int& nEdges,
1741 std::string& label) const {
1742 if (!m_tube) return false;
1743 r = m_cotube;
1744 voltage = m_vttube;
1745 nEdges = m_ntube;
1746 label = m_planes[4].type;
1747 return true;
1748}
1749
1751 double& ex, double& ey) {
1752 //-----------------------------------------------------------------------
1753 // FFIELD - Subroutine calculating the electric field at a given wire
1754 // position, as if the wire itself were not there but with
1755 // the presence of its mirror images.
1756 // VARIABLES : IW : wire number
1757 // EX, EY : x- and y-component of the electric field.
1758 // (Last changed on 27/ 1/96.)
1759 //-----------------------------------------------------------------------
1760 ex = ey = 0.;
1761 // Check the wire number.
1762 if (iw >= m_nWires) {
1763 std::cerr << m_className << "::ElectricFieldAtWire: Index out of range.\n";
1764 return false;
1765 }
1766 // Set the flags appropriately.
1767 std::vector<bool> cnalso(m_nWires, true);
1768 cnalso[iw] = false;
1769
1770 const double xpos = m_w[iw].x;
1771 const double ypos = m_w[iw].y;
1772 // Call the appropriate function.
1773 switch (m_cellType) {
1774 case A00:
1775 FieldAtWireA00(xpos, ypos, ex, ey, cnalso);
1776 break;
1777 case B1X:
1778 FieldAtWireB1X(xpos, ypos, ex, ey, cnalso);
1779 break;
1780 case B1Y:
1781 FieldAtWireB1Y(xpos, ypos, ex, ey, cnalso);
1782 break;
1783 case B2X:
1784 FieldAtWireB2X(xpos, ypos, ex, ey, cnalso);
1785 break;
1786 case B2Y:
1787 FieldAtWireB2Y(xpos, ypos, ex, ey, cnalso);
1788 break;
1789 case C10:
1790 FieldAtWireC10(xpos, ypos, ex, ey, cnalso);
1791 break;
1792 case C2X:
1793 FieldAtWireC2X(xpos, ypos, ex, ey, cnalso);
1794 break;
1795 case C2Y:
1796 FieldAtWireC2Y(xpos, ypos, ex, ey, cnalso);
1797 break;
1798 case C30:
1799 FieldAtWireC30(xpos, ypos, ex, ey, cnalso);
1800 break;
1801 case D10:
1802 FieldAtWireD10(xpos, ypos, ex, ey, cnalso);
1803 break;
1804 case D20:
1805 FieldAtWireD20(xpos, ypos, ex, ey, cnalso);
1806 break;
1807 case D30:
1808 FieldAtWireD30(xpos, ypos, ex, ey, cnalso);
1809 break;
1810 default:
1811 std::cerr << m_className << "::ElectricFieldAtWire:\n"
1812 << " Unknown cell type (id " << m_cellType << ")\n";
1813 return false;
1814 break;
1815 }
1816 // Correct for the equipotential planes.
1817 ex -= m_corvta;
1818 ey -= m_corvtb;
1819 if (m_polar) {
1820 const double r = exp(xpos);
1821 const double er = ex / r;
1822 const double ep = ey / r;
1823 const double ct = cos(ypos);
1824 const double st = sin(ypos);
1825 ex = +ct * er - st * ep;
1826 ey = +st * er + ct * ep;
1827 }
1828 return true;
1829}
1830
1832 const unsigned int nY) {
1833 if (nX < 2) {
1834 std::cerr << m_className << "::SetScanningGrid:\n"
1835 << " Number of x-lines must be > 1.\n";
1836 } else {
1837 m_nScanX = nX;
1838 }
1839 if (nY < 2) {
1840 std::cerr << m_className << "::SetScanningGrid:\n"
1841 << " Number of y-lines must be > 1.\n";
1842 } else {
1843 m_nScanY = nY;
1844 }
1845}
1846
1848 const double xmax,
1849 const double ymin,
1850 const double ymax) {
1851 if (std::abs(xmax - xmin) < Small || std::abs(ymax - ymin) < Small) {
1852 std::cerr << m_className << "::SetScanningArea:\n"
1853 << " Zero range not permitted.\n";
1854 return;
1855 }
1856 m_scanRange = ScanningRange::User;
1857 m_xScanMin = std::min(xmin, xmax);
1858 m_xScanMax = std::max(xmin, xmax);
1859 m_yScanMin = std::min(ymin, ymax);
1860 m_yScanMax = std::max(ymin, ymax);
1861}
1862
1864 m_scanRange = ScanningRange::FirstOrder;
1865 if (scale > 0.) {
1866 m_scaleRange = scale;
1867 } else {
1868 std::cerr << m_className << "::SetScanningAreaFirstOrder:\n"
1869 << " Scaling factor must be > 0.\n";
1870 }
1871}
1872
1873void ComponentAnalyticField::SetGravity(const double dx, const double dy,
1874 const double dz) {
1875
1876 const double d = sqrt(dx * dx + dy * dy + dz * dz);
1877 if (d > 0.) {
1878 m_down[0] = dx / d;
1879 m_down[1] = dy / d;
1880 m_down[2] = dz / d;
1881 } else {
1882 std::cerr << m_className << "::SetGravity:\n"
1883 << " The gravity vector has zero norm ; ignored.\n";
1884 }
1885}
1886
1887void ComponentAnalyticField::GetGravity(double& dx, double& dy,
1888 double& dz) const {
1889
1890 dx = m_down[0];
1891 dy = m_down[1];
1892 dz = m_down[2];
1893}
1894
1896 const unsigned int iw, std::vector<double>& xMap, std::vector<double>& yMap,
1897 std::vector<std::vector<double> >& fxMap,
1898 std::vector<std::vector<double> >& fyMap) {
1899 if (!m_cellset && !Prepare()) {
1900 std::cerr << m_className << "::ForcesOnWire: Cell not set up.\n";
1901 return false;
1902 }
1903
1904 if (iw >= m_nWires) {
1905 std::cerr << m_className << "::ForcesOnWire: Wire index out of range.\n";
1906 return false;
1907 }
1908 if (m_polar) {
1909 std::cerr << m_className << "::ForcesOnWire: Cannot handle polar cells.\n";
1910 return false;
1911 }
1912 const auto& wire = m_w[iw];
1913 // Compute a 'safe box' around the wire.
1914 double bxmin = m_perx ? wire.x - 0.5 * m_sx : 2 * m_xmin - m_xmax;
1915 double bxmax = m_perx ? wire.x + 0.5 * m_sx : 2 * m_xmax - m_xmin;
1916 double bymin = m_pery ? wire.y - 0.5 * m_sy : 2 * m_ymin - m_ymax;
1917 double bymax = m_pery ? wire.y + 0.5 * m_sy : 2 * m_ymax - m_ymin;
1918
1919 // If the initial area is almost zero in 1 direction, make it square.
1920 if (std::abs(bxmax - bxmin) < 0.1 * std::abs(bymax - bymin)) {
1921 bxmin = wire.x - 0.5 * std::abs(bymax - bymin);
1922 bxmax = wire.x + 0.5 * std::abs(bymax - bymin);
1923 } else if (std::abs(bymax - bymin) < 0.1 * std::abs(bxmax - bxmin)) {
1924 bymin = wire.y - 0.5 * std::abs(bxmax - bxmin);
1925 bymax = wire.y + 0.5 * std::abs(bxmax - bxmin);
1926 }
1927 const double dw = 2 * wire.r;
1928 // Scan the other wires.
1929 for (unsigned int j = 0; j < m_nWires; ++j) {
1930 if (j == iw) continue;
1931 const double xj = m_w[j].x;
1932 const double yj = m_w[j].y;
1933 const double dj = 2 * m_w[j].r;
1934 double xnear = m_perx ? xj - m_sx * int(round((xj - wire.x) / m_sx)) : xj;
1935 double ynear = m_pery ? yj - m_sy * int(round((yj - wire.y) / m_sy)) : yj;
1936 if (std::abs(xnear - wire.x) > std::abs(ynear - wire.y)) {
1937 if (xnear < wire.x) {
1938 bxmin = std::max(bxmin, xnear + dj + dw);
1939 if (m_perx) bxmax = std::min(bxmax, xnear + m_sx - dj - dw);
1940 } else {
1941 bxmax = std::min(bxmax, xnear - dj - dw);
1942 if (m_perx) bxmin = std::max(bxmin, xnear - m_sx + dj + dw);
1943 }
1944 } else {
1945 if (ynear < wire.y) {
1946 bymin = std::max({bymin, ynear - dj - dw, ynear + dj + dw});
1947 if (m_pery) bymax = std::min(bymax, ynear + m_sy - dj - dw);
1948 } else {
1949 bymax = std::min({bymax, ynear - dj - dw, ynear + dj + dw});
1950 if (m_pery) bymin = std::max(bymin, ynear - m_sy + dj + dw);
1951 }
1952 }
1953 }
1954 // Scan the planes.
1955 if (m_ynplan[0]) bxmin = std::max(bxmin, m_coplan[0] + dw);
1956 if (m_ynplan[1]) bxmax = std::min(bxmax, m_coplan[1] - dw);
1957 if (m_ynplan[2]) bymin = std::max(bymin, m_coplan[2] + dw);
1958 if (m_ynplan[3]) bymax = std::min(bymax, m_coplan[3] - dw);
1959
1960 // If there is a tube, check all corners.
1961 if (m_tube) {
1962 const double d2 = m_cotube2 - dw * dw;
1963 if (d2 < Small) {
1964 std::cerr << m_className << "::ForcesOnWire:\n Diameter of wire " << iw
1965 << " is too large compared to the tube.\n";
1966 return false;
1967 }
1968
1969 double corr = sqrt((bxmin * bxmin + bymin * bymin) / d2);
1970 if (corr > 1.) {
1971 bxmin /= corr;
1972 bymin /= corr;
1973 }
1974 corr = sqrt((bxmin * bxmin + bymax * bymax) / d2);
1975 if (corr > 1.) {
1976 bxmin /= corr;
1977 bymax /= corr;
1978 }
1979 corr = sqrt((bxmax * bxmax + bymin * bymin) / d2);
1980 if (corr > 1.) {
1981 bxmax /= corr;
1982 bymin /= corr;
1983 }
1984 corr = sqrt((bxmax * bxmax + bymax * bymax) / d2);
1985 if (corr > 1) {
1986 bxmax /= corr;
1987 bymax /= corr;
1988 }
1989 }
1990 // Make sure we found a reasonable 'safe area'.
1991 if ((bxmin - wire.x) * (wire.x - bxmax) <= 0 ||
1992 (bymin - wire.y) * (wire.y - bymax) <= 0) {
1993 std::cerr << m_className << "::ForcesOnWire:\n Unable to find an area "
1994 << "free of elements around wire " << iw << ".\n";
1995 return false;
1996 }
1997 // Now set a reasonable scanning range.
1998 double sxmin = bxmin;
1999 double sxmax = bxmax;
2000 double symin = bymin;
2001 double symax = bymax;
2002 if (m_scanRange == ScanningRange::User) {
2003 // User-specified range.
2004 sxmin = wire.x + m_xScanMin;
2005 symin = wire.y + m_yScanMin;
2006 sxmax = wire.x + m_xScanMax;
2007 symax = wire.y + m_yScanMax;
2008 } else if (m_scanRange == ScanningRange::FirstOrder) {
2009 // Get the field and force at the nominal position.
2010 double ex = 0., ey = 0.;
2011 ElectricFieldAtWire(iw, ex, ey);
2012 double fx = -ex * wire.e * Internal2Newton;
2013 double fy = -ey * wire.e * Internal2Newton;
2014 if (m_useGravitationalForce) {
2015 // Mass per unit length [kg / cm].
2016 const double m = 1.e-3 * wire.density * Pi * wire.r * wire.r;
2017 fx -= m_down[0] * GravitationalAcceleration * m;
2018 fy -= m_down[1] * GravitationalAcceleration * m;
2019 }
2020 const double u2 = wire.u * wire.u;
2021 const double shiftx =
2022 -125 * fx * u2 / (GravitationalAcceleration * wire.tension);
2023 const double shifty =
2024 -125 * fy * u2 / (GravitationalAcceleration * wire.tension);
2025 // If 0th order estimate of shift is not small.
2026 const double tol = 0.1 * wire.r;
2027 if (std::abs(shiftx) > tol || std::abs(shifty) > tol) {
2028 sxmin = std::max(bxmin, std::min(wire.x + m_scaleRange * shiftx,
2029 wire.x - shiftx / m_scaleRange));
2030 symin = std::max(bymin, std::min(wire.y + m_scaleRange * shifty,
2031 wire.y - shifty / m_scaleRange));
2032 sxmax = std::min(bxmax, std::max(wire.x + m_scaleRange * shiftx,
2033 wire.x - shiftx / m_scaleRange));
2034 symax = std::min(bymax, std::max(wire.y + m_scaleRange * shifty,
2035 wire.y - shifty / m_scaleRange));
2036 // If one is very small, make the area square within bounds.
2037 if (std::abs(sxmax - sxmin) < 0.1 * std::abs(symax - symin)) {
2038 sxmin = std::max(bxmin, wire.x - 0.5 * std::abs(symax - symin));
2039 sxmax = std::min(bxmax, wire.x + 0.5 * std::abs(symax - symin));
2040 } else if (std::abs(symax - symin) < 0.1 * std::abs(sxmax - sxmin)) {
2041 symin = std::max(bymin, wire.y - 0.5 * std::abs(sxmax - sxmin));
2042 symax = std::min(bymax, wire.y + 0.5 * std::abs(sxmax - sxmin));
2043 }
2044 }
2045 }
2046 if (m_debug) {
2047 std::cout << m_className << "::ForcesOnWire:\n";
2048 std::printf(" Free area %12.5e < x < %12.5e\n", bxmin, bxmax);
2049 std::printf(" %12.5e < y < %12.5e\n", bymin, bymax);
2050 std::printf(" Scan area %12.5e < x < %12.5e\n", sxmin, sxmax);
2051 std::printf(" %12.5e < y < %12.5e\n", symin, symax);
2052 }
2053
2054 xMap.resize(m_nScanX);
2055 const double stepx = (sxmax - sxmin) / (m_nScanX - 1);
2056 for (unsigned int i = 0; i < m_nScanX; ++i) {
2057 xMap[i] = sxmin + i * stepx;
2058 }
2059 yMap.resize(m_nScanY);
2060 const double stepy = (symax - symin) / (m_nScanY - 1);
2061 for (unsigned int i = 0; i < m_nScanY; ++i) {
2062 yMap[i] = symin + i * stepy;
2063 }
2064 // Save the original coordinates of the wire.
2065 const double x0 = wire.x;
2066 const double y0 = wire.y;
2067 // Prepare interpolation tables.
2068 fxMap.assign(m_nScanX, std::vector<double>(m_nScanY, 0.));
2069 fyMap.assign(m_nScanX, std::vector<double>(m_nScanY, 0.));
2070 bool ok = true;
2071 for (unsigned int i = 0; i < m_nScanX; ++i) {
2072 for (unsigned int j = 0; j < m_nScanY; ++j) {
2073 // Get the wire position for this shift.
2074 m_w[iw].x = xMap[i];
2075 m_w[iw].y = yMap[j];
2076 // Verify the current situation.
2077 if (!WireCheck()) {
2078 std::cerr << m_className << "::ForcesOnWire: Wire " << iw << ".\n"
2079 << " Scan involves a disallowed wire position.\n";
2080 ok = false;
2081 continue;
2082 }
2083 // Recompute the charges for this configuration.
2084 if (!Setup()) {
2085 std::cerr << m_className << "::ForcesOnWire: Wire " << iw << ".\n"
2086 << " Failed to compute charges at a scan point.\n";
2087 ok = false;
2088 continue;
2089 }
2090 // Compute the forces.
2091 double ex = 0., ey = 0.;
2092 ElectricFieldAtWire(iw, ex, ey);
2093 fxMap[i][j] = -ex * wire.e * Internal2Newton;
2094 fyMap[i][j] = -ey * wire.e * Internal2Newton;
2095 }
2096 }
2097 // Place the wire back in its shifted position.
2098 m_w[iw].x = x0;
2099 m_w[iw].y = y0;
2100 Setup();
2101 return ok;
2102}
2103
2105 if (n == 0) {
2106 std::cerr << m_className << "::SetNumberOfSteps:\n"
2107 << " Number of steps must be > 0.\n";
2108 return;
2109 }
2110 m_nSteps = n;
2111}
2112
2114 const unsigned int iw, const bool detailed, std::vector<double>& csag,
2115 std::vector<double>& xsag, std::vector<double>& ysag, double& stretch,
2116 const bool print) {
2117 if (!m_cellset && !Prepare()) {
2118 std::cerr << m_className << "::WireDisplacement: Cell not set up.\n";
2119 return false;
2120 }
2121 if (iw >= m_nWires) {
2122 std::cerr << m_className
2123 << "::WireDisplacement: Wire index out of range.\n";
2124 return false;
2125 }
2126 if (m_polar) {
2127 std::cerr << m_className
2128 << "::WireDisplacement: Cannot handle polar cells.\n";
2129 return false;
2130 }
2131 const auto& wire = m_w[iw];
2132 // Save the original coordinates.
2133 const double x0 = wire.x;
2134 const double y0 = wire.y;
2135 // First-order approximation.
2136 if (!Setup()) {
2137 std::cerr << m_className << "::WireDisplacement:\n"
2138 << " Charge calculation failed at central position.\n";
2139 return false;
2140 }
2141
2142 double fx = 0., fy = 0.;
2143 if (m_useElectrostaticForce) {
2144 double ex = 0., ey = 0.;
2145 ElectricFieldAtWire(iw, ex, ey);
2146 fx -= ex * wire.e * Internal2Newton;
2147 fy -= ey * wire.e * Internal2Newton;
2148 }
2149 if (m_useGravitationalForce) {
2150 // Mass per unit length [kg / cm].
2151 const double m = 1.e-3 * wire.density * Pi * wire.r * wire.r;
2152 fx -= m_down[0] * GravitationalAcceleration * m;
2153 fy -= m_down[1] * GravitationalAcceleration * m;
2154 }
2155 const double u2 = wire.u * wire.u;
2156 const double shiftx =
2157 -125 * fx * u2 / (GravitationalAcceleration * wire.tension);
2158 const double shifty =
2159 -125 * fy * u2 / (GravitationalAcceleration * wire.tension);
2160 // Get the elongation from this.
2161 const double s = 4 * sqrt(shiftx * shiftx + shifty * shifty) / wire.u;
2162 double length = wire.u;
2163 if (s > Small) {
2164 const double t = sqrt(1 + s * s);
2165 length *= 0.5 * (t + log(s + t) / s);
2166 }
2167 stretch = (length - wire.u) / wire.u;
2168 if (print) {
2169 std::cout << m_className
2170 << "::WireDisplacement:\n"
2171 << " Forces and displacement in 0th order.\n"
2172 << " Wire information: number = " << iw << "\n"
2173 << " type = " << wire.type << "\n"
2174 << " location = (" << wire.x << ", " << wire.y
2175 << ") cm\n"
2176 << " voltage = " << wire.v << " V\n"
2177 << " length = " << wire.u << " cm\n"
2178 << " tension = " << wire.tension << " g\n"
2179 << " In this position: Fx = " << fx << " N/cm\n"
2180 << " Fy = " << fy << " N/cm\n"
2181 << " x-shift = " << shiftx << " cm\n"
2182 << " y-shift = " << shifty << " cm\n"
2183 << " stretch = " << 100. * stretch << "%\n";
2184 }
2185 if (!detailed) {
2186 csag = {0.};
2187 xsag = {shiftx};
2188 ysag = {shifty};
2189 return true;
2190 }
2191 std::vector<double> xMap(m_nScanX, 0.);
2192 std::vector<double> yMap(m_nScanY, 0.);
2193 std::vector<std::vector<double> > fxMap(m_nScanX,
2194 std::vector<double>(m_nScanY, 0.));
2195 std::vector<std::vector<double> > fyMap(m_nScanX,
2196 std::vector<double>(m_nScanY, 0.));
2197 if (!ForcesOnWire(iw, xMap, yMap, fxMap, fyMap)) {
2198 std::cerr << m_className << "::WireDisplacement:\n"
2199 << " Could not compute the electrostatic force table.\n";
2200 return false;
2201 }
2202 // Compute the detailed wire shift.
2203 if (!SagDetailed(wire, xMap, yMap, fxMap, fyMap, csag, xsag, ysag)) {
2204 std::cerr << m_className << "::WireDisplacement: Wire " << iw << ".\n"
2205 << " Computation of the wire sag failed.\n";
2206 return false;
2207 }
2208 // Verify that the wire is in range.
2209 const double sxmin = xMap.front();
2210 const double sxmax = xMap.back();
2211 const double symin = yMap.front();
2212 const double symax = yMap.back();
2213 const unsigned int nSag = xsag.size();
2214 bool outside = false;
2215 length = 0.;
2216 double xAvg = 0.;
2217 double yAvg = 0.;
2218 double xMax = 0.;
2219 double yMax = 0.;
2220 for (unsigned int i = 0; i < nSag; ++i) {
2221 if (x0 + xsag[i] < sxmin || x0 + xsag[i] > sxmax || y0 + ysag[i] < symin ||
2222 y0 + ysag[i] > symax) {
2223 outside = true;
2224 }
2225 xAvg += xsag[i];
2226 yAvg += ysag[i];
2227 xMax = std::max(xMax, std::abs(xsag[i]));
2228 yMax = std::max(yMax, std::abs(ysag[i]));
2229 if (i == 0) continue;
2230 const double dx = xsag[i] - xsag[i - 1];
2231 const double dy = ysag[i] - ysag[i - 1];
2232 const double dz = csag[i] - csag[i - 1];
2233 length += sqrt(dx * dx + dy * dy + dz * dz);
2234 }
2235 xAvg /= nSag;
2236 yAvg /= nSag;
2237 stretch = (length - wire.u) / wire.u;
2238 // Warn if a point outside the scanning area was found.
2239 if (outside) {
2240 std::cerr
2241 << m_className << "::WireDisplacement: Warning.\n "
2242 << "The wire profile is located partially outside the scanning area.\n";
2243 }
2244 if (print) {
2245 std::cout << " Sag profile for wire " << iw << ".\n"
2246 << " Point z [cm] x-sag [um] y-sag [um]\n";
2247 for (unsigned int i = 0; i < nSag; ++i) {
2248 std::printf(" %3d %10.4f %10.4f %10.4f\n",
2249 i, csag[i], xsag[i] * 1.e4, ysag[i] * 1.e4);
2250 }
2251 std::printf(" Average sag in x and y: %10.4f and %10.4f micron\n",
2252 1.e4 * xAvg, 1.e4 * yAvg);
2253 std::printf(" Maximum sag in x and y: %10.4f and %10.4f micron\n",
2254 1.e4 * xMax, 1.e4 * yMax);
2255 std::cout << " Elongation: " << 100. * stretch << "%\n";
2256 }
2257 return true;
2258}
2259
2260int ComponentAnalyticField::Field(const double xin, const double yin,
2261 const double zin, double& ex, double& ey,
2262 double& ez, double& volt, const bool opt) {
2263 //-----------------------------------------------------------------------
2264 // EFIELD - Subroutine calculating the electric field and the potential
2265 // at a given place. It makes use of the routines POT...,
2266 // depending on the type of the cell.
2267 // VARIABLES : XPOS : x-coordinate of the place where the field
2268 // is to be calculated.
2269 // YPOS, ZPOS : y- and z-coordinates
2270 // EX, EY, EZ : x-, y-, z-component of the electric field.
2271 // VOLT : potential at (XPOS,YPOS).
2272 // IOPT : 1 if both E and V are required, 0 if only E
2273 // is to be computed.
2274 // ILOC : Tells where the point is located (0: normal
2275 // I > 0: in wire I, -1: outside a plane,
2276 // -5: in a material, -6: outside the mesh,
2277 // -10: unknown potential).
2278 // (Last changed on 28/ 9/07.)
2279 //-----------------------------------------------------------------------
2280
2281 // Initialise the field for returns without actual calculations.
2282 ex = ey = ez = volt = 0.;
2283
2284 // Make sure the charges have been calculated.
2285 if (!m_cellset && !Prepare()) return -11;
2286
2287 double xpos = xin, ypos = yin;
2288 if (m_polar) Cartesian2Internal(xin, yin, xpos, ypos);
2289 // In case of periodicity, move the point into the basic cell.
2290 if (m_perx) {
2291 xpos -= m_sx * int(round(xin / m_sx));
2292 }
2293 double arot = 0.;
2294 if (m_pery && m_tube) {
2295 Cartesian2Polar(xin, yin, xpos, ypos);
2296 arot = RadToDegree * m_sy * int(round(DegreeToRad * ypos / m_sy));
2297 ypos -= arot;
2298 Polar2Cartesian(xpos, ypos, xpos, ypos);
2299 } else if (m_pery) {
2300 ypos -= m_sy * int(round(ypos / m_sy));
2301 }
2302
2303 // Move the point to the correct side of the plane.
2304 if (m_perx && m_ynplan[0] && xpos <= m_coplan[0]) xpos += m_sx;
2305 if (m_perx && m_ynplan[1] && xpos >= m_coplan[1]) xpos -= m_sx;
2306 if (m_pery && m_ynplan[2] && ypos <= m_coplan[2]) ypos += m_sy;
2307 if (m_pery && m_ynplan[3] && ypos >= m_coplan[3]) ypos -= m_sy;
2308
2309 // In case (XPOS,YPOS) is located behind a plane there is no field.
2310 if (m_tube) {
2311 if (!InTube(xpos, ypos, m_cotube, m_ntube)) {
2312 volt = m_vttube;
2313 return -4;
2314 }
2315 } else {
2316 if (m_ynplan[0] && xpos < m_coplan[0]) {
2317 volt = m_vtplan[0];
2318 return -4;
2319 }
2320 if (m_ynplan[1] && xpos > m_coplan[1]) {
2321 volt = m_vtplan[1];
2322 return -4;
2323 }
2324 if (m_ynplan[2] && ypos < m_coplan[2]) {
2325 volt = m_vtplan[2];
2326 return -4;
2327 }
2328 if (m_ynplan[3] && ypos > m_coplan[3]) {
2329 volt = m_vtplan[3];
2330 return -4;
2331 }
2332 }
2333
2334 // If (xpos, ypos) is within a wire, there is no field either.
2335 for (int i = m_nWires; i--;) {
2336 double dx = xpos - m_w[i].x;
2337 double dy = ypos - m_w[i].y;
2338 // Correct for periodicities.
2339 if (m_perx) dx -= m_sx * int(round(dx / m_sx));
2340 if (m_pery) dy -= m_sy * int(round(dy / m_sy));
2341 // Check the actual position.
2342 if (dx * dx + dy * dy < m_w[i].r * m_w[i].r) {
2343 volt = m_w[i].v;
2344 return i + 1;
2345 }
2346 }
2347
2348 // Call the appropriate potential calculation function.
2349 switch (m_cellType) {
2350 case A00:
2351 FieldA00(xpos, ypos, ex, ey, volt, opt);
2352 break;
2353 case B1X:
2354 FieldB1X(xpos, ypos, ex, ey, volt, opt);
2355 break;
2356 case B1Y:
2357 FieldB1Y(xpos, ypos, ex, ey, volt, opt);
2358 break;
2359 case B2X:
2360 FieldB2X(xpos, ypos, ex, ey, volt, opt);
2361 break;
2362 case B2Y:
2363 FieldB2Y(xpos, ypos, ex, ey, volt, opt);
2364 break;
2365 case C10:
2366 FieldC10(xpos, ypos, ex, ey, volt, opt);
2367 break;
2368 case C2X:
2369 FieldC2X(xpos, ypos, ex, ey, volt, opt);
2370 break;
2371 case C2Y:
2372 FieldC2Y(xpos, ypos, ex, ey, volt, opt);
2373 break;
2374 case C30:
2375 FieldC30(xpos, ypos, ex, ey, volt, opt);
2376 break;
2377 case D10:
2378 FieldD10(xpos, ypos, ex, ey, volt, opt);
2379 break;
2380 case D20:
2381 FieldD20(xpos, ypos, ex, ey, volt, opt);
2382 break;
2383 case D30:
2384 FieldD30(xpos, ypos, ex, ey, volt, opt);
2385 break;
2386 default:
2387 // Unknown cell type
2388 std::cerr << m_className << "::Field:\n";
2389 std::cerr << " Unknown cell type (id " << m_cellType << ")\n";
2390 return -10;
2391 break;
2392 }
2393
2394 // Add dipole terms if requested
2395 if (m_dipole) {
2396 double exd = 0., eyd = 0., voltd = 0.;
2397 switch (m_cellType) {
2398 case A00:
2399 DipoleFieldA00(xpos, ypos, exd, eyd, voltd, opt);
2400 break;
2401 case B1X:
2402 DipoleFieldB1X(xpos, ypos, exd, eyd, voltd, opt);
2403 break;
2404 case B1Y:
2405 DipoleFieldB1Y(xpos, ypos, exd, eyd, voltd, opt);
2406 break;
2407 case B2X:
2408 DipoleFieldB2X(xpos, ypos, exd, eyd, voltd, opt);
2409 break;
2410 case B2Y:
2411 DipoleFieldB2Y(xpos, ypos, exd, eyd, voltd, opt);
2412 break;
2413 default:
2414 break;
2415 }
2416 ex += exd;
2417 ey += eyd;
2418 volt += voltd;
2419 }
2420
2421 // Rotate the field in some special cases.
2422 if (m_pery && m_tube) {
2423 double xaux, yaux;
2424 Cartesian2Polar(ex, ey, xaux, yaux);
2425 yaux += arot;
2426 Polar2Cartesian(xaux, yaux, ex, ey);
2427 }
2428
2429 // Correct for the equipotential planes.
2430 ex -= m_corvta;
2431 ey -= m_corvtb;
2432 volt += m_corvta * xpos + m_corvtb * ypos + m_corvtc;
2433
2434 // Add three dimensional point charges.
2435 if (!m_ch3d.empty()) {
2436 double ex3d = 0., ey3d = 0., ez3d = 0., volt3d = 0.;
2437 switch (m_cellType) {
2438 case A00:
2439 case B1X:
2440 case B1Y:
2441 Field3dA00(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2442 break;
2443 case B2X:
2444 Field3dB2X(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2445 break;
2446 case B2Y:
2447 Field3dB2Y(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2448 break;
2449 case D10:
2450 Field3dD10(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2451 break;
2452 default:
2453 Field3dA00(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2454 break;
2455 }
2456 ex += ex3d;
2457 ey += ey3d;
2458 ez += ez3d;
2459 volt += volt3d;
2460 }
2461
2462 if (m_polar) {
2463 const double r = exp(xpos);
2464 const double er = ex / r;
2465 const double ep = ey / r;
2466 const double theta = atan2(yin, xin);
2467 const double ct = cos(theta);
2468 const double st = sin(theta);
2469 ex = +ct * er - st * ep;
2470 ey = +st * er + ct * ep;
2471 }
2472 return 0;
2473}
2474
2475void ComponentAnalyticField::CellInit() {
2476
2477 m_cellset = false;
2478 m_sigset = false;
2479
2480 // Coordinate system
2481 m_polar = false;
2482
2483 // Cell type
2484 m_cellType = A00;
2485
2486 // Bounding box and voltage range.
2487 m_xmin = m_xmax = 0.;
2488 m_ymin = m_ymax = 0.;
2489 m_zmin = m_zmax = 0.;
2490 m_vmin = m_vmax = 0.;
2491
2492 // Periodicities
2493 m_perx = m_pery = false;
2494 m_periodic[0] = m_periodic[1] = false;
2495 m_sx = m_sy = 1.;
2496
2497 // Signals
2498 m_nFourier = 1;
2499 m_cellTypeFourier = A00;
2500 m_fperx = false;
2501 m_fpery = false;
2502 m_mxmin = 0;
2503 m_mxmax = 0;
2504 m_mymin = 0;
2505 m_mymax = 0;
2506 m_mfexp = 0;
2507
2508 m_readout.clear();
2509
2510 // Wires.
2511 m_nWires = 0;
2512 m_w.clear();
2513
2514 // Dipole settings
2515 m_dipole = false;
2516 m_cosph2.clear();
2517 m_sinph2.clear();
2518 m_amp2.clear();
2519
2520 // B2 type cells
2521 m_b2sin.clear();
2522 // C type cells
2523 m_mode = 0;
2524 m_zmult = std::complex<double>(0., 0.);
2525 m_p1 = m_p2 = m_c1 = 0.;
2526 // D3 type cells
2527 wmap.clear();
2528 m_kappa = 0.;
2529
2530 // Reference potential
2531 m_v0 = 0.;
2532 m_corvta = m_corvtb = m_corvtc = 0.;
2533
2534 // Planes
2535 for (int i = 0; i < 5; ++i) {
2536 m_planes[i].type = '?';
2537 m_planes[i].ind = -1;
2538 m_planes[i].ewxcor = 0.;
2539 m_planes[i].ewycor = 0.;
2540 m_planes[i].strips1.clear();
2541 m_planes[i].strips2.clear();
2542 m_planes[i].pixels.clear();
2543 }
2544 for (int i = 0; i < 4; ++i) {
2545 m_ynplan[i] = false;
2546 m_coplan[i] = 0.;
2547 m_vtplan[i] = 0.;
2548 }
2549 // Plane shorthand
2550 m_ynplax = m_ynplay = false;
2551 m_coplax = m_coplay = 1.;
2552
2553 // Tube properties
2554 m_tube = false;
2555 m_ntube = 0;
2556 m_mtube = 1;
2557 m_cotube = 1.;
2558 m_cotube2 = 1.;
2559 m_vttube = 0.;
2560
2561 // Capacitance matrices
2562 m_a.clear();
2563 m_sigmat.clear();
2564 m_qplane.clear();
2565
2566 // 3D charges
2567 m_ch3d.clear();
2568
2569 // Gravity
2570 m_down = {0, 0, 1};
2571}
2572
2573bool ComponentAnalyticField::Prepare() {
2574 // Check that the cell makes sense.
2575 if (!CellCheck()) {
2576 std::cerr << m_className << "::Prepare:\n"
2577 << " The cell does not meet the requirements.\n";
2578 return false;
2579 }
2580 if (m_debug) std::cout << m_className << "::Prepare: Cell check ok.\n";
2581
2582 // Determine the cell type.
2583 if (!CellType()) {
2584 std::cerr << m_className << "::Prepare:\n"
2585 << " Type identification of the cell failed.\n";
2586 return false;
2587 }
2588 if (m_debug) {
2589 std::cout << m_className << "::Prepare:\n"
2590 << " Cell is of type " << CellType() << ".\n";
2591 }
2592
2593 // Calculate the charges.
2594 if (!Setup()) {
2595 std::cerr << m_className << "::Prepare: Calculation of charges failed.\n";
2596 return false;
2597 }
2598 if (m_debug) {
2599 std::cout << m_className << "::Prepare:\n"
2600 << " Calculation of charges was successful.\n";
2601 }
2602
2603 // Assign default gaps for strips and pixels.
2604 if (!PrepareStrips()) {
2605 std::cerr << m_className << "::Prepare: Strip/pixel preparation failed.\n";
2606 return false;
2607 }
2608
2609 m_cellset = true;
2610
2611 // Add dipole terms if required
2612 if (m_dipole) {
2613 if (!SetupDipoleTerms()) {
2614 std::cerr << m_className << "::Prepare:\n"
2615 << " Computing the dipole moments failed.\n";
2616 m_dipole = false;
2617 }
2618 }
2619 return true;
2620}
2621
2622bool ComponentAnalyticField::CellCheck() {
2623 //-----------------------------------------------------------------------
2624 // CELCHK - Subroutine checking the wire positions, The equipotential
2625 // planes and the periodicity. Two planes having different
2626 // voltages are not allowed to have a common line, wires are
2627 // not allowed to be at the same position etc.
2628 // This routine determines also the cell-dimensions.
2629 // VARIABLE : WRONG(I) : .TRUE. if wire I will be removed
2630 // IPLAN. : Number of wires with coord > than plane .
2631 // (Last changed on 16/ 2/05.)
2632 //-----------------------------------------------------------------------
2633
2634 // Checks on the planes, first move the x planes to the basic cell.
2635 if (m_perx) {
2636 const std::string xr = m_polar ? "r" : "x";
2637 double conew1 = m_coplan[0] - m_sx * int(round(m_coplan[0] / m_sx));
2638 double conew2 = m_coplan[1] - m_sx * int(round(m_coplan[1] / m_sx));
2639 // Check that they are not one on top of the other.
2640 if (m_ynplan[0] && m_ynplan[1] && conew1 == conew2) {
2641 if (conew1 > 0.)
2642 conew1 -= m_sx;
2643 else
2644 conew2 += m_sx;
2645 }
2646 // Print some warnings if the planes have been moved.
2647 if ((conew1 != m_coplan[0] && m_ynplan[0]) ||
2648 (conew2 != m_coplan[1] && m_ynplan[1])) {
2649 std::cout << m_className << "::CellCheck:\n The planes in "
2650 << xr << " are moved to the basic period.\n"
2651 << " This should not affect the results.\n";
2652 }
2653 m_coplan[0] = conew1;
2654 m_coplan[1] = conew2;
2655
2656 // Two planes should now be separated by SX, cancel PERX if not.
2657 if (m_ynplan[0] && m_ynplan[1] && fabs(m_coplan[1] - m_coplan[0]) != m_sx) {
2658 std::cerr << m_className << "::CellCheck:\n The separation of the "
2659 << xr << " planes does not match the period.\n"
2660 << " The periodicity is cancelled.\n";
2661 m_perx = false;
2662 }
2663 // If there are two planes left, they should have identical V's.
2664 if (m_ynplan[0] && m_ynplan[1] && m_vtplan[0] != m_vtplan[1]) {
2665 std::cerr << m_className << "::CellCheck:\n The voltages of the two "
2666 << xr << " planes differ.\n"
2667 << " The periodicity is cancelled.\n";
2668 m_perx = false;
2669 }
2670 }
2671
2672 // Idem for the y or phi planes: move them to the basic period.
2673 if (m_pery) {
2674 const std::string yp = m_polar ? "phi" : "y";
2675 double conew3 = m_coplan[2] - m_sy * int(round(m_coplan[2] / m_sy));
2676 double conew4 = m_coplan[3] - m_sy * int(round(m_coplan[3] / m_sy));
2677 // Check that they are not one on top of the other.
2678 if (m_ynplan[2] && m_ynplan[3] && conew3 == conew4) {
2679 if (conew3 > 0.)
2680 conew3 -= m_sy;
2681 else
2682 conew4 += m_sy;
2683 }
2684 // Print some warnings if the planes have been moved.
2685 if ((conew3 != m_coplan[2] && m_ynplan[2]) ||
2686 (conew4 != m_coplan[3] && m_ynplan[3])) {
2687 std::cout << m_className << "::CellCheck:\n The planes in "
2688 << yp << " are moved to the basic period.\n"
2689 << " This should not affect the results.\n";
2690 }
2691 m_coplan[2] = conew3;
2692 m_coplan[3] = conew4;
2693
2694 // Two planes should now be separated by SY, cancel PERY if not.
2695 if (m_ynplan[2] && m_ynplan[3] && fabs(m_coplan[3] - m_coplan[2]) != m_sy) {
2696 std::cerr << m_className << "::CellCheck:\n The separation of the two "
2697 << yp << " planes does not match the period.\n"
2698 << " The periodicity is cancelled.\n";
2699 m_pery = false;
2700 }
2701 // If there are two planes left, they should have identical V's.
2702 if (m_ynplan[2] && m_ynplan[3] && m_vtplan[2] != m_vtplan[3]) {
2703 std::cerr << m_className << "::CellCheck:\n The voltages of the two "
2704 << yp << " planes differ.\n"
2705 << " The periodicity is cancelled.\n";
2706 m_pery = false;
2707 }
2708 }
2709
2710 // Check that there is no voltage conflict of crossing planes.
2711 for (int i = 0; i < 2; ++i) {
2712 for (int j = 2; j < 3; ++j) {
2713 if (m_ynplan[i] && m_ynplan[j] && m_vtplan[i] != m_vtplan[j]) {
2714 const std::string yp = m_polar ? "phi" : "y";
2715 std::cerr << m_className << "::CellCheck:\n"
2716 << " Conflicting potential of two crossing planes.\n"
2717 << " One " << yp << " plane is removed.\n";
2718 m_ynplan[j] = false;
2719 }
2720 }
2721 }
2722
2723 // Make sure the coordinates of the planes are properly ordered.
2724 for (int i = 0; i < 3; i += 2) {
2725 if (m_ynplan[i] && m_ynplan[i + 1]) {
2726 if (m_coplan[i] == m_coplan[i + 1]) {
2727 std::cerr << m_className << "::CellCheck:\n"
2728 << " Two planes are on top of each other.\n"
2729 << " One of them is removed.\n";
2730 m_ynplan[i + 1] = false;
2731 }
2732 if (m_coplan[i] > m_coplan[i + 1]) {
2733 if (m_debug) {
2734 std::cout << m_className << "::CellCheck:\n Planes "
2735 << i << " and " << i + 1 << " are interchanged.\n";
2736 }
2737 // Interchange the two planes.
2738 const double cohlp = m_coplan[i];
2739 m_coplan[i] = m_coplan[i + 1];
2740 m_coplan[i + 1] = cohlp;
2741
2742 const double vthlp = m_vtplan[i];
2743 m_vtplan[i] = m_vtplan[i + 1];
2744 m_vtplan[i + 1] = vthlp;
2745
2746 Plane plahlp = m_planes[i];
2747 m_planes[i] = m_planes[i + 1];
2748 m_planes[i + 1] = plahlp;
2749 }
2750 }
2751 }
2752
2753 // Checks on the wires, start moving them to the basic x period.
2754 if (m_perx) {
2755 for (auto& wire : m_w) {
2756 double xnew = wire.x - m_sx * int(round(wire.x / m_sx));
2757 if (m_ynplan[0] && xnew <= m_coplan[0]) xnew += m_sx;
2758 if (m_ynplan[1] && xnew >= m_coplan[1]) xnew -= m_sx;
2759 if (fabs(xnew - wire.x) > 1.e-8) {
2760 double xprt = wire.x;
2761 double yprt = wire.y;
2762 if (m_polar) Internal2Polar(wire.x, wire.y, xprt, yprt);
2763 const std::string xr = m_polar ? "r" : "x";
2764 std::cout << m_className << "::CellCheck:\n The " << wire.type
2765 << "-wire at (" << xprt << ", " << yprt
2766 << ") is moved to the basic " << xr << " period.\n"
2767 << " This should not affect the results.\n";
2768 }
2769 wire.x = xnew;
2770 }
2771 }
2772
2773 // In case of y-periodicity, all wires should be in the first y-period.
2774 if (m_tube && m_pery) {
2775 for (unsigned int i = 0; i < m_nWires; ++i) {
2776 double xnew = m_w[i].x;
2777 double ynew = m_w[i].y;
2778 Cartesian2Polar(xnew, ynew, xnew, ynew);
2779 if (int(round(DegreeToRad * ynew / m_sy)) != 0) {
2780 std::cout << m_className << "::CellCheck:\n";
2781 std::cout << " The " << m_w[i].type << "-wire at (" << m_w[i].x
2782 << ", " << m_w[i].y
2783 << ") is moved to the basic phi period.\n";
2784 std::cout << " This should not affect the results.\n";
2785 ynew -= RadToDegree * m_sy * int(round(DegreeToRad * ynew / m_sy));
2786 Polar2Cartesian(xnew, ynew, m_w[i].x, m_w[i].y);
2787 }
2788 }
2789 } else if (m_pery) {
2790 for (auto& wire : m_w) {
2791 double ynew = wire.y - m_sy * int(round(wire.y / m_sy));
2792 if (m_ynplan[2] && ynew <= m_coplan[2]) ynew += m_sy;
2793 if (m_ynplan[3] && ynew >= m_coplan[3]) ynew -= m_sy;
2794 if (fabs(ynew - wire.y) > 1.e-8) {
2795 double xprt = wire.x;
2796 double yprt = wire.y;
2797 if (m_polar) Internal2Polar(wire.x, wire.y, xprt, yprt);
2798 const std::string yp = m_polar ? "phi" : "y";
2799 std::cout << m_className << "::CellCheck:\n The " << wire.type
2800 << "-wire at (" << xprt << ", " << yprt
2801 << ") is moved to the basic " << yp << " period.\n"
2802 << " This should not affect the results.\n";
2803 }
2804 wire.y = ynew;
2805 }
2806 }
2807
2808 // Make sure the plane numbering is standard: P1 wires P2, P3 wires P4.
2809 int iplan1 = 0, iplan2 = 0, iplan3 = 0, iplan4 = 0;
2810 for (const auto& wire : m_w) {
2811 if (m_ynplan[0] && wire.x <= m_coplan[0]) ++iplan1;
2812 if (m_ynplan[1] && wire.x <= m_coplan[1]) ++iplan2;
2813 if (m_ynplan[2] && wire.y <= m_coplan[2]) ++iplan3;
2814 if (m_ynplan[3] && wire.y <= m_coplan[3]) ++iplan4;
2815 }
2816
2817 // Find out whether smaller (-1) or larger (+1) coord. are to be kept.
2818 const int imid = int(m_nWires) / 2;
2819 if (m_ynplan[0] && m_ynplan[1]) {
2820 if (iplan1 > imid) {
2821 m_ynplan[1] = false;
2822 iplan1 = -1;
2823 } else {
2824 iplan1 = +1;
2825 }
2826 if (iplan2 < imid) {
2827 m_ynplan[0] = false;
2828 iplan2 = +1;
2829 } else {
2830 iplan2 = -1;
2831 }
2832 }
2833 if (m_ynplan[0] && !m_ynplan[1]) {
2834 if (iplan1 > imid) {
2835 iplan1 = -1;
2836 } else {
2837 iplan1 = +1;
2838 }
2839 }
2840 if (m_ynplan[1] && !m_ynplan[0]) {
2841 if (iplan2 < imid) {
2842 iplan2 = +1;
2843 } else {
2844 iplan2 = -1;
2845 }
2846 }
2847
2848 if (m_ynplan[2] && m_ynplan[3]) {
2849 if (iplan3 > imid) {
2850 m_ynplan[3] = false;
2851 iplan3 = -1;
2852 } else {
2853 iplan3 = +1;
2854 }
2855 if (iplan4 < imid) {
2856 m_ynplan[2] = false;
2857 iplan4 = +1;
2858 } else {
2859 iplan4 = -1;
2860 }
2861 }
2862 if (m_ynplan[2] && !m_ynplan[3]) {
2863 if (iplan3 > imid) {
2864 iplan3 = -1;
2865 } else {
2866 iplan3 = +1;
2867 }
2868 }
2869 if (m_ynplan[3] && !m_ynplan[2]) {
2870 if (iplan4 < imid) {
2871 iplan4 = +1;
2872 } else {
2873 iplan4 = -1;
2874 }
2875 }
2876
2877 // Adapt the numbering of the planes if necessary.
2878 if (iplan1 == -1) {
2879 m_ynplan[0] = false;
2880 m_ynplan[1] = true;
2881 m_coplan[1] = m_coplan[0];
2882 m_vtplan[1] = m_vtplan[0];
2883 m_planes[1] = m_planes[0];
2884 }
2885
2886 if (iplan2 == +1) {
2887 m_ynplan[1] = false;
2888 m_ynplan[0] = true;
2889 m_coplan[0] = m_coplan[1];
2890 m_vtplan[0] = m_vtplan[1];
2891 m_planes[0] = m_planes[1];
2892 }
2893
2894 if (iplan3 == -1) {
2895 m_ynplan[2] = false;
2896 m_ynplan[3] = true;
2897 m_coplan[3] = m_coplan[2];
2898 m_vtplan[3] = m_vtplan[2];
2899 m_planes[3] = m_planes[2];
2900 }
2901
2902 if (iplan4 == +1) {
2903 m_ynplan[3] = false;
2904 m_ynplan[2] = true;
2905 m_coplan[2] = m_coplan[3];
2906 m_vtplan[2] = m_vtplan[3];
2907 m_planes[2] = m_planes[3];
2908 }
2909
2910 std::vector<bool> wrong(m_nWires, false);
2911 // Second pass for the wires, check position relative to the planes.
2912 for (unsigned int i = 0; i < m_nWires; ++i) {
2913 const double rw = m_w[i].r;
2914 const double dw = 2. * rw;
2915 if (m_ynplan[0] && m_w[i].x - rw <= m_coplan[0]) wrong[i] = true;
2916 if (m_ynplan[1] && m_w[i].x + rw >= m_coplan[1]) wrong[i] = true;
2917 if (m_ynplan[2] && m_w[i].y - rw <= m_coplan[2]) wrong[i] = true;
2918 if (m_ynplan[3] && m_w[i].y + rw >= m_coplan[3]) wrong[i] = true;
2919 if (m_tube) {
2920 if (!InTube(m_w[i].x, m_w[i].y, m_cotube, m_ntube)) {
2921 std::cerr << m_className << "::CellCheck:\n";
2922 std::cerr << " The " << m_w[i].type << "-wire at (" << m_w[i].x
2923 << ", " << m_w[i].y << ") is located outside the tube.\n";
2924 std::cerr << " This wire is removed.\n";
2925 wrong[i] = true;
2926 }
2927 } else if (wrong[i]) {
2928 double xprt = m_w[i].x;
2929 double yprt = m_w[i].y;
2930 if (m_polar) Internal2Polar(m_w[i].x, m_w[i].y, xprt, yprt);
2931 std::cerr << m_className << "::CellCheck:\n The " << m_w[i].type
2932 << "-wire at (" << xprt << ", " << yprt << ") is located "
2933 << "outside the planes.\n This wire is removed.\n";
2934 } else if ((m_perx && dw >= m_sx) || (m_pery && dw >= m_sy)) {
2935 double xprt = m_w[i].x;
2936 double yprt = m_w[i].y;
2937 if (m_polar) Internal2Polar(m_w[i].x, m_w[i].y, xprt, yprt);
2938 std::cerr << m_className << "::CellCheck:\n The diameter of the "
2939 << m_w[i].type << "-wire at (" << xprt << ", " << yprt
2940 << ") exceeds 1 period.\n This wire is removed.\n";
2941 wrong[i] = true;
2942 }
2943 }
2944
2945 // Check the wire spacing.
2946 for (unsigned int i = 0; i < m_nWires; ++i) {
2947 if (wrong[i]) continue;
2948 for (unsigned int j = i + 1; j < m_nWires; ++j) {
2949 if (wrong[j]) continue;
2950 double xsepar = 0.;
2951 double ysepar = 0.;
2952 if (m_tube) {
2953 if (m_pery) {
2954 double xaux1, xaux2, yaux1, yaux2;
2955 Cartesian2Polar(m_w[i].x, m_w[i].y, xaux1, yaux1);
2956 Cartesian2Polar(m_w[j].x, m_w[j].y, xaux2, yaux2);
2957 yaux1 -= m_sy * int(round(yaux1 / m_sy));
2958 yaux2 -= m_sy * int(round(yaux2 / m_sy));
2959 Polar2Cartesian(xaux1, yaux1, xaux1, yaux1);
2960 Polar2Cartesian(xaux2, yaux2, xaux2, yaux2);
2961 xsepar = xaux1 - xaux2;
2962 ysepar = yaux1 - yaux2;
2963 } else {
2964 xsepar = m_w[i].x - m_w[j].x;
2965 ysepar = m_w[i].y - m_w[j].y;
2966 }
2967 } else {
2968 xsepar = fabs(m_w[i].x - m_w[j].x);
2969 if (m_perx) xsepar -= m_sx * int(round(xsepar / m_sx));
2970 ysepar = fabs(m_w[i].y - m_w[j].y);
2971 if (m_pery) ysepar -= m_sy * int(round(ysepar / m_sy));
2972 }
2973 const double rij = m_w[i].r + m_w[j].r;
2974 if (xsepar * xsepar + ysepar * ysepar > rij * rij) continue;
2975 double xprti = m_w[i].x;
2976 double yprti = m_w[i].y;
2977 double xprtj = m_w[j].x;
2978 double yprtj = m_w[j].y;
2979 if (m_polar) {
2980 Internal2Polar(m_w[i].x, m_w[i].y, xprti, yprti);
2981 Internal2Polar(m_w[j].x, m_w[j].y, xprtj, yprtj);
2982 }
2983 std::cerr << m_className << "::CellCheck:\n Wires " << m_w[i].type
2984 << " at (" << xprti << ", " << yprti << ") and " << m_w[j].type
2985 << " at (" << xprtj << ", " << yprtj
2986 << ") overlap at least partially.\n"
2987 << " The latter wire is removed.\n";
2988 wrong[j] = true;
2989 }
2990 }
2991
2992 // Remove the wires which are not acceptable for one reason or another.
2993 const int iWires = m_nWires;
2994 m_nWires = 0;
2995 for (int i = 0; i < iWires; ++i) {
2996 if (!wrong[i]) {
2997 m_w[m_nWires] = m_w[i];
2998 ++m_nWires;
2999 }
3000 }
3001
3002 // Ensure that some elements are left.
3003 int nElements = m_nWires;
3004 if (m_ynplan[0]) ++nElements;
3005 if (m_ynplan[1]) ++nElements;
3006 if (m_ynplan[2]) ++nElements;
3007 if (m_ynplan[3]) ++nElements;
3008 if (m_tube) ++nElements;
3009
3010 if (nElements < 2) {
3011 std::cerr << m_className << "::CellCheck:\n";
3012 std::cerr << " At least 2 elements are necessary.\n";
3013 std::cerr << " Cell rejected.\n";
3014 return false;
3015 }
3016
3017 // Determine maximum and minimum coordinates and potentials.
3018 bool setx = false;
3019 bool sety = false;
3020 bool setz = false;
3021 bool setv = false;
3022
3023 m_xmin = m_xmax = 0.;
3024 m_ymin = m_ymax = 0.;
3025 m_zmin = m_zmax = 0.;
3026 m_vmin = m_vmax = 0.;
3027
3028 // Loop over the wires.
3029 for (const auto& wire : m_w) {
3030 const double rw = wire.r;
3031 if (setx) {
3032 m_xmin = std::min(m_xmin, wire.x - rw);
3033 m_xmax = std::max(m_xmax, wire.x + rw);
3034 } else {
3035 m_xmin = wire.x - rw;
3036 m_xmax = wire.x + rw;
3037 setx = true;
3038 }
3039 if (sety) {
3040 m_ymin = std::min(m_ymin, wire.y - rw);
3041 m_ymax = std::max(m_ymax, wire.y + rw);
3042 } else {
3043 m_ymin = wire.y - rw;
3044 m_ymax = wire.y + rw;
3045 sety = true;
3046 }
3047 if (setz) {
3048 m_zmin = std::min(m_zmin, -0.5 * wire.u);
3049 m_zmax = std::max(m_zmax, +0.5 * wire.u);
3050 } else {
3051 m_zmin = -0.5 * wire.u;
3052 m_zmax = +0.5 * wire.u;
3053 setz = true;
3054 }
3055 if (setv) {
3056 m_vmin = std::min(m_vmin, wire.v);
3057 m_vmax = std::max(m_vmax, wire.v);
3058 } else {
3059 m_vmin = m_vmax = wire.v;
3060 setv = true;
3061 }
3062 }
3063 // Consider the planes.
3064 for (int i = 0; i < 4; ++i) {
3065 if (!m_ynplan[i]) continue;
3066 if (i < 2) {
3067 if (setx) {
3068 m_xmin = std::min(m_xmin, m_coplan[i]);
3069 m_xmax = std::max(m_xmax, m_coplan[i]);
3070 } else {
3071 m_xmin = m_xmax = m_coplan[i];
3072 setx = true;
3073 }
3074 } else {
3075 if (sety) {
3076 m_ymin = std::min(m_ymin, m_coplan[i]);
3077 m_ymax = std::max(m_ymax, m_coplan[i]);
3078 } else {
3079 m_ymin = m_ymax = m_coplan[i];
3080 sety = true;
3081 }
3082 }
3083 if (setv) {
3084 m_vmin = std::min(m_vmin, m_vtplan[i]);
3085 m_vmax = std::max(m_vmax, m_vtplan[i]);
3086 } else {
3087 m_vmin = m_vmax = m_vtplan[i];
3088 setv = true;
3089 }
3090 }
3091
3092 // Consider the tube.
3093 if (m_tube) {
3094 m_xmin = -1.1 * m_cotube;
3095 m_xmax = +1.1 * m_cotube;
3096 setx = true;
3097 m_ymin = -1.1 * m_cotube;
3098 m_ymax = +1.1 * m_cotube;
3099 sety = true;
3100 m_vmin = std::min(m_vmin, m_vttube);
3101 m_vmax = std::max(m_vmax, m_vttube);
3102 setv = true;
3103 }
3104
3105 // In case of x-periodicity, XMAX-XMIN should be SX,
3106 if (m_perx && m_sx > (m_xmax - m_xmin)) {
3107 m_xmin = -0.5 * m_sx;
3108 m_xmax = +0.5 * m_sx;
3109 setx = true;
3110 }
3111 // in case of y-periodicity, YMAX-YMIN should be SY,
3112 if (m_pery && m_sy > (m_ymax - m_ymin)) {
3113 m_ymin = -0.5 * m_sy;
3114 m_ymax = +0.5 * m_sy;
3115 sety = true;
3116 }
3117 // in case the cell is polar, the y range should be < 2 pi.
3118 if (m_polar && (m_ymax - m_ymin) >= TwoPi) {
3119 m_ymin = -Pi;
3120 m_ymax = +Pi;
3121 sety = true;
3122 }
3123
3124 // Fill in missing dimensions.
3125 if (setx && m_xmin != m_xmax && (m_ymin == m_ymax || !sety)) {
3126 m_ymin -= 0.5 * fabs(m_xmax - m_xmin);
3127 m_ymax += 0.5 * fabs(m_xmax - m_xmin);
3128 sety = true;
3129 }
3130 if (sety && m_ymin != m_ymax && (m_xmin == m_xmax || !setx)) {
3131 m_xmin -= 0.5 * fabs(m_ymax - m_ymin);
3132 m_xmax += 0.5 * fabs(m_ymax - m_ymin);
3133 setx = true;
3134 }
3135
3136 if (!setz) {
3137 m_zmin = -0.25 * (fabs(m_xmax - m_xmin) + fabs(m_ymax - m_ymin));
3138 m_zmax = +0.25 * (fabs(m_xmax - m_xmin) + fabs(m_ymax - m_ymin));
3139 setz = true;
3140 }
3141
3142 // Ensure that all dimensions are now set.
3143 if (!(setx && sety && setz)) {
3144 std::cerr << m_className << "::CellCheck:\n";
3145 std::cerr << " Unable to establish"
3146 << " default dimensions in all directions.\n";
3147 }
3148
3149 // Check that at least some different voltages are present.
3150 if (m_vmin == m_vmax || !setv) {
3151 std::cerr << m_className << "::CellCheck:\n";
3152 std::cerr << " All potentials in the cell are the same.\n";
3153 std::cerr << " There is no point in going on.\n";
3154 return false;
3155 }
3156
3157 // Cell seems to be alright since it passed all critical tests.
3158 return true;
3159}
3160
3161bool ComponentAnalyticField::WireCheck() const {
3162 //-----------------------------------------------------------------------
3163 // CELWCH - Subroutine checking the wire positions only, contrary
3164 // to CELCHK, this routine does not modify the cell.
3165 //-----------------------------------------------------------------------
3166
3167 if (m_nWires == 0) return false;
3168
3169 if (m_nWires == 1 &&
3170 !(m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3]) && !m_tube) {
3171 return false;
3172 }
3173 // Check position relative to the planes.
3174 for (unsigned int i = 0; i < m_nWires; ++i) {
3175 const auto& wire = m_w[i];
3176 if (m_ynplan[0] && wire.x - wire.r <= m_coplan[0]) return false;
3177 if (m_ynplan[1] && wire.x + wire.r >= m_coplan[1]) return false;
3178 if (m_ynplan[2] && wire.y - wire.r <= m_coplan[2]) return false;
3179 if (m_ynplan[3] && wire.y + wire.r >= m_coplan[3]) return false;
3180 if (m_tube) {
3181 if (!InTube(wire.x, wire.y, m_cotube, m_ntube)) return false;
3182 } else if ((m_perx && 2 * wire.r >= m_sx) ||
3183 (m_pery && 2 * wire.r >= m_sy)) {
3184 return false;
3185 }
3186 }
3187 // Check the wire spacing.
3188 for (unsigned int i = 0; i < m_nWires; ++i) {
3189 const double xi = m_w[i].x;
3190 const double yi = m_w[i].y;
3191 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3192 const double xj = m_w[j].x;
3193 const double yj = m_w[j].y;
3194 double xsepar = std::abs(xi - xj);
3195 double ysepar = std::abs(yi - yj);
3196 if (m_tube) {
3197 if (m_pery) {
3198 double xaux1 = 0., yaux1 = 0.;
3199 double xaux2 = 0., yaux2 = 0.;
3200 Cartesian2Polar(xi, yi, xaux1, yaux1);
3201 Cartesian2Polar(xj, yj, xaux2, yaux2);
3202 yaux1 -= m_sy * int(round(yaux1 / m_sy));
3203 yaux2 -= m_sy * int(round(yaux2 / m_sy));
3204 Polar2Cartesian(xaux1, yaux1, xaux1, yaux1);
3205 Polar2Cartesian(xaux2, yaux2, xaux2, yaux2);
3206 xsepar = xaux1 - xaux2;
3207 ysepar = yaux1 - yaux2;
3208 }
3209 } else {
3210 if (m_perx) xsepar -= m_sx * int(round(xsepar / m_sx));
3211 if (m_pery) ysepar -= m_sy * int(round(ysepar / m_sy));
3212 }
3213 const double rij = m_w[i].r + m_w[j].r;
3214 if (xsepar * xsepar + ysepar * ysepar < rij * rij) return false;
3215 }
3216 }
3217 return true;
3218}
3219
3220bool ComponentAnalyticField::CellType() {
3221 // Tube geometries
3222 if (m_tube) {
3223 if (m_ntube == 0) {
3224 if (m_pery) {
3225 m_cellType = D20;
3226 } else {
3227 m_cellType = D10;
3228 }
3229 } else if (m_ntube >= 3 && m_ntube <= 8) {
3230 if (m_pery) {
3231 m_cellType = D40;
3232 } else {
3233 m_cellType = D30;
3234 }
3235 } else {
3236 std::cerr << m_className << "::CellType:\n"
3237 << " Potentials for tube with " << m_ntube
3238 << " edges are not yet available.\n"
3239 << " Using a round tube instead.\n";
3240 m_ntube = 0;
3241 m_cellType = D30;
3242 }
3243 return true;
3244 }
3245
3246 // Find the 'A' type cell.
3247 if (!(m_perx || m_pery) && !(m_ynplan[0] && m_ynplan[1]) &&
3248 !(m_ynplan[2] && m_ynplan[3])) {
3249 m_cellType = A00;
3250 return true;
3251 }
3252
3253 // Find the 'B1X' type cell.
3254 if (m_perx && !m_pery && !(m_ynplan[0] || m_ynplan[1]) &&
3255 !(m_ynplan[2] && m_ynplan[3])) {
3256 m_cellType = B1X;
3257 return true;
3258 }
3259
3260 // Find the 'B1Y' type cell.
3261 if (m_pery && !m_perx && !(m_ynplan[0] && m_ynplan[1]) &&
3262 !(m_ynplan[2] || m_ynplan[3])) {
3263 m_cellType = B1Y;
3264 return true;
3265 }
3266
3267 // Find the 'B2X' type cell.
3268 if (m_perx && !m_pery && !(m_ynplan[2] && m_ynplan[3])) {
3269 m_cellType = B2X;
3270 return true;
3271 }
3272
3273 if (!(m_perx || m_pery) && !(m_ynplan[2] && m_ynplan[3]) &&
3274 (m_ynplan[0] && m_ynplan[1])) {
3275 m_sx = fabs(m_coplan[1] - m_coplan[0]);
3276 m_cellType = B2X;
3277 return true;
3278 }
3279
3280 // Find the 'B2Y' type cell.
3281 if (m_pery && !m_perx && !(m_ynplan[0] && m_ynplan[1])) {
3282 m_cellType = B2Y;
3283 return true;
3284 }
3285
3286 if (!(m_perx || m_pery) && !(m_ynplan[0] && m_ynplan[1]) &&
3287 (m_ynplan[2] && m_ynplan[3])) {
3288 m_sy = fabs(m_coplan[3] - m_coplan[2]);
3289 m_cellType = B2Y;
3290 return true;
3291 }
3292
3293 // Find the 'C1 ' type cell.
3294 if (!(m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3]) && m_perx &&
3295 m_pery) {
3296 m_cellType = C10;
3297 return true;
3298 }
3299
3300 // Find the 'C2X' type cell.
3301 if (!((m_ynplan[2] && m_pery) || (m_ynplan[2] && m_ynplan[3]))) {
3302 if (m_ynplan[0] && m_ynplan[1]) {
3303 m_sx = fabs(m_coplan[1] - m_coplan[0]);
3304 m_cellType = C2X;
3305 return true;
3306 }
3307 if (m_perx && m_ynplan[0]) {
3308 m_cellType = C2X;
3309 return true;
3310 }
3311 }
3312
3313 // Find the 'C2Y' type cell.
3314 if (!((m_ynplan[0] && m_perx) || (m_ynplan[0] && m_ynplan[1]))) {
3315 if (m_ynplan[2] && m_ynplan[3]) {
3316 m_sy = fabs(m_coplan[3] - m_coplan[2]);
3317 m_cellType = C2Y;
3318 return true;
3319 }
3320 if (m_pery && m_ynplan[2]) {
3321 m_cellType = C2Y;
3322 return true;
3323 }
3324 }
3325
3326 // Find the 'C3 ' type cell.
3327 if (m_perx && m_pery) {
3328 m_cellType = C30;
3329 return true;
3330 }
3331
3332 if (m_perx) {
3333 m_sy = fabs(m_coplan[3] - m_coplan[2]);
3334 m_cellType = C30;
3335 return true;
3336 }
3337
3338 if (m_pery) {
3339 m_sx = fabs(m_coplan[1] - m_coplan[0]);
3340 m_cellType = C30;
3341 return true;
3342 }
3343
3344 if (m_ynplan[0] && m_ynplan[1] && m_ynplan[2] && m_ynplan[3]) {
3345 m_sx = fabs(m_coplan[1] - m_coplan[0]);
3346 m_sy = fabs(m_coplan[3] - m_coplan[2]);
3347 m_cellType = C30;
3348 return true;
3349 }
3350
3351 // Cell is not recognised.
3352 return false;
3353}
3354
3355std::string ComponentAnalyticField::GetCellType(const Cell) const {
3356 switch (m_cellType) {
3357 case A00:
3358 return "A ";
3359 case B1X:
3360 return "B1X";
3361 case B1Y:
3362 return "B1Y";
3363 case B2X:
3364 return "B2X";
3365 case B2Y:
3366 return "B2Y";
3367 case C10:
3368 return "C1 ";
3369 case C2X:
3370 return "C2X";
3371 case C2Y:
3372 return "C2Y";
3373 case C30:
3374 return "C3 ";
3375 case D10:
3376 return "D1 ";
3377 case D20:
3378 return "D2 ";
3379 case D30:
3380 return "D3 ";
3381 case D40:
3382 return "D4 ";
3383 default:
3384 break;
3385 }
3386 return "Unknown";
3387}
3388
3389bool ComponentAnalyticField::PrepareStrips() {
3390 // -----------------------------------------------------------------------
3391 // CELSTR - Assigns default anode-cathode gaps, if applicable.
3392 // (Last changed on 7/12/00.)
3393 // -----------------------------------------------------------------------
3394
3395 double gapDef[4] = {0., 0., 0., 0.};
3396
3397 // Compute default gaps.
3398 if (m_ynplan[0]) {
3399 if (m_ynplan[1]) {
3400 gapDef[0] = m_coplan[1] - m_coplan[0];
3401 } else if (m_nWires <= 0) {
3402 gapDef[0] = -1.;
3403 } else {
3404 gapDef[0] = m_w[0].x - m_coplan[0];
3405 for (const auto& wire : m_w) {
3406 gapDef[0] = std::min(wire.x - m_coplan[0], gapDef[0]);
3407 }
3408 }
3409 }
3410
3411 if (m_ynplan[1]) {
3412 if (m_ynplan[0]) {
3413 gapDef[1] = m_coplan[1] - m_coplan[0];
3414 } else if (m_nWires <= 0) {
3415 gapDef[1] = -1.;
3416 } else {
3417 gapDef[1] = m_coplan[1] - m_w[0].x;
3418 for (const auto& wire : m_w) {
3419 gapDef[1] = std::min(m_coplan[1] - wire.x, gapDef[1]);
3420 }
3421 }
3422 }
3423
3424 if (m_ynplan[2]) {
3425 if (m_ynplan[3]) {
3426 gapDef[2] = m_coplan[3] - m_coplan[2];
3427 } else if (m_nWires <= 0) {
3428 gapDef[2] = -1.;
3429 } else {
3430 gapDef[2] = m_w[0].y - m_coplan[2];
3431 for (const auto& wire : m_w) {
3432 gapDef[2] = std::min(wire.y - m_coplan[2], gapDef[2]);
3433 }
3434 }
3435 }
3436
3437 if (m_ynplan[3]) {
3438 if (m_ynplan[2]) {
3439 gapDef[3] = m_coplan[3] - m_coplan[2];
3440 } else if (m_nWires <= 0) {
3441 gapDef[3] = -1.;
3442 } else {
3443 gapDef[3] = m_coplan[3] - m_w[0].y;
3444 for (const auto& wire : m_w) {
3445 gapDef[3] = std::min(m_coplan[3] - wire.y, gapDef[3]);
3446 }
3447 }
3448 }
3449
3450 // Assign.
3451 for (unsigned int i = 0; i < 4; ++i) {
3452 for (auto& strip : m_planes[i].strips1) {
3453 if (strip.gap < 0. && gapDef[i] < 0.) {
3454 std::cerr << m_className << "::PrepareStrips:\n"
3455 << " Not able to set a default anode-cathode gap\n";
3456 if (m_polar) {
3457 std::cerr << " for r/phi-strips of plane " << i << ".\n";
3458 } else {
3459 std::cerr << " for x/y-strips of plane " << i << ".\n";
3460 }
3461 return false;
3462 }
3463 if (strip.gap < 0.) {
3464 strip.gap = gapDef[i];
3465 } else if (m_polar && i < 2) {
3466 if (i == 0) {
3467 strip.gap = log1p(strip.gap / exp(m_coplan[i]));
3468 } else {
3469 strip.gap = -log1p(-strip.gap / exp(m_coplan[i]));
3470 }
3471 }
3472 }
3473 for (auto& strip : m_planes[i].strips2) {
3474 if (strip.gap < 0. && gapDef[i] < 0.) {
3475 std::cerr << m_className << "::PrepareStrips:\n"
3476 << " Not able to set a default anode-cathode gap\n"
3477 << " for z-strips of plane " << i << ".\n";
3478 return false;
3479 }
3480 if (strip.gap < 0.) {
3481 strip.gap = gapDef[i];
3482 } else if (m_polar && i < 2) {
3483 if (i == 0) {
3484 strip.gap = log1p(strip.gap / exp(m_coplan[i]));
3485 } else {
3486 strip.gap = -log1p(-strip.gap / exp(m_coplan[i]));
3487 }
3488 }
3489 }
3490 for (auto& pixel : m_planes[i].pixels) {
3491 if (pixel.gap < 0. && gapDef[i] < 0.) {
3492 std::cerr << m_className << "::PrepareStrips:\n"
3493 << " Not able to set a default anode-cathode gap\n"
3494 << " for pixels on plane " << i << ".\n";
3495 return false;
3496 }
3497 if (pixel.gap < 0.) {
3498 pixel.gap = gapDef[i];
3499 } else if (m_polar && i < 2) {
3500 if (i == 0) {
3501 pixel.gap = log1p(pixel.gap / exp(m_coplan[i]));
3502 } else {
3503 pixel.gap = -log1p(-pixel.gap / exp(m_coplan[i]));
3504 }
3505 }
3506 }
3507 }
3508
3509 return true;
3510}
3511
3512void ComponentAnalyticField::AddReadout(const std::string& label) {
3513 // Check if this readout group already exists.
3514 if (std::find(m_readout.begin(), m_readout.end(), label) != m_readout.end()) {
3515 std::cout << m_className << "::AddReadout:\n";
3516 std::cout << " Readout group " << label << " already exists.\n";
3517 return;
3518 }
3519 m_readout.push_back(label);
3520
3521 unsigned int nWiresFound = 0;
3522 for (const auto& wire : m_w) {
3523 if (wire.type == label) ++nWiresFound;
3524 }
3525
3526 unsigned int nPlanesFound = 0;
3527 unsigned int nStripsFound = 0;
3528 unsigned int nPixelsFound = 0;
3529 for (int i = 0; i < 5; ++i) {
3530 if (m_planes[i].type == label) ++nPlanesFound;
3531 for (const auto& strip : m_planes[i].strips1) {
3532 if (strip.type == label) ++nStripsFound;
3533 }
3534 for (const auto& strip : m_planes[i].strips2) {
3535 if (strip.type == label) ++nStripsFound;
3536 }
3537 for (const auto& pixel : m_planes[i].pixels) {
3538 if (pixel.type == label) ++nPixelsFound;
3539 }
3540 }
3541
3542 if (nWiresFound == 0 && nPlanesFound == 0 && nStripsFound == 0 &&
3543 nPixelsFound == 0) {
3544 std::cerr << m_className << "::AddReadout:\n";
3545 std::cerr << " At present there are no wires, planes or strips\n";
3546 std::cerr << " associated to readout group " << label << ".\n";
3547 } else {
3548 std::cout << m_className << "::AddReadout:\n";
3549 std::cout << " Readout group " << label << " comprises:\n";
3550 if (nWiresFound > 1) {
3551 std::cout << " " << nWiresFound << " wires\n";
3552 } else if (nWiresFound == 1) {
3553 std::cout << " 1 wire\n";
3554 }
3555 if (nPlanesFound > 1) {
3556 std::cout << " " << nPlanesFound << " planes\n";
3557 } else if (nPlanesFound == 1) {
3558 std::cout << " 1 plane\n";
3559 }
3560 if (nStripsFound > 1) {
3561 std::cout << " " << nStripsFound << " strips\n";
3562 } else if (nStripsFound == 1) {
3563 std::cout << " 1 strip\n";
3564 }
3565 if (nPixelsFound > 1) {
3566 std::cout << " " << nPixelsFound << " pixels\n";
3567 } else if (nPixelsFound == 1) {
3568 std::cout << " 1 pixel\n";
3569 }
3570 }
3571
3572 m_sigset = false;
3573}
3574
3575bool ComponentAnalyticField::Setup() {
3576 //-----------------------------------------------------------------------
3577 // SETUP - Routine calling the appropriate setup routine.
3578 // (Last changed on 19/ 9/07.)
3579 //-----------------------------------------------------------------------
3580
3581 // Set a separate set of plane variables to avoid repeated loops.
3582 if (m_ynplan[0]) {
3583 m_coplax = m_coplan[0];
3584 m_ynplax = true;
3585 } else if (m_ynplan[1]) {
3586 m_coplax = m_coplan[1];
3587 m_ynplax = true;
3588 } else {
3589 m_ynplax = false;
3590 }
3591
3592 if (m_ynplan[2]) {
3593 m_coplay = m_coplan[2];
3594 m_ynplay = true;
3595 } else if (m_ynplan[3]) {
3596 m_coplay = m_coplan[3];
3597 m_ynplay = true;
3598 } else {
3599 m_ynplay = false;
3600 }
3601
3602 // Set the correction parameters for the planes.
3603 if (m_tube) {
3604 m_corvta = 0.;
3605 m_corvtb = 0.;
3606 m_corvtc = m_vttube;
3607 } else if ((m_ynplan[0] && m_ynplan[1]) && !(m_ynplan[2] || m_ynplan[3])) {
3608 m_corvta = (m_vtplan[0] - m_vtplan[1]) / (m_coplan[0] - m_coplan[1]);
3609 m_corvtb = 0.;
3610 m_corvtc = (m_vtplan[1] * m_coplan[0] - m_vtplan[0] * m_coplan[1]) /
3611 (m_coplan[0] - m_coplan[1]);
3612 } else if ((m_ynplan[2] && m_ynplan[3]) && !(m_ynplan[0] || m_ynplan[1])) {
3613 m_corvta = 0.;
3614 m_corvtb = (m_vtplan[2] - m_vtplan[3]) / (m_coplan[2] - m_coplan[3]);
3615 m_corvtc = (m_vtplan[3] * m_coplan[2] - m_vtplan[2] * m_coplan[3]) /
3616 (m_coplan[2] - m_coplan[3]);
3617 } else {
3618 m_corvta = m_corvtb = m_corvtc = 0.;
3619 if (m_ynplan[0]) m_corvtc = m_vtplan[0];
3620 if (m_ynplan[1]) m_corvtc = m_vtplan[1];
3621 if (m_ynplan[2]) m_corvtc = m_vtplan[2];
3622 if (m_ynplan[3]) m_corvtc = m_vtplan[3];
3623 }
3624
3625 // Skip wire calculations if there aren't any.
3626 if (m_nWires <= 0) return true;
3627
3628 // Redimension the capacitance matrix
3629 m_a.assign(m_nWires, std::vector<double>(m_nWires, 0.));
3630
3631 bool ok = true;
3632
3633 // Call the set routine appropriate for the present cell type.
3634 switch (m_cellType) {
3635 case A00:
3636 ok = SetupA00();
3637 break;
3638 case B1X:
3639 ok = SetupB1X();
3640 break;
3641 case B1Y:
3642 ok = SetupB1Y();
3643 break;
3644 case B2X:
3645 ok = SetupB2X();
3646 break;
3647 case B2Y:
3648 ok = SetupB2Y();
3649 break;
3650 case C10:
3651 ok = SetupC10();
3652 break;
3653 case C2X:
3654 ok = SetupC2X();
3655 break;
3656 case C2Y:
3657 ok = SetupC2Y();
3658 break;
3659 case C30:
3660 ok = SetupC30();
3661 break;
3662 case D10:
3663 ok = SetupD10();
3664 break;
3665 case D20:
3666 ok = SetupD20();
3667 break;
3668 case D30:
3669 ok = SetupD30();
3670 break;
3671 default:
3672 std::cerr << m_className << "::Setup: Unknown cell type.\n";
3673 break;
3674 }
3675
3676
3677 m_a.clear();
3678
3679 if (!ok) {
3680 std::cerr << m_className << "::Setup:\n";
3681 std::cerr << " Preparing the cell for field calculations"
3682 << " did not succeed.\n";
3683 return false;
3684 }
3685 return true;
3686}
3687
3688bool ComponentAnalyticField::SetupA00() {
3689 //-----------------------------------------------------------------------
3690 // SETA00 - Subroutine preparing the field calculations by calculating
3691 // the charges on the wires, for the cell with one charge and
3692 // not more than one plane in either x or y.
3693 // The potential used is log(r).
3694 //-----------------------------------------------------------------------
3695
3696 // Loop over all wire combinations.
3697 for (unsigned int i = 0; i < m_nWires; ++i) {
3698 m_a[i][i] = m_w[i].r * m_w[i].r;
3699 // Take care of the equipotential planes.
3700 if (m_ynplax) m_a[i][i] /= 4. * pow(m_w[i].x - m_coplax, 2);
3701 if (m_ynplay) m_a[i][i] /= 4. * pow(m_w[i].y - m_coplay, 2);
3702 // Take care of combinations of equipotential planes.
3703 if (m_ynplax && m_ynplay)
3704 m_a[i][i] *=
3705 4.0 * (pow(m_w[i].x - m_coplax, 2) + pow(m_w[i].y - m_coplay, 2));
3706 // Define the final version of a[i][i].
3707 m_a[i][i] = -0.5 * log(m_a[i][i]);
3708 // Loop over all other wires for the off-diagonal elements.
3709 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3710 m_a[i][j] = pow(m_w[i].x - m_w[j].x, 2) + pow(m_w[i].y - m_w[j].y, 2);
3711 // Take care of equipotential planes.
3712 if (m_ynplax)
3713 m_a[i][j] = m_a[i][j] / (pow(m_w[i].x + m_w[j].x - 2. * m_coplax, 2) +
3714 pow(m_w[i].y - m_w[j].y, 2));
3715 if (m_ynplay)
3716 m_a[i][j] = m_a[i][j] / (pow(m_w[i].x - m_w[j].x, 2) +
3717 pow(m_w[i].y + m_w[j].y - 2. * m_coplay, 2));
3718 // Take care of pairs of equipotential planes in different directions.
3719 if (m_ynplax && m_ynplay)
3720 m_a[i][j] *= pow(m_w[i].x + m_w[j].x - 2. * m_coplax, 2) +
3721 pow(m_w[i].y + m_w[j].y - 2. * m_coplay, 2);
3722 // Define a final version of a[i][j].
3723 m_a[i][j] = -0.5 * log(m_a[i][j]);
3724 // Copy this to a[j][i] since the capacitance matrix is symmetric.
3725 m_a[j][i] = m_a[i][j];
3726 }
3727 }
3728 // Call CHARGE to calculate the charges really.
3729 return Charge();
3730}
3731
3732bool ComponentAnalyticField::SetupB1X() {
3733 //-----------------------------------------------------------------------
3734 // SETB1X - Routine preparing the field calculations by filling the
3735 // c-matrix, the potential used is re(log(sin Pi/s (z-z0))).
3736 // VARIABLES : xx : Difference in x of two wires * factor.
3737 // yy : Difference in y of two wires * factor.
3738 // yymirr : Difference in y of one wire and the mirror
3739 // image of another * factor.
3740 // r2plan : Periodic length of (xx,yymirr)
3741 //-----------------------------------------------------------------------
3742
3743 double xx = 0., yy = 0., yymirr = 0.;
3744 double r2plan = 0.;
3745
3746 // Loop over all wires and calculate the diagonal elements first.
3747 for (unsigned int i = 0; i < m_nWires; ++i) {
3748 m_a[i][i] = -log(m_w[i].r * Pi / m_sx);
3749 // Take care of a plane at constant y if it exist.
3750 if (m_ynplay) {
3751 yy = (Pi / m_sx) * 2. * (m_w[i].y - m_coplay);
3752 if (fabs(yy) > 20.) m_a[i][i] += fabs(yy) - CLog2;
3753 if (fabs(yy) <= 20.) m_a[i][i] += log(fabs(sinh(yy)));
3754 }
3755 // Loop over all other wires to obtain off-diagonal elements.
3756 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3757 xx = (Pi / m_sx) * (m_w[i].x - m_w[j].x);
3758 yy = (Pi / m_sx) * (m_w[i].y - m_w[j].y);
3759 if (fabs(yy) > 20.) m_a[i][j] = -fabs(yy) + CLog2;
3760 if (fabs(yy) <= 20.) {
3761 const double sinhy = sinh(yy);
3762 const double sinx = sin(xx);
3763 m_a[i][j] = -0.5 * log(sinhy * sinhy + sinx * sinx);
3764 }
3765 // Take equipotential planes into account if they exist.
3766 if (m_ynplay) {
3767 r2plan = 0.;
3768 yymirr = (Pi / m_sx) * (m_w[i].y + m_w[j].y - 2. * m_coplay);
3769 if (fabs(yymirr) > 20.) r2plan = fabs(yymirr) - CLog2;
3770 if (fabs(yymirr) <= 20.) {
3771 const double sinhy = sinh(yymirr);
3772 const double sinx = sin(xx);
3773 r2plan = 0.5 * log(sinhy * sinhy + sinx * sinx);
3774 }
3775 m_a[i][j] += r2plan;
3776 }
3777 // Copy a[i][j] to a[j][i], the capactance matrix is symmetric.
3778 m_a[j][i] = m_a[i][j];
3779 }
3780 }
3781 // Call function CHARGE calculating all kinds of useful things.
3782 return Charge();
3783}
3784
3785bool ComponentAnalyticField::SetupB1Y() {
3786 //-----------------------------------------------------------------------
3787 // SETB1Y - Routine preparing the field calculations by setting the
3788 // charges. The potential used is Re log(sinh Pi/sy(z-z0)).
3789 // VARIABLES : yy : Difference in y of two wires * factor.
3790 // xxmirr : Difference in x of one wire and the mirror
3791 // image of another * factor.
3792 // r2plan : Periodic length of (xxmirr,yy).
3793 //-----------------------------------------------------------------------
3794
3795 double xx = 0., yy = 0., xxmirr = 0.;
3796 double r2plan = 0.;
3797
3798 // Loop over all wires and calculate the diagonal elements first.
3799 for (unsigned int i = 0; i < m_nWires; ++i) {
3800 m_a[i][i] = -log(m_w[i].r * Pi / m_sy);
3801 // Take care of planes 1 and 2 if present.
3802 if (m_ynplax) {
3803 xx = (Pi / m_sy) * 2. * (m_w[i].x - m_coplax);
3804 if (fabs(xx) > 20.) m_a[i][i] += fabs(xx) - CLog2;
3805 if (fabs(xx) <= 20.) m_a[i][i] += log(fabs(sinh(xx)));
3806 }
3807 // Loop over all other wires to obtain off-diagonal elements.
3808 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3809 xx = (Pi / m_sy) * (m_w[i].x - m_w[j].x);
3810 yy = (Pi / m_sy) * (m_w[i].y - m_w[j].y);
3811 if (fabs(xx) > 20.) m_a[i][j] = -fabs(xx) + CLog2;
3812 if (fabs(xx) <= 20.) {
3813 const double sinhx = sinh(xx);
3814 const double siny = sin(yy);
3815 m_a[i][j] = -0.5 * log(sinhx * sinhx + siny * siny);
3816 }
3817 // Take care of a plane at constant x.
3818 if (m_ynplax) {
3819 xxmirr = (Pi / m_sy) * (m_w[i].x + m_w[j].x - 2. * m_coplax);
3820 r2plan = 0.;
3821 if (fabs(xxmirr) > 20.) r2plan = fabs(xxmirr) - CLog2;
3822 if (fabs(xxmirr) <= 20.) {
3823 const double sinhx = sinh(xxmirr);
3824 const double siny = sin(yy);
3825 r2plan = 0.5 * log(sinhx * sinhx + siny * siny);
3826 }
3827 m_a[i][j] += r2plan;
3828 }
3829 // Copy a[i][j] to a[j][i], the capacitance matrix is symmetric.
3830 m_a[j][i] = m_a[i][j];
3831 }
3832 }
3833 // Call function CHARGE calculating all kinds of useful things.
3834 return Charge();
3835}
3836
3837bool ComponentAnalyticField::SetupB2X() {
3838 //-----------------------------------------------------------------------
3839 // SETB2X - Routine preparing the field calculations by setting the
3840 // charges.
3841 // VARIABLES : xx : Difference in x of two wires * factor.
3842 // yy : Difference in y of two wires * factor.
3843 // xxneg : Difference in x of one wire and the mirror
3844 // image in period direction of another * fac.
3845 // yymirr : Difference in y of one wire and the mirror
3846 // image of another * factor.
3847 //-----------------------------------------------------------------------
3848
3849 m_b2sin.resize(m_nWires);
3850
3851 // Loop over all wires and calculate the diagonal elements first.
3852 for (unsigned int i = 0; i < m_nWires; ++i) {
3853 double xx = (Pi / m_sx) * (m_w[i].x - m_coplax);
3854 m_a[i][i] = (0.5 * m_w[i].r * Pi / m_sx) / sin(xx);
3855 // Take care of a plane at constant y if it exists.
3856 if (m_ynplay) {
3857 const double yymirr = (Pi / m_sx) * (m_w[i].y - m_coplay);
3858 if (fabs(yymirr) <= 20.) {
3859 const double sinhy = sinh(yymirr);
3860 const double sinx = sin(xx);
3861 m_a[i][i] *= sqrt(sinhy * sinhy + sinx * sinx) / sinhy;
3862 }
3863 }
3864 // Store the true value of a[i][i].
3865 m_a[i][i] = -log(fabs(m_a[i][i]));
3866 // Loop over all other wires to obtain off-diagonal elements.
3867 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3868 xx = HalfPi * (m_w[i].x - m_w[j].x) / m_sx;
3869 const double yy = HalfPi * (m_w[i].y - m_w[j].y) / m_sx;
3870 const double xxneg =
3871 HalfPi * (m_w[i].x + m_w[j].x - 2. * m_coplax) / m_sx;
3872 if (fabs(yy) <= 20.) {
3873 const double sinhy = sinh(yy);
3874 const double sinxx = sin(xx);
3875 const double sinxxneg = sin(xxneg);
3876 m_a[i][j] = (sinhy * sinhy + sinxx * sinxx) /
3877 (sinhy * sinhy + sinxxneg * sinxxneg);
3878 }
3879 if (fabs(yy) > 20.) m_a[i][j] = 1.0;
3880 // Take an equipotential plane at constant y into account.
3881 if (m_ynplay) {
3882 const double yymirr =
3883 HalfPi * (m_w[i].y + m_w[j].y - 2. * m_coplay) / m_sx;
3884 if (fabs(yymirr) <= 20.) {
3885 const double sinhy = sinh(yymirr);
3886 const double sinxx = sin(xx);
3887 const double sinxxneg = sin(xxneg);
3888 m_a[i][j] *= (sinhy * sinhy + sinxxneg * sinxxneg) /
3889 (sinhy * sinhy + sinxx * sinxx);
3890 }
3891 }
3892 // Store the true value of a[i][j] in both a[i][j] and a[j][i].
3893 m_a[i][j] = -0.5 * log(m_a[i][j]);
3894 m_a[j][i] = m_a[i][j];
3895 }
3896 // Set the b2sin vector.
3897 m_b2sin[i] = sin(Pi * (m_coplax - m_w[i].x) / m_sx);
3898 }
3899 // Call function CHARGE calculating all kinds of useful things.
3900 return Charge();
3901}
3902
3903bool ComponentAnalyticField::SetupB2Y() {
3904 //-----------------------------------------------------------------------
3905 // SETB2Y - Routine preparing the field calculations by setting the
3906 // charges.
3907 // VARIABLES : xx : Difference in x of two wires * factor.
3908 // yy : Difference in y of two wires * factor.
3909 // xxmirr : Difference in x of one wire and the mirror
3910 // image of another * factor.
3911 // yyneg : Difference in y of one wire and the mirror
3912 // image in period direction of another * fac.
3913 //-----------------------------------------------------------------------
3914
3915 m_b2sin.resize(m_nWires);
3916
3917 // Loop over all wires and calculate the diagonal elements first.
3918 for (unsigned int i = 0; i < m_nWires; ++i) {
3919 double yy = (Pi / m_sy) * (m_w[i].y - m_coplay);
3920 m_a[i][i] = (0.5 * m_w[i].r * Pi / m_sy) / sin(yy);
3921 // Take care of a plane at constant x if present.
3922 if (m_ynplax) {
3923 const double xxmirr = (Pi / m_sy) * (m_w[i].x - m_coplax);
3924 if (fabs(xxmirr) <= 20.) {
3925 const double sinhx = sinh(xxmirr);
3926 const double sinyy = sin(yy);
3927 m_a[i][i] *= sqrt(sinhx * sinhx + sinyy * sinyy) / sinhx;
3928 }
3929 }
3930 // Store the true value of a[i][i].
3931 m_a[i][i] = -log(fabs(m_a[i][i]));
3932 // Loop over all other wires to obtain off-diagonal elements.
3933 for (unsigned int j = i + 1; j < m_nWires; j++) {
3934 const double xx = HalfPi * (m_w[i].x - m_w[j].x) / m_sy;
3935 yy = HalfPi * (m_w[i].y - m_w[j].y) / m_sy;
3936 const double yyneg =
3937 HalfPi * (m_w[i].y + m_w[j].y - 2. * m_coplay) / m_sy;
3938 if (fabs(xx) <= 20.) {
3939 const double sinhx = sinh(xx);
3940 const double sinyy = sin(yy);
3941 const double sinyyneg = sin(yyneg);
3942 m_a[i][j] = (sinhx * sinhx + sinyy * sinyy) /
3943 (sinhx * sinhx + sinyyneg * sinyyneg);
3944 }
3945 if (fabs(xx) > 20.) m_a[i][j] = 1.0;
3946 // Take an equipotential plane at constant x into account.
3947 if (m_ynplax) {
3948 const double xxmirr =
3949 HalfPi * (m_w[i].x + m_w[j].x - 2. * m_coplax) / m_sy;
3950 if (fabs(xxmirr) <= 20.) {
3951 const double sinhx = sinh(xxmirr);
3952 const double sinyy = sin(yy);
3953 const double sinyyneg = sin(yyneg);
3954 m_a[i][j] *= (sinhx * sinhx + sinyyneg * sinyyneg) /
3955 (sinhx * sinhx + sinyy * sinyy);
3956 }
3957 }
3958 // Store the true value of a[i][j] in both a[i][j] and a[j][i].
3959 m_a[i][j] = -0.5 * log(m_a[i][j]);
3960 m_a[j][i] = m_a[i][j];
3961 }
3962 // Set the b2sin vector.
3963 m_b2sin[i] = sin(Pi * (m_coplay - m_w[i].y) / m_sy);
3964 }
3965 // Call function CHARGE calculating all kinds of useful things.
3966 return Charge();
3967}
3968
3969bool ComponentAnalyticField::SetupC10() {
3970 //-----------------------------------------------------------------------
3971 // SETC10 - This initialising routine computes the wire charges E and
3972 // sets certain constants in common. The wire are located at
3973 // (x[j],y[j])+(LX*SX,LY*SY), J=1(1)NWIRE,
3974 // LX=-infinity(1)infinity, LY=-infinity(1)infinity.
3975 // Use is made of the function PH2.
3976 //
3977 // (Written by G.A.Erskine/DD, 14.8.1984 modified to some extent)
3978 //-----------------------------------------------------------------------
3979
3980 // Initialise the constants.
3981 double p = 0.;
3982 m_p1 = m_p2 = 0.;
3983
3984 m_mode = 0;
3985 if (m_sx <= m_sy) {
3986 m_mode = 1;
3987 if (m_sy / m_sx < 8.) p = exp(-Pi * m_sy / m_sx);
3988 m_zmult = std::complex<double>(Pi / m_sx, 0.);
3989 } else {
3990 m_mode = 0;
3991 if (m_sx / m_sy < 8.) p = exp(-Pi * m_sx / m_sy);
3992 m_zmult = std::complex<double>(0., Pi / m_sy);
3993 }
3994 m_p1 = p * p;
3995 if (m_p1 > 1.e-10) m_p2 = pow(p, 6);
3996
3997 if (m_debug) {
3998 std::cout << m_className << "::SetupC10:\n";
3999 std::cout << " p, p1, p2 = " << p << ", " << m_p1 << ", " << m_p2
4000 << "\n";
4001 std::cout << " zmult = " << m_zmult << "\n";
4002 std::cout << " mode = " << m_mode << "\n";
4003 }
4004
4005 // Store the capacitance matrix.
4006 for (unsigned int i = 0; i < m_nWires; ++i) {
4007 for (unsigned int j = 0; j < m_nWires; ++j) {
4008 const double xyi = m_mode == 0 ? m_w[i].x : m_w[i].y;
4009 const double xyj = m_mode == 0 ? m_w[j].x : m_w[j].y;
4010 const double temp = xyi * xyj * TwoPi / (m_sx * m_sy);
4011 if (i == j) {
4012 m_a[i][j] = Ph2Lim(m_w[i].r) - temp;
4013 } else {
4014 m_a[i][j] = Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) - temp;
4015 }
4016 }
4017 }
4018 // Call CHARGE to find the charges.
4019 if (!Charge()) return false;
4020 // Calculate the non-logarithmic term in the potential.
4021 double s = 0.;
4022 for (unsigned int j = 0; j < m_nWires; ++j) {
4023 const double xyj = m_mode == 0 ? m_w[j].x : m_w[j].y;
4024 s += m_w[j].e * xyj;
4025 }
4026 m_c1 = -s * 2. * Pi / (m_sx * m_sy);
4027 return true;
4028}
4029
4030bool ComponentAnalyticField::SetupC2X() {
4031 //-----------------------------------------------------------------------
4032 // SETC2X - This initializing subroutine stores the capacitance matrix
4033 // for the configuration:
4034 // wires at zw(j)+cmplx(lx*2*sx,ly*sy),
4035 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
4036 // but the signs of the charges alternate in the x-direction
4037 //-----------------------------------------------------------------------
4038
4039 // Initialise the constants.
4040 double p = 0.;
4041 m_p1 = m_p2 = 0.;
4042
4043 m_mode = 0;
4044 if (2. * m_sx <= m_sy) {
4045 m_mode = 1;
4046 if (m_sy / m_sx < 25.) p = exp(-HalfPi * m_sy / m_sx);
4047 m_zmult = std::complex<double>(HalfPi / m_sx, 0.);
4048 } else {
4049 m_mode = 0;
4050 if (m_sx / m_sy < 6.) p = exp(-2. * Pi * m_sx / m_sy);
4051 m_zmult = std::complex<double>(0., Pi / m_sy);
4052 }
4053 m_p1 = p * p;
4054 if (m_p1 > 1.e-10) m_p2 = pow(p, 6);
4055
4056 if (m_debug) {
4057 std::cout << m_className << "::SetupC2X:\n";
4058 std::cout << " p, p1, p2 = " << p << ", " << m_p1 << ", " << m_p2
4059 << "\n";
4060 std::cout << " zmult = " << m_zmult << "\n";
4061 std::cout << " mode = " << m_mode << "\n";
4062 }
4063
4064 // Fill the capacitance matrix.
4065 for (unsigned int i = 0; i < m_nWires; ++i) {
4066 const double cx =
4067 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
4068 for (unsigned int j = 0; j < m_nWires; ++j) {
4069 double temp = 0.;
4070 if (m_mode == 0) {
4071 temp = (m_w[i].x - cx) * (m_w[j].x - cx) * TwoPi / (m_sx * m_sy);
4072 }
4073 if (i == j) {
4074 m_a[i][i] =
4075 Ph2Lim(m_w[i].r) - Ph2(2. * (m_w[i].x - cx), 0.) - temp;
4076 } else {
4077 m_a[i][j] = Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
4078 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y - m_w[j].y) -
4079 temp;
4080 }
4081 }
4082 }
4083 // Call CHARGE to find the wire charges.
4084 if (!Charge()) return false;
4085 // Determine the non-logarithmic part of the potential (0 if MODE=1).
4086 m_c1 = 0.;
4087 if (m_mode == 0) {
4088 double s = 0.;
4089 for (unsigned int i = 0; i < m_nWires; ++i) {
4090 const double cx =
4091 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
4092 s += m_w[i].e * (m_w[i].x - cx);
4093 }
4094 m_c1 = -s * TwoPi / (m_sx * m_sy);
4095 }
4096 return true;
4097}
4098
4099bool ComponentAnalyticField::SetupC2Y() {
4100 //-----------------------------------------------------------------------
4101 // SETC2Y - This initializing subroutine stores the capacitance matrix
4102 // for the configuration:
4103 // wires at zw(j)+cmplx(lx*sx,ly*2*sy),
4104 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
4105 // but the signs of the charges alternate in the y-direction
4106 //-----------------------------------------------------------------------
4107
4108 // Initialise the constants.
4109 double p = 0.;
4110 m_p1 = m_p2 = 0.;
4111
4112 m_mode = 0;
4113 if (m_sx <= 2. * m_sy) {
4114 m_mode = 1;
4115 if (m_sy / m_sx <= 6.) p = exp(-2. * Pi * m_sy / m_sx);
4116 m_zmult = std::complex<double>(Pi / m_sx, 0.);
4117 } else {
4118 m_mode = 0;
4119 if (m_sx / m_sy <= 25.) p = exp(-HalfPi * m_sx / m_sy);
4120 m_zmult = std::complex<double>(0., HalfPi / m_sy);
4121 }
4122 m_p1 = p * p;
4123 if (m_p1 > 1.e-10) m_p2 = pow(p, 6);
4124
4125 if (m_debug) {
4126 std::cout << m_className << "::SetupC2Y:\n";
4127 std::cout << " p, p1, p2 = " << p << ", " << m_p1 << ", " << m_p2
4128 << "\n";
4129 std::cout << " zmult = " << m_zmult << "\n";
4130 std::cout << " mode = " << m_mode << "\n";
4131 }
4132
4133 // Fill the capacitance matrix.
4134 for (unsigned int i = 0; i < m_nWires; ++i) {
4135 const double cy =
4136 m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
4137 for (unsigned int j = 0; j < m_nWires; ++j) {
4138 double temp = 0.;
4139 if (m_mode == 1) {
4140 temp = (m_w[i].y - cy) * (m_w[j].y - cy) * TwoPi / (m_sx * m_sy);
4141 }
4142 if (i == j) {
4143 m_a[i][i] =
4144 Ph2Lim(m_w[i].r) - Ph2(0., 2. * (m_w[j].y - cy)) - temp;
4145 } else {
4146 m_a[i][j] = Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
4147 Ph2(m_w[i].x - m_w[j].x, m_w[i].y + m_w[j].y - 2. * cy) -
4148 temp;
4149 }
4150 }
4151 }
4152 // Call CHARGE to find the wire charges.
4153 if (!Charge()) return false;
4154 // The non-logarithmic part of the potential is zero if MODE=0.
4155 m_c1 = 0.;
4156 if (m_mode == 1) {
4157 double s = 0.;
4158 for (unsigned int i = 0; i < m_nWires; ++i) {
4159 const double cy =
4160 m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
4161 s += m_w[i].e * (m_w[i].y - cy);
4162 }
4163 m_c1 = -s * TwoPi / (m_sx * m_sy);
4164 }
4165 return true;
4166}
4167
4168bool ComponentAnalyticField::SetupC30() {
4169 //-----------------------------------------------------------------------
4170 // SETC30 - This initializing subroutine stores the capacitance matrix
4171 // for a configuration with
4172 // wires at zw(j)+cmplx(lx*2*sx,ly*2*sy),
4173 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
4174 // but the signs of the charges alternate in both directions.
4175 //-----------------------------------------------------------------------
4176
4177 // Initialise the constants.
4178 double p = 0.;
4179 m_p1 = m_p2 = 0.;
4180
4181 m_mode = 0;
4182 if (m_sx <= m_sy) {
4183 m_mode = 1;
4184 if (m_sy / m_sx <= 13.) p = exp(-Pi * m_sy / m_sx);
4185 m_zmult = std::complex<double>(HalfPi / m_sx, 0.);
4186 } else {
4187 m_mode = 0;
4188 if (m_sx / m_sy <= 13.) p = exp(-Pi * m_sx / m_sy);
4189 m_zmult = std::complex<double>(0., HalfPi / m_sy);
4190 }
4191 m_p1 = p * p;
4192 if (m_p1 > 1.e-10) m_p2 = pow(p, 6);
4193
4194 if (m_debug) {
4195 std::cout << m_className << "::SetupC30:\n";
4196 std::cout << " p, p1, p2 = " << p << ", " << m_p1 << ", " << m_p2
4197 << "\n";
4198 std::cout << " zmult = " << m_zmult << "\n";
4199 std::cout << " mode = " << m_mode << "\n";
4200 }
4201
4202 // Fill the capacitance matrix.
4203 for (unsigned int i = 0; i < m_nWires; ++i) {
4204 double cx = m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
4205 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
4206 for (unsigned int j = 0; j < m_nWires; ++j) {
4207 if (i == j) {
4208 m_a[i][i] = Ph2Lim(m_w[i].r) - Ph2(0., 2. * (m_w[i].y - cy)) -
4209 Ph2(2. * (m_w[i].x - cx), 0.) +
4210 Ph2(2. * (m_w[i].x - cx), 2. * (m_w[i].y - cy));
4211 } else {
4212 m_a[i][j] =
4213 Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
4214 Ph2(m_w[i].x - m_w[j].x, m_w[i].y + m_w[j].y - 2. * cy) -
4215 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y - m_w[j].y) +
4216 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y + m_w[j].y - 2. * cy);
4217 }
4218 }
4219 }
4220 // Call CHARGE to find the wire charges.
4221 if (!Charge()) return false;
4222 // The non-logarithmic part of the potential is zero in this case.
4223 m_c1 = 0.;
4224 return true;
4225}
4226
4227bool ComponentAnalyticField::SetupD10() {
4228 //-----------------------------------------------------------------------
4229 // SETD10 - Subroutine preparing the field calculations by calculating
4230 // the charges on the wires, for cells with a tube.
4231 //
4232 // (Last changed on 4/ 9/95.)
4233 //-----------------------------------------------------------------------
4234
4235 // Loop over all wires.
4236 for (unsigned int i = 0; i < m_nWires; ++i) {
4237 // Set the diagonal terms.
4238 m_a[i][i] = -log(m_w[i].r * m_cotube /
4239 (m_cotube2 - (m_w[i].x * m_w[i].x + m_w[i].y * m_w[i].y)));
4240 // Set a complex wire-coordinate to make things a little easier.
4241 std::complex<double> zi(m_w[i].x, m_w[i].y);
4242 // Loop over all other wires for the off-diagonal elements.
4243 for (unsigned int j = i + 1; j < m_nWires; ++j) {
4244 // Set a complex wire-coordinate to make things a little easier.
4245 std::complex<double> zj(m_w[j].x, m_w[j].y);
4246 m_a[i][j] = -log(abs(m_cotube * (zi - zj) / (m_cotube2 - conj(zi) * zj)));
4247 // Copy this to a[j][i] since the capacitance matrix is symmetric.
4248 m_a[j][i] = m_a[i][j];
4249 }
4250 }
4251 // Call CHARGE to calculate the charges really.
4252 return Charge();
4253}
4254
4255bool ComponentAnalyticField::SetupD20() {
4256 //-----------------------------------------------------------------------
4257 // SETD20 - Subroutine preparing the field calculations by calculating
4258 // the charges on the wires, for cells with a tube and a
4259 // phi periodicity. Assymetric capacitance matrix !
4260 //
4261 // (Last changed on 18/ 2/93.)
4262 //-----------------------------------------------------------------------
4263
4264 // Loop over all wires.
4265 for (unsigned int i = 0; i < m_nWires; ++i) {
4266 // Set a complex wire-coordinate to make things a little easier.
4267 std::complex<double> zi(m_w[i].x, m_w[i].y);
4268 if (abs(zi) < m_w[i].r) {
4269 // Case of a wire near the centre.
4270 // Inner loop over the wires.
4271 for (unsigned int j = 0; j < m_nWires; ++j) {
4272 if (i == j) {
4273 // Set the diagonal terms.
4274 m_a[i][i] =
4275 -log(m_w[i].r /
4276 (m_cotube -
4277 (m_w[i].x * m_w[i].x + m_w[i].y * m_w[i].y) / m_cotube));
4278 } else {
4279 // Off-diagonal terms.
4280 std::complex<double> zj(m_w[j].x, m_w[j].y);
4281 m_a[j][i] = -log(abs((1. / m_cotube) * (zi - zj) /
4282 (1. - conj(zi) * zj / m_cotube2)));
4283 }
4284 }
4285 } else {
4286 // Normal case.
4287 // Inner wire loop.
4288 for (unsigned int j = 0; j < m_nWires; ++j) {
4289 if (i == j) {
4290 // Diagonal elements.
4291 m_a[i][i] =
4292 -log(abs(m_w[i].r * m_mtube * pow(zi, m_mtube - 1) /
4293 (pow(m_cotube, m_mtube) *
4294 (1. - pow((abs(zi) / m_cotube), 2 * m_mtube)))));
4295 } else {
4296 // Off-diagonal terms.
4297 std::complex<double> zj(m_w[j].x, m_w[j].y);
4298 m_a[j][i] = -log(abs((1 / pow(m_cotube, m_mtube)) *
4299 (pow(zj, m_mtube) - pow(zi, m_mtube)) /
4300 (1. - pow(zj * conj(zi) / m_cotube2, m_mtube))));
4301 }
4302 }
4303 }
4304 }
4305 // Call CHARGE to calculate the charges really.
4306 return Charge();
4307}
4308
4309bool ComponentAnalyticField::SetupD30() {
4310 //-----------------------------------------------------------------------
4311 // SETD30 - Subroutine preparing the field calculations by calculating
4312 // the charges on the wires, for cells with wires inside a
4313 // polygon.
4314 //
4315 // (Last changed on 21/ 2/94.)
4316 //-----------------------------------------------------------------------
4317
4318 wmap.assign(m_nWires, std::complex<double>(0., 0.));
4319
4320 std::complex<double> wd = std::complex<double>(0., 0.);
4321
4322 // Evaluate kappa, a constant needed by ConformalMap.
4323 m_kappa = tgamma((m_ntube + 1.) / m_ntube) *
4324 tgamma((m_ntube - 2.) / m_ntube) / tgamma((m_ntube - 1.) / m_ntube);
4325 // Loop over all wire combinations.
4326 for (unsigned int i = 0; i < m_nWires; ++i) {
4327 // Compute wire mappings only once.
4328 ConformalMap(std::complex<double>(m_w[i].x, m_w[i].y) / m_cotube, wmap[i],
4329 wd);
4330 // Diagonal elements.
4331 m_a[i][i] = -log(
4332 abs((m_w[i].r / m_cotube) * wd / (1. - pow(abs(wmap[i]), 2))));
4333 // Loop over all other wires for the off-diagonal elements.
4334 for (unsigned int j = 0; j < i; ++j) {
4335 m_a[i][j] =
4336 -log(abs((wmap[i] - wmap[j]) / (1. - conj(wmap[i]) * wmap[j])));
4337 // Copy this to a[j][i] since the capacitance matrix is symmetric.
4338 m_a[j][i] = m_a[i][j];
4339 }
4340 }
4341 // Call CHARGE to calculate the charges really.
4342 return Charge();
4343}
4344
4345bool ComponentAnalyticField::Charge() {
4346 //-----------------------------------------------------------------------
4347 // CHARGE - Routine actually inverting the capacitance matrix filled in
4348 // the SET... routines thereby providing the charges.
4349 // (Last changed on 30/ 1/93.)
4350 //-----------------------------------------------------------------------
4351
4352 // Transfer the voltages to rhs vector,
4353 // correcting for the equipotential planes.
4354 std::vector<double> b(m_nWires, 0.);
4355 for (unsigned int i = 0; i < m_nWires; ++i) {
4356 b[i] = m_w[i].v - (m_corvta * m_w[i].x + m_corvtb * m_w[i].y + m_corvtc);
4357 }
4358
4359 bool ok = true;
4360 // Force sum charges = 0 in case of absence of equipotential planes.
4361 if (!(m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3] || m_tube)) {
4362 // Add extra elements to A, acting as constraints.
4363 b.push_back(0.);
4364 m_a.resize(m_nWires + 1);
4365 m_a[m_nWires].clear();
4366 for (unsigned int i = 0; i < m_nWires; ++i) {
4367 m_a[i].push_back(1.);
4368 m_a[m_nWires].push_back(1.);
4369 }
4370 m_a[m_nWires].push_back(0.);
4371 // Solve equations to yield charges.
4372 if (Numerics::CERNLIB::deqinv(m_nWires + 1, m_a, b) != 0) {
4373 std::cerr << m_className << "::Charge: Matrix inversion failed.\n";
4374 return false;
4375 }
4376 // Modify A to give true inverse of capacitance matrix.
4377 if (m_a[m_nWires][m_nWires] != 0.) {
4378 const double t = 1. / m_a[m_nWires][m_nWires];
4379 for (unsigned int i = 0; i < m_nWires; ++i) {
4380 for (unsigned int j = 0; j < m_nWires; ++j) {
4381 m_a[i][j] -= t * m_a[i][m_nWires] * m_a[m_nWires][j];
4382 }
4383 }
4384 } else {
4385 std::cerr << m_className << "::Charge:\n"
4386 << " True inverse of the capacitance matrix"
4387 << " could not be calculated.\n";
4388 ok = false;
4389 }
4390 // Store reference potential.
4391 m_v0 = b[m_nWires];
4392 } else {
4393 // Handle the case when the sum of the charges is zero automatically.
4394 if (Numerics::CERNLIB::deqinv(m_nWires, m_a, b) != 0) {
4395 std::cerr << m_className << "::Charge: Matrix inversion failed.\n";
4396 return false;
4397 }
4398 // Reference potential chosen to be zero.
4399 m_v0 = 0.;
4400 }
4401
4402 // Check the error condition flag.
4403 if (!ok) {
4404 std::cerr << m_className << "::Charge:\n"
4405 << " Failure to solve the capacitance equations.\n"
4406 << " No charges are available.\n";
4407 return false;
4408 }
4409
4410 // Copy the charges to E.
4411 for (unsigned int i = 0; i < m_nWires; ++i) m_w[i].e = b[i];
4412
4413 // If debugging is on, print the capacitance matrix.
4414 if (m_debug) {
4415 std::cout << m_className << "::Charge:\n";
4416 std::cout << " Dump of the capacitance matrix after inversion:\n";
4417 for (unsigned int i = 0; i < m_nWires; i += 10) {
4418 for (unsigned int j = 0; j < m_nWires; j += 10) {
4419 std::cout << " (Block " << i / 10 << ", " << j / 10 << ")\n";
4420 for (unsigned int ii = 0; ii < 10; ++ii) {
4421 if (i + ii >= m_nWires) break;
4422 for (unsigned int jj = 0; jj < 10; ++jj) {
4423 if (j + jj >= m_nWires) break;
4424 std::cout << std::setw(6) << m_a[i + ii][j + jj] << " ";
4425 }
4426 std::cout << "\n";
4427 }
4428 std::cout << "\n";
4429 }
4430 }
4431 std::cout << m_className << "::Charge:\n";
4432 std::cout << " End of the inverted capacitance matrix.\n";
4433 }
4434
4435 // And also check the quality of the matrix inversion.
4436 if (m_chargeCheck) {
4437 std::cout << m_className << "::Charge:\n";
4438 std::cout << " Quality check of the charge calculation.\n";
4439 std::cout << " Wire E as obtained E reconstructed\n";
4440 for (unsigned int i = 0; i < m_nWires; ++i) {
4441 b[i] = 0.;
4442 for (unsigned int j = 0; j < m_nWires; ++j) {
4443 b[i] += m_a[i][j] *
4444 (m_w[j].v - m_v0 -
4445 (m_corvta * m_w[j].x + m_corvtb * m_w[j].y + m_corvtc));
4446 }
4447 std::cout << " " << i << " " << m_w[i].e << " " << b[i]
4448 << "\n";
4449 }
4450 }
4451 return true;
4452}
4453
4454double ComponentAnalyticField::Ph2(const double xpos, const double ypos) const {
4455 //-----------------------------------------------------------------------
4456 // PH2 - Logarithmic contribution to real single-wire potential,
4457 // for a doubly priodic wire array.
4458 // PH2LIM - Entry, PH2LIM(r) corresponds to z on the surface of a wire
4459 // of (small) radius r.
4460 //
4461 // Clenshaw's algorithm is used for the evaluation of the sum
4462 // ZTERM = SIN(zeta) - P1*SIN(3*zeta) + P2*SIN(5*zeta).
4463 //
4464 // (G.A.Erskine/DD, 14.8.1984; some minor modifications (i) common block
4465 // /EV2COM/ incorporated in /CELDAT/ (ii) large imag(zeta) corrected)
4466 //-----------------------------------------------------------------------
4467
4468 // Start of the main subroutine, off diagonal elements.
4469 std::complex<double> zeta = m_zmult * std::complex<double>(xpos, ypos);
4470 if (fabs(imag(zeta)) < 10.) {
4471 std::complex<double> zsin = sin(zeta);
4472 std::complex<double> zcof = 4. * zsin * zsin - 2.;
4473 std::complex<double> zu = -m_p1 - zcof * m_p2;
4474 std::complex<double> zunew = 1. - zcof * zu - m_p2;
4475 std::complex<double> zterm = (zunew + zu) * zsin;
4476 return -log(abs(zterm));
4477 }
4478
4479 return -fabs(imag(zeta)) + CLog2;
4480}
4481
4482void ComponentAnalyticField::ConformalMap(const std::complex<double>& z,
4483 std::complex<double>& ww,
4484 std::complex<double>& wd) const {
4485 //-----------------------------------------------------------------------
4486 // EFCMAP - Maps a the interior part of a regular in the unit circle.
4487 // Variables: Z - point to be mapped
4488 // W - the image of Z
4489 // WD - derivative of the mapping at Z
4490 // CC1 - coefficients for expansion around centre
4491 // CC2 - coefficients for expansion around corner
4492 // (Last changed on 19/ 2/94.)
4493 //-----------------------------------------------------------------------
4494
4495 // Coefficients for centre expansion in triangles, squares, pentagons,
4496 // hexagons, heptagons, octogons.
4497 constexpr std::array<std::array<double, 16>, 6> cc1 = {
4498 {{{0.1000000000e+01, -.1666666865e+00, 0.3174602985e-01, -.5731921643e-02,
4499 0.1040112227e-02, -.1886279933e-03, 0.3421107249e-04, -.6204730198e-05,
4500 0.1125329618e-05, -.2040969207e-06, 0.3701631357e-07, -.6713513301e-08,
4501 0.1217605794e-08, -.2208327132e-09, 0.4005162868e-10,
4502 -.7264017512e-11}},
4503 {{0.1000000000e+01, -.1000000238e+00, 0.8333332837e-02, -.7051283028e-03,
4504 0.5967194738e-04, -.5049648280e-05, 0.4273189802e-06, -.3616123934e-07,
4505 0.3060091514e-08, -.2589557457e-09, 0.2191374859e-10, -.1854418528e-11,
4506 0.1569274224e-12, -.1327975205e-13, 0.1123779363e-14,
4507 -.9509817570e-16}},
4508 {{0.1000000000e+01, -.6666666269e-01, 0.1212121220e-02, -.2626262140e-03,
4509 -.3322110570e-04, -.9413293810e-05, -.2570029210e-05, -.7695705904e-06,
4510 -.2422486887e-06, -.7945993730e-07, -.2691839640e-07, -.9361642128e-08,
4511 -.3327319087e-08, -.1204430555e-08, -.4428404310e-09,
4512 -.1650302672e-09}},
4513 {{0.1000000000e+01, -.4761904851e-01, -.1221001148e-02, -.3753788769e-03,
4514 -.9415557724e-04, -.2862767724e-04, -.9587882232e-05, -.3441659828e-05,
4515 -.1299798896e-05, -.5103651119e-06, -.2066504408e-06, -.8578405186e-07,
4516 -.3635090096e-07, -.1567239494e-07, -.6857355572e-08,
4517 -.3038770346e-08}},
4518 {{0.1000000000e+01, -.3571428731e-01, -.2040816238e-02, -.4936389159e-03,
4519 -.1446709794e-03, -.4963850370e-04, -.1877940667e-04, -.7600909157e-05,
4520 -.3232265954e-05, -.1427365532e-05, -.6493634714e-06, -.3026190711e-06,
4521 -.1438593245e-06, -.6953911225e-07, -.3409525462e-07,
4522 -.1692310647e-07}},
4523 {{0.1000000000e+01, -.2777777612e-01, -.2246732125e-02, -.5571441725e-03,
4524 -.1790652314e-03, -.6708275760e-04, -.2766949183e-04, -.1219387286e-04,
4525 -.5640039490e-05, -.2706697160e-05, -.1337270078e-05, -.6763995657e-06,
4526 -.3488264610e-06, -.1828456675e-06, -.9718036154e-07,
4527 -.5227070332e-07}}}};
4528 // Coefficients for corner expansion.
4529 constexpr std::array<std::array<double, 16>, 6> cc2 = {
4530 {{{0.3333333135e+00, -.5555555597e-01, 0.1014109328e-01, -.1837154618e-02,
4531 0.3332451452e-03, -.6043842586e-04, 0.1096152027e-04, -.1988050826e-05,
4532 0.3605655365e-06, -.6539443120e-07, 0.1186035448e-07, -.2151069323e-08,
4533 0.3901317047e-09, -.7075676156e-10, 0.1283289534e-10,
4534 -.2327455936e-11}},
4535 {{0.1000000000e+01, -.5000000000e+00, 0.3000000119e+00, -.1750000119e+00,
4536 0.1016666889e+00, -.5916666612e-01, 0.3442307562e-01, -.2002724260e-01,
4537 0.1165192947e-01, -.6779119372e-02, 0.3944106400e-02, -.2294691978e-02,
4538 0.1335057430e-02, -.7767395582e-03, 0.4519091453e-03,
4539 -.2629216760e-03}},
4540 {{0.1248050690e+01, -.7788147926e+00, 0.6355384588e+00, -.4899077415e+00,
4541 0.3713272810e+00, -.2838423252e+00, 0.2174729109e+00, -.1663445234e+00,
4542 0.1271933913e+00, -.9728997946e-01, 0.7442557812e-01, -.5692918226e-01,
4543 0.4354400188e-01, -.3330700099e-01, 0.2547712997e-01,
4544 -.1948769018e-01}},
4545 {{0.1333333015e+01, -.8888888955e+00, 0.8395061493e+00, -.7242798209e+00,
4546 0.6016069055e+00, -.5107235312e+00, 0.4393203855e+00, -.3745460510e+00,
4547 0.3175755739e+00, -.2703750730e+00, 0.2308617830e+00, -.1966916919e+00,
4548 0.1672732830e+00, -.1424439549e+00, 0.1214511395e+00,
4549 -.1034612656e+00}},
4550 {{0.1359752655e+01, -.9244638681e+00, 0.9593217969e+00, -.8771237731e+00,
4551 0.7490229011e+00, -.6677658558e+00, 0.6196745634e+00, -.5591596961e+00,
4552 0.4905325770e+00, -.4393517375e+00, 0.4029803872e+00, -.3631100059e+00,
4553 0.3199430704e+00, -.2866140604e+00, 0.2627358437e+00,
4554 -.2368256450e+00}},
4555 {{0.1362840652e+01, -.9286670089e+00, 0.1035511017e+01, -.9800255299e+00,
4556 0.8315343261e+00, -.7592730522e+00, 0.7612683773e+00, -.7132136226e+00,
4557 0.6074471474e+00, -.5554352999e+00, 0.5699443221e+00, -.5357525349e+00,
4558 0.4329345822e+00, -.3916820884e+00, 0.4401986003e+00,
4559 -.4197303057e+00}}}};
4560
4561 constexpr int nterm = 15;
4562 if (z == 0.) {
4563 // Z coincides with the centre. Results are trivial.
4564 ww = 0;
4565 wd = m_kappa;
4566 } else if (abs(z) < 0.75) {
4567 // Z is close to the centre. Series expansion.
4568 std::complex<double> zterm = pow(m_kappa * z, m_ntube);
4569 std::complex<double> wdsum = 0.;
4570 std::complex<double> wsum = cc1[m_ntube - 3][nterm];
4571 for (int i = nterm; i--;) {
4572 wdsum = wsum + zterm * wdsum;
4573 wsum = cc1[m_ntube - 3][i] + zterm * wsum;
4574 }
4575 // Return the results.
4576 ww = m_kappa * z * wsum;
4577 wd = m_kappa * (wsum + double(m_ntube) * zterm * wdsum);
4578 } else {
4579 // Z is close to the edge.
4580 // First rotate Z nearest to 1.
4581 double arot = -TwoPi *
4582 int(round(atan2(imag(z), real(z)) * m_ntube / TwoPi)) /
4583 m_ntube;
4584 const std::complex<double> zz =
4585 z * std::complex<double>(cos(arot), sin(arot));
4586 // Expand in a series.
4587 std::complex<double> zterm =
4588 pow(m_kappa * (1. - zz), m_ntube / (m_ntube - 2.));
4589 std::complex<double> wdsum = 0.;
4590 std::complex<double> wsum = cc2[m_ntube - 3][nterm];
4591 for (int i = nterm; i--;) {
4592 wdsum = wsum + zterm * wdsum;
4593 wsum = cc2[m_ntube - 3][i] + zterm * wsum;
4594 }
4595 // And return the results.
4596 ww = std::complex<double>(cos(arot), -sin(arot)) * (1. - zterm * wsum);
4597 wd = m_ntube * m_kappa * pow(m_kappa * (1. - zz), 2. / (m_ntube - 2.)) *
4598 (wsum + zterm * wdsum) / (m_ntube - 2.);
4599 }
4600}
4601
4602void ComponentAnalyticField::E2Sum(const double xpos, const double ypos,
4603 double& ex, double& ey) const {
4604 //-----------------------------------------------------------------------
4605 // E2SUM - Components of the elecrostatic field intensity in a doubly
4606 // periodic wire array.
4607 // Clenshaw's algorithm is used for the evaluation of the sums
4608 // ZTERM1 = SIN(ZETA) - P1*SIN(3*ZETA) + P2*SIN(5*ZETA),
4609 // ZTERM2 = COS(ZETA)- 3 P1*COS(3*ZETA)+ 5P2*COS(5*ZETA)
4610 // VARIABLES : (XPOS,YPOS): Position in the basic cell at which the
4611 // field is to be computed.
4612 // (Essentially by G.A.Erskine/DD, 14.8.1984)
4613 //-----------------------------------------------------------------------
4614
4615 constexpr std::complex<double> icons(0., 1.);
4616
4617 std::complex<double> wsum = 0.;
4618 for (const auto& wire : m_w) {
4619 const auto zeta =
4620 m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
4621 if (imag(zeta) > 15.) {
4622 wsum -= wire.e * icons;
4623 } else if (imag(zeta) < -15.) {
4624 wsum += wire.e * icons;
4625 } else {
4626 const auto zterm = Th1(zeta, m_p1, m_p2);
4627 wsum += wire.e * (zterm.second / zterm.first);
4628 }
4629 }
4630 ex = -real(-m_zmult * wsum);
4631 ey = imag(-m_zmult * wsum);
4632}
4633
4634void ComponentAnalyticField::FieldA00(const double xpos, const double ypos,
4635 double& ex, double& ey, double& volt,
4636 const bool opt) const {
4637 //-----------------------------------------------------------------------
4638 // EFCA00 - Subroutine performing the actual field calculations in case
4639 // only one charge and not more than 1 mirror-charge in either
4640 // x or y is present.
4641 // The potential used is 1/2*pi*eps0 log(r).
4642 // VARIABLES : R2 : Potential before taking -log(sqrt(...))
4643 // EX, EY : x,y-component of the electric field.
4644 // ETOT : Magnitude of electric field.
4645 // VOLT : Potential.
4646 // EXHELP etc : One term in the series to be summed.
4647 // (XPOS,YPOS): The position where the field is calculated.
4648 // (Last changed on 25/ 1/96.)
4649 //-----------------------------------------------------------------------
4650
4651 // Initialise the electric field and potential.
4652 ex = ey = 0.;
4653 volt = m_v0;
4654
4655 double xxmirr = 0., yymirr = 0.;
4656 // Loop over all wires.
4657 for (const auto& wire : m_w) {
4658 const double xx = xpos - wire.x;
4659 const double yy = ypos - wire.y;
4660 double r2 = xx * xx + yy * yy;
4661 // Calculate the field in case there are no planes.
4662 double exhelp = xx / r2;
4663 double eyhelp = yy / r2;
4664 // Take care of a plane at constant x.
4665 if (m_ynplax) {
4666 xxmirr = wire.x + (xpos - 2. * m_coplax);
4667 const double r2plan = xxmirr * xxmirr + yy * yy;
4668 exhelp -= xxmirr / r2plan;
4669 eyhelp -= yy / r2plan;
4670 r2 /= r2plan;
4671 }
4672 // Take care of a plane at constant y.
4673 if (m_ynplay) {
4674 yymirr = wire.y + (ypos - 2. * m_coplay);
4675 const double r2plan = xx * xx + yymirr * yymirr;
4676 exhelp -= xx / r2plan;
4677 eyhelp -= yymirr / r2plan;
4678 r2 /= r2plan;
4679 }
4680 // Take care of pairs of planes.
4681 if (m_ynplax && m_ynplay) {
4682 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
4683 exhelp += xxmirr / r2plan;
4684 eyhelp += yymirr / r2plan;
4685 r2 *= r2plan;
4686 }
4687 // Calculate the electric field and potential.
4688 if (opt) volt -= 0.5 * wire.e * log(r2);
4689 ex += wire.e * exhelp;
4690 ey += wire.e * eyhelp;
4691 }
4692}
4693
4694void ComponentAnalyticField::FieldB1X(const double xpos, const double ypos,
4695 double& ex, double& ey, double& volt,
4696 const bool opt) const {
4697 //-----------------------------------------------------------------------
4698 // EFCB1X - Routine calculating the potential for a row of positive
4699 // charges. The potential used is Re(Log(sin pi/s (z-z0))).
4700 // VARIABLES : See routine EFCA00 for most of the variables.
4701 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
4702 // ECOMPL : EX + I*EY ; I**2=-1
4703 //-----------------------------------------------------------------------
4704
4705 constexpr std::complex<double> icons(0., 1.);
4706
4707 std::complex<double> ecompl;
4708
4709 double r2 = 0.;
4710
4711 // Initialise the electric field and potential.
4712 ex = ey = 0.;
4713 volt = m_v0;
4714
4715 const double tx = Pi / m_sx;
4716 // Loop over all wires.
4717 for (const auto& wire : m_w) {
4718 const double xx = tx * (xpos - wire.x);
4719 const double yy = tx * (ypos - wire.y);
4720 // Calculate the field in case there are no equipotential planes.
4721 if (yy > 20.) {
4722 ecompl = -icons;
4723 } else if (yy < -20.) {
4724 ecompl = icons;
4725 } else {
4726 const std::complex<double> zz(xx, yy);
4727 const auto expzz = exp(2. * icons * zz);
4728 ecompl = icons * (expzz + 1.) / (expzz - 1.);
4729 }
4730
4731 if (opt) {
4732 if (fabs(yy) > 20.) r2 = -fabs(yy) + CLog2;
4733 if (fabs(yy) <= 20.) r2 = -0.5 * log(pow(sinh(yy), 2) + pow(sin(xx), 2));
4734 }
4735 // Take care of a plane at constant y.
4736 if (m_ynplay) {
4737 const double yymirr = tx * (ypos + wire.y - 2. * m_coplay);
4738 if (yymirr > 20.) {
4739 ecompl += icons;
4740 } else if (yymirr < -20.) {
4741 ecompl += -icons;
4742 } else {
4743 const std::complex<double> zzmirr(xx, yymirr);
4744 const auto expzzmirr = exp(2. * icons * zzmirr);
4745 ecompl += -icons * (expzzmirr + 1.) / (expzzmirr - 1.);
4746 }
4747 if (opt && fabs(yymirr) > 20.) r2 += fabs(yymirr) - CLog2;
4748 if (opt && fabs(yymirr) <= 20.)
4749 r2 += 0.5 * log(pow(sinh(yymirr), 2) + pow(sin(xx), 2));
4750 }
4751 // Calculate the electric field and potential.
4752 ex += wire.e * real(ecompl);
4753 ey -= wire.e * imag(ecompl);
4754 if (opt) volt += wire.e * r2;
4755 }
4756 ex *= tx;
4757 ey *= tx;
4758}
4759
4760void ComponentAnalyticField::FieldB1Y(const double xpos, const double ypos,
4761 double& ex, double& ey, double& volt,
4762 const bool opt) const {
4763 //-----------------------------------------------------------------------
4764 // EFCB1Y - Routine calculating the potential for a row of positive
4765 // charges. The potential used is Re(Log(sinh pi/sy(z-z0)).
4766 // VARIABLES : See routine EFCA00 for most of the variables.
4767 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
4768 // ECOMPL : EX + I*EY ; I**2=-1
4769 //-----------------------------------------------------------------------
4770
4771 std::complex<double> ecompl;
4772
4773 double r2 = 0.;
4774
4775 // Initialise the electric field and potential.
4776 ex = ey = 0.;
4777 volt = m_v0;
4778
4779 const double ty = Pi / m_sy;
4780 // Loop over all wires.
4781 for (const auto& wire : m_w) {
4782 const double xx = ty * (xpos - wire.x);
4783 const double yy = ty * (ypos - wire.y);
4784 // Calculate the field in case there are no equipotential planes.
4785 if (xx > 20.) {
4786 ecompl = 1.;
4787 } else if (xx < -20.) {
4788 ecompl = -1.;
4789 } else {
4790 const std::complex<double> zz(xx, yy);
4791 const auto expzz = exp(2. * zz);
4792 ecompl = (expzz + 1.) / (expzz - 1.);
4793 }
4794 if (opt) {
4795 if (fabs(xx) > 20.) r2 = -fabs(xx) + CLog2;
4796 if (fabs(xx) <= 20.) r2 = -0.5 * log(pow(sinh(xx), 2) + pow(sin(yy), 2));
4797 }
4798 // Take care of a plane at constant x.
4799 if (m_ynplax) {
4800 const double xxmirr = ty * (xpos + wire.x - 2. * m_coplax);
4801 if (xxmirr > 20.) {
4802 ecompl -= 1.;
4803 } else if (xxmirr < -20.) {
4804 ecompl += 1.;
4805 } else {
4806 const std::complex<double> zzmirr(xxmirr, yy);
4807 const auto expzzmirr = exp(2. * zzmirr);
4808 ecompl -= (expzzmirr + 1.) / (expzzmirr - 1.);
4809 }
4810 if (opt && fabs(xxmirr) > 20.) r2 += fabs(xxmirr) - CLog2;
4811 if (opt && fabs(xxmirr) <= 20.)
4812 r2 += 0.5 * log(pow(sinh(xxmirr), 2) + pow(sin(yy), 2));
4813 }
4814 // Calculate the electric field and potential.
4815 ex += wire.e * real(ecompl);
4816 ey -= wire.e * imag(ecompl);
4817 if (opt) volt += wire.e * r2;
4818 }
4819 ex *= ty;
4820 ey *= ty;
4821}
4822
4823void ComponentAnalyticField::FieldB2X(const double xpos, const double ypos,
4824 double& ex, double& ey, double& volt,
4825 const bool opt) const {
4826 //-----------------------------------------------------------------------
4827 // EFCB2X - Routine calculating the potential for a row of alternating
4828 // + - charges. The potential used is re log(sin pi/sx (z-z0))
4829 // VARIABLES : See routine EFCA00 for most of the variables.
4830 // Z, ZZMRR : X + i*Y , XXMIRR + i*YYMIRR ; i**2=-1
4831 // ECOMPL : EX + i*EY ; i**2=-1
4832 // (Cray vectorisable)
4833 //-----------------------------------------------------------------------
4834
4835 // Initialise the electric field and potential.
4836 ex = ey = 0.;
4837 volt = m_v0;
4838
4839 const double tx = HalfPi / m_sx;
4840 // Loop over all wires.
4841 for (unsigned int i = 0; i < m_nWires; ++i) {
4842 const double xx = tx * (xpos - m_w[i].x);
4843 const double yy = tx * (ypos - m_w[i].y);
4844 const double xxneg = tx * (xpos - m_w[i].x - 2 * m_coplax);
4845 // Calculate the field in case there are no equipotential planes.
4846 std::complex<double> ecompl(0., 0.);
4847 double r2 = 1.;
4848 if (fabs(yy) <= 20.) {
4849 const std::complex<double> zz(xx, yy);
4850 const std::complex<double> zzneg(xxneg, yy);
4851 ecompl = -m_b2sin[i] / (sin(zz) * sin(zzneg));
4852 if (opt) {
4853 const double sinhy = sinh(yy);
4854 const double sinxx = sin(xx);
4855 const double sinxxneg = sin(xxneg);
4856 r2 = (sinhy * sinhy + sinxx * sinxx) /
4857 (sinhy * sinhy + sinxxneg * sinxxneg);
4858 }
4859 }
4860 // Take care of a planes at constant y.
4861 if (m_ynplay) {
4862 const double yymirr = tx * (ypos + m_w[i].y - 2 * m_coplay);
4863 if (fabs(yymirr) <= 20.) {
4864 const std::complex<double> zzmirr(xx, yymirr);
4865 const std::complex<double> zznmirr(xxneg, yymirr);
4866 ecompl += m_b2sin[i] / (sin(zzmirr) * sin(zznmirr));
4867 if (opt) {
4868 const double sinhy = sinh(yymirr);
4869 const double sinxx = sin(xx);
4870 const double sinxxneg = sin(xxneg);
4871 const double r2plan = (sinhy * sinhy + sinxx * sinxx) /
4872 (sinhy * sinhy + sinxxneg * sinxxneg);
4873 r2 /= r2plan;
4874 }
4875 }
4876 }
4877 // Calculate the electric field and potential.
4878 ex += m_w[i].e * real(ecompl);
4879 ey -= m_w[i].e * imag(ecompl);
4880 if (opt) volt -= 0.5 * m_w[i].e * log(r2);
4881 }
4882 ex *= tx;
4883 ey *= tx;
4884}
4885
4886void ComponentAnalyticField::FieldB2Y(const double xpos, const double ypos,
4887 double& ex, double& ey, double& volt,
4888 const bool opt) const {
4889 //-----------------------------------------------------------------------
4890 // EFCB2Y - Routine calculating the potential for a row of alternating
4891 // + - charges. The potential used is re log(sin pi/sx (z-z0))
4892 // VARIABLES : See routine EFCA00 for most of the variables.
4893 // Z, ZMIRR : X + i*Y , XXMIRR + i*YYMIRR ; i**2=-1
4894 // ECOMPL : EX + i*EY ; i**2=-1
4895 // (Cray vectorisable)
4896 //-----------------------------------------------------------------------
4897
4898 const std::complex<double> icons(0., 1.);
4899
4900 // Initialise the electric field and potential.
4901 ex = ey = 0.;
4902 volt = m_v0;
4903
4904 const double ty = HalfPi / m_sy;
4905 // Loop over all wires.
4906 for (unsigned int i = 0; i < m_nWires; ++i) {
4907 const double xx = ty * (xpos - m_w[i].x);
4908 const double yy = ty * (ypos - m_w[i].y);
4909 const double yyneg = ty * (ypos + m_w[i].y - 2 * m_coplay);
4910 // Calculate the field in case there are no equipotential planes.
4911 std::complex<double> ecompl(0., 0.);
4912 double r2 = 1.;
4913 if (fabs(xx) <= 20.) {
4914 const std::complex<double> zz(xx, yy);
4915 const std::complex<double> zzneg(xx, yyneg);
4916 ecompl = icons * m_b2sin[i] / (sin(icons * zz) * sin(icons * zzneg));
4917 if (opt) {
4918 const double sinhx = sinh(xx);
4919 const double sinyy = sin(yy);
4920 const double sinyyneg = sin(yyneg);
4921 r2 = (sinhx * sinhx + sinyy * sinyy) /
4922 (sinhx * sinhx + sinyyneg * sinyyneg);
4923 }
4924 }
4925 // Take care of a plane at constant x.
4926 if (m_ynplax) {
4927 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
4928 if (fabs(xxmirr) <= 20.) {
4929 const std::complex<double> zzmirr(xxmirr, yy);
4930 const std::complex<double> zznmirr(xxmirr, yyneg);
4931 ecompl -=
4932 icons * m_b2sin[i] / (sin(icons * zzmirr) * sin(icons * zznmirr));
4933 if (opt) {
4934 const double sinhx = sinh(xxmirr);
4935 const double sinyy = sin(yy);
4936 const double sinyyneg = sin(yyneg);
4937 const double r2plan = (sinhx * sinhx + sinyy * sinyy) /
4938 (sinhx * sinhx + sinyyneg * sinyyneg);
4939 r2 /= r2plan;
4940 }
4941 }
4942 }
4943 // Calculate the electric field and potential.
4944 ex += m_w[i].e * real(ecompl);
4945 ey -= m_w[i].e * imag(ecompl);
4946 if (opt) volt -= 0.5 * m_w[i].e * log(r2);
4947 }
4948 ex *= ty;
4949 ey *= ty;
4950}
4951
4952void ComponentAnalyticField::FieldC10(const double xpos, const double ypos,
4953 double& ex, double& ey, double& volt,
4954 const bool opt) const {
4955 //-----------------------------------------------------------------------
4956 // EFCC10 - Routine returning the potential and electric field. It
4957 // calls the routines PH2 and E2SUM written by G.A.Erskine.
4958 // VARIABLES : No local variables.
4959 //-----------------------------------------------------------------------
4960
4961 // Calculate voltage first, if needed.
4962 if (opt) {
4963 if (m_mode == 0) volt = m_v0 + m_c1 * xpos;
4964 if (m_mode == 1) volt = m_v0 + m_c1 * ypos;
4965 for (const auto& wire : m_w) {
4966 volt += wire.e * Ph2(xpos - wire.x, ypos - wire.y);
4967 }
4968 }
4969
4970 // And finally the electric field.
4971 E2Sum(xpos, ypos, ex, ey);
4972 if (m_mode == 0) ex -= m_c1;
4973 if (m_mode == 1) ey -= m_c1;
4974}
4975
4976void ComponentAnalyticField::FieldC2X(const double xpos, const double ypos,
4977 double& ex, double& ey, double& volt,
4978 const bool opt) const {
4979 //-----------------------------------------------------------------------
4980 // EFCC2X - Routine returning the potential and electric field in a
4981 // configuration with 2 x planes and y periodicity.
4982 // VARIABLES : see the writeup
4983 //-----------------------------------------------------------------------
4984
4985 constexpr std::complex<double> icons(0., 1.);
4986
4987 // Initial values.
4988 std::complex<double> wsum1 = 0.;
4989 std::complex<double> wsum2 = 0.;
4990 volt = 0.;
4991
4992 // Wire loop.
4993 for (const auto& wire : m_w) {
4994 // Compute the direct contribution.
4995 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
4996 if (imag(zeta) > 15.) {
4997 wsum1 -= wire.e * icons;
4998 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
4999 } else if (imag(zeta) < -15.) {
5000 wsum1 += wire.e * icons;
5001 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5002 } else {
5003 const auto zterm = Th1(zeta, m_p1, m_p2);
5004 wsum1 += wire.e * (zterm.second / zterm.first);
5005 if (opt) volt -= wire.e * log(abs(zterm.first));
5006 }
5007 // Find the plane nearest to the wire.
5008 const double cx = m_coplax - m_sx * int(round((m_coplax - wire.x) / m_sx));
5009 // Mirror contribution.
5010 zeta =
5011 m_zmult * std::complex<double>(2. * cx - xpos - wire.x, ypos - wire.y);
5012 if (imag(zeta) > 15.) {
5013 wsum2 -= wire.e * icons;
5014 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5015 } else if (imag(zeta) < -15.) {
5016 wsum2 += wire.e * icons;
5017 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5018 } else {
5019 const auto zterm = Th1(zeta, m_p1, m_p2);
5020 wsum2 += wire.e * (zterm.second / zterm.first);
5021 if (opt) volt += wire.e * log(abs(zterm.first));
5022 }
5023 // Correct the voltage, if needed (MODE).
5024 if (opt && m_mode == 0) {
5025 volt -= TwoPi * wire.e * (xpos - cx) * (wire.x - cx) / (m_sx * m_sy);
5026 }
5027 }
5028 // Convert the two contributions to a real field.
5029 ex = real(m_zmult * (wsum1 + wsum2));
5030 ey = -imag(m_zmult * (wsum1 - wsum2));
5031 // Constant correction terms.
5032 if (m_mode == 0) ex -= m_c1;
5033}
5034
5035void ComponentAnalyticField::FieldC2Y(const double xpos, const double ypos,
5036 double& ex, double& ey, double& volt,
5037 const bool opt) const {
5038 //-----------------------------------------------------------------------
5039 // EFCC2Y - Routine returning the potential and electric field in a
5040 // configuration with 2 y planes and x periodicity.
5041 // VARIABLES : see the writeup
5042 //-----------------------------------------------------------------------
5043
5044 constexpr std::complex<double> icons(0., 1.);
5045
5046 // Initial values.
5047 volt = 0.;
5048 std::complex<double> wsum1 = 0.;
5049 std::complex<double> wsum2 = 0.;
5050
5051 // Wire loop.
5052 for (const auto& wire : m_w) {
5053 // Compute the direct contribution.
5054 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
5055 if (imag(zeta) > 15.) {
5056 wsum1 -= wire.e * icons;
5057 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5058 } else if (imag(zeta) < -15.) {
5059 wsum1 += wire.e * icons;
5060 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5061 } else {
5062 const auto zterm = Th1(zeta, m_p1, m_p2);
5063 wsum1 += wire.e * (zterm.second / zterm.first);
5064 if (opt) volt -= wire.e * log(abs(zterm.first));
5065 }
5066 // Find the plane nearest to the wire.
5067 const double cy = m_coplay - m_sy * int(round((m_coplay - wire.y) / m_sy));
5068 // Mirror contribution from the y plane.
5069 zeta =
5070 m_zmult * std::complex<double>(xpos - wire.x, 2 * cy - ypos - wire.y);
5071 if (imag(zeta) > 15.) {
5072 wsum2 -= wire.e * icons;
5073 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5074 } else if (imag(zeta) < -15.) {
5075 wsum2 += wire.e * icons;
5076 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5077 } else {
5078 const auto zterm = Th1(zeta, m_p1, m_p2);
5079 wsum2 += wire.e * (zterm.second / zterm.first);
5080 if (opt) volt += wire.e * log(abs(zterm.first));
5081 }
5082 // Correct the voltage, if needed (MODE).
5083 if (opt && m_mode == 1) {
5084 volt -= TwoPi * wire.e * (ypos - cy) * (wire.y - cy) / (m_sx * m_sy);
5085 }
5086 }
5087 // Convert the two contributions to a real field.
5088 ex = real(m_zmult * (wsum1 - wsum2));
5089 ey = -imag(m_zmult * (wsum1 + wsum2));
5090 // Constant correction terms.
5091 if (m_mode == 1) ey -= m_c1;
5092}
5093
5094void ComponentAnalyticField::FieldC30(const double xpos, const double ypos,
5095 double& ex, double& ey, double& volt,
5096 const bool opt) const {
5097 //-----------------------------------------------------------------------
5098 // EFCC30 - Routine returning the potential and electric field in a
5099 // configuration with 2 y and 2 x planes.
5100 // VARIABLES : see the writeup
5101 //-----------------------------------------------------------------------
5102
5103 constexpr std::complex<double> icons(0., 1.);
5104
5105 // Initial values.
5106 std::complex<double> wsum1 = 0.;
5107 std::complex<double> wsum2 = 0.;
5108 std::complex<double> wsum3 = 0.;
5109 std::complex<double> wsum4 = 0.;
5110 volt = 0.;
5111
5112 // Wire loop.
5113 for (const auto& wire : m_w) {
5114 // Compute the direct contribution.
5115 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
5116 if (imag(zeta) > 15.) {
5117 wsum1 -= wire.e * icons;
5118 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5119 } else if (imag(zeta) < -15.) {
5120 wsum1 += wire.e * icons;
5121 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5122 } else {
5123 const auto zterm = Th1(zeta, m_p1, m_p2);
5124 wsum1 += wire.e * (zterm.second / zterm.first);
5125 if (opt) volt -= wire.e * log(abs(zterm.first));
5126 }
5127 // Find the plane nearest to the wire.
5128 const double cx = m_coplax - m_sx * int(round((m_coplax - wire.x) / m_sx));
5129 // Mirror contribution from the x plane.
5130 zeta =
5131 m_zmult * std::complex<double>(2. * cx - xpos - wire.x, ypos - wire.y);
5132 if (imag(zeta) > 15.) {
5133 wsum2 -= wire.e * icons;
5134 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5135 } else if (imag(zeta) < -15.) {
5136 wsum2 += wire.e * icons;
5137 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5138 } else {
5139 const auto zterm = Th1(zeta, m_p1, m_p2);
5140 wsum2 += wire.e * (zterm.second / zterm.first);
5141 if (opt) volt += wire.e * log(abs(zterm.first));
5142 }
5143 // Find the plane nearest to the wire.
5144 const double cy = m_coplay - m_sy * int(round((m_coplay - wire.y) / m_sy));
5145 // Mirror contribution from the x plane.
5146 zeta =
5147 m_zmult * std::complex<double>(xpos - wire.x, 2. * cy - ypos - wire.y);
5148 if (imag(zeta) > 15.) {
5149 wsum3 -= wire.e * icons;
5150 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5151 } else if (imag(zeta) < -15.) {
5152 wsum3 += wire.e * icons;
5153 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5154 } else {
5155 const auto zterm = Th1(zeta, m_p1, m_p2);
5156 wsum3 += wire.e * (zterm.second / zterm.first);
5157 if (opt) volt += wire.e * log(abs(zterm.first));
5158 }
5159 // Mirror contribution from both the x and the y plane.
5160 zeta = m_zmult * std::complex<double>(2. * cx - xpos - wire.x,
5161 2. * cy - ypos - wire.y);
5162 if (imag(zeta) > 15.) {
5163 wsum4 -= wire.e * icons;
5164 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5165 } else if (imag(zeta) < -15.) {
5166 wsum4 += wire.e * icons;
5167 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5168 } else {
5169 const auto zterm = Th1(zeta, m_p1, m_p2);
5170 wsum4 += wire.e * (zterm.second / zterm.first);
5171 if (opt) volt -= wire.e * log(abs(zterm.first));
5172 }
5173 }
5174 // Convert the two contributions to a real field.
5175 ex = real(m_zmult * (wsum1 + wsum2 - wsum3 - wsum4));
5176 ey = -imag(m_zmult * (wsum1 - wsum2 + wsum3 - wsum4));
5177}
5178
5179void ComponentAnalyticField::FieldD10(const double xpos, const double ypos,
5180 double& ex, double& ey, double& volt,
5181 const bool opt) const {
5182 //-----------------------------------------------------------------------
5183 // EFCD10 - Subroutine performing the actual field calculations for a
5184 // cell which has a one circular plane and some wires.
5185 // VARIABLES : EX, EY, VOLT:Electric field and potential.
5186 // ETOT, VOLT : Magnitude of electric field, potential.
5187 // (XPOS,YPOS): The position where the field is calculated.
5188 // ZI, ZPOS : Shorthand complex notations.
5189 // (Last changed on 4/ 9/95.)
5190 //-----------------------------------------------------------------------
5191
5192 // Initialise the electric field and potential.
5193 ex = ey = 0.;
5194 volt = m_v0;
5195
5196 // Set the complex position coordinates.
5197 const std::complex<double> zpos = std::complex<double>(xpos, ypos);
5198 // Loop over all wires.
5199 for (const auto& wire : m_w) {
5200 // Set the complex version of the wire-coordinate for simplicity.
5201 const std::complex<double> zi(wire.x, wire.y);
5202 // Compute the contribution to the potential, if needed.
5203 if (opt) {
5204 volt -= wire.e *
5205 log(abs(m_cotube * (zpos - zi) / (m_cotube2 - zpos * conj(zi))));
5206 }
5207 // Compute the contribution to the electric field, always.
5208 const std::complex<double> wi =
5209 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
5210 ex += wire.e * real(wi);
5211 ey += wire.e * imag(wi);
5212 }
5213}
5214
5215void ComponentAnalyticField::FieldD20(const double xpos, const double ypos,
5216 double& ex, double& ey, double& volt,
5217 const bool opt) const {
5218 //-----------------------------------------------------------------------
5219 // EFCD20 - Subroutine performing the actual field calculations for a
5220 // cell which has a tube and phi periodicity.
5221 // VARIABLES : EX, EY, VOLT:Electric field and potential.
5222 // ETOT, VOLT : Magnitude of electric field, potential.
5223 // (XPOS,YPOS): The position where the field is calculated.
5224 // ZI, ZPOS : Shorthand complex notations.
5225 // (Last changed on 10/ 2/93.)
5226 //-----------------------------------------------------------------------
5227
5228 // Initialise the electric field and potential.
5229 ex = ey = 0.;
5230 volt = m_v0;
5231
5232 // Set the complex position coordinates.
5233 const std::complex<double> zpos = std::complex<double>(xpos, ypos);
5234 // Loop over all wires.
5235 for (const auto& wire : m_w) {
5236 // Set the complex version of the wire-coordinate for simplicity.
5237 const std::complex<double> zi(wire.x, wire.y);
5238 // Case of the wire which is not in the centre.
5239 if (abs(zi) > wire.r) {
5240 // Compute the contribution to the potential, if needed.
5241 if (opt) {
5242 volt -=
5243 wire.e * log(abs((1. / pow(m_cotube, m_mtube)) *
5244 (pow(zpos, m_mtube) - pow(zi, m_mtube)) /
5245 (1. - pow(zpos * conj(zi) / m_cotube2, m_mtube))));
5246 }
5247 // Compute the contribution to the electric field, always.
5248 const std::complex<double> wi =
5249 double(m_mtube) * pow(conj(zpos), m_mtube - 1) *
5250 (1. / conj(pow(zpos, m_mtube) - pow(zi, m_mtube)) +
5251 pow(zi, m_mtube) /
5252 (pow(m_cotube, 2 * m_mtube) - pow(conj(zpos) * zi, m_mtube)));
5253 ex += wire.e * real(wi);
5254 ey += wire.e * imag(wi);
5255 } else {
5256 // Case of the central wire.
5257 if (opt) {
5258 volt -= wire.e * log(abs((1. / m_cotube) * (zpos - zi) /
5259 (1. - zpos * conj(zi) / m_cotube2)));
5260 }
5261 const std::complex<double> wi =
5262 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
5263 // Compute the contribution to the electric field, always.
5264 ex += wire.e * real(wi);
5265 ey += wire.e * imag(wi);
5266 }
5267 }
5268}
5269
5270void ComponentAnalyticField::FieldD30(const double xpos, const double ypos,
5271 double& ex, double& ey, double& volt,
5272 const bool opt) const {
5273 //-----------------------------------------------------------------------
5274 // EFCD30 - Subroutine performing the actual field calculations for a
5275 // cell which has a polygon as tube and some wires.
5276 // VARIABLES : EX, EY, VOLT:Electric field and potential.
5277 // ETOT, VOLT : Magnitude of electric field, potential.
5278 // (XPOS,YPOS): The position where the field is calculated.
5279 // ZI, ZPOS : Shorthand complex notations.
5280 // (Last changed on 19/ 2/94.)
5281 //-----------------------------------------------------------------------
5282
5283 // Initialise the electric field and potential.
5284 ex = ey = 0.;
5285 volt = m_v0;
5286
5287 std::complex<double> whelp;
5288
5289 // Get the mapping of the position.
5290 std::complex<double> wpos, wdpos;
5291 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
5292 // Loop over all wires.
5293 for (int i = m_nWires; i--;) {
5294 // Compute the contribution to the potential, if needed.
5295 if (opt) {
5296 volt -=
5297 m_w[i].e * log(abs((wpos - wmap[i]) / (1. - wpos * conj(wmap[i]))));
5298 }
5299 whelp = wdpos * (1. - pow(abs(wmap[i]), 2)) /
5300 ((wpos - wmap[i]) * (1. - conj(wmap[i]) * wpos));
5301 // Compute the contribution to the electric field, always.
5302 ex += m_w[i].e * real(whelp);
5303 ey -= m_w[i].e * imag(whelp);
5304 }
5305 ex /= m_cotube;
5306 ey /= m_cotube;
5307}
5308
5309bool ComponentAnalyticField::InTube(const double x0, const double y0,
5310 const double a, const int n) const {
5311 //-----------------------------------------------------------------------
5312 // INTUBE - Determines whether a point is located inside a polygon.
5313 // ILOC is set to +1 if outside, 0 if inside and -1 if the
5314 // arguments are not valid.
5315 // (Last changed on 18/ 3/01.)
5316 //-----------------------------------------------------------------------
5317
5318 // Special case: x = y = 0
5319 if (x0 == 0. && y0 == 0.) return true;
5320
5321 // Special case: round tube.
5322 if (n == 0) {
5323 if (x0 * x0 + y0 * y0 > a * a) return false;
5324 return true;
5325 }
5326
5327 if (n < 0 || n == 1 || n == 2) {
5328 std::cerr << m_className << "::InTube:\n";
5329 std::cerr << " Invalid number of edges (n = " << n << ")\n";
5330 return false;
5331 }
5332
5333 // Truly polygonal tubes.
5334 // Reduce angle to the first sector.
5335 double phi = atan2(y0, x0);
5336 if (phi < 0.) phi += TwoPi;
5337 phi -= TwoPi * int(0.5 * n * phi / Pi) / n;
5338 // Compare the length to the local radius.
5339 if ((x0 * x0 + y0 * y0) * pow(cos(Pi / n - phi), 2) >
5340 a * a * pow(cos(Pi / n), 2))
5341 return false;
5342
5343 return true;
5344}
5345
5346void ComponentAnalyticField::Field3dA00(const double xpos, const double ypos,
5347 const double zpos, double& ex,
5348 double& ey, double& ez, double& volt) {
5349 //-----------------------------------------------------------------------
5350 // E3DA00 - Subroutine adding 3-dimensional charges for A cells.
5351 // The potential used is 1/2*pi*eps0 1/r
5352 // VARIABLES : EX, EY : x,y-component of the electric field.
5353 // ETOT : Magnitude of electric field.
5354 // VOLT : Potential.
5355 // EXHELP etc : One term in the series to be summed.
5356 // (XPOS,YPOS): The position where the field is calculated.
5357 // (Last changed on 5/12/94.)
5358 //-----------------------------------------------------------------------
5359
5360 // Initialise the electric field and potential.
5361 ex = ey = ez = volt = 0.;
5362
5363 // Loop over all charges.
5364 for (const auto& charge : m_ch3d) {
5365 // Calculate the field in case there are no planes.
5366 const double dx = xpos - charge.x;
5367 const double dy = ypos - charge.y;
5368 const double dz = zpos - charge.z;
5369 const double r = sqrt(dx * dx + dy * dy + dz * dz);
5370 if (fabs(r) < Small) continue;
5371 const double r3 = pow(r, 3);
5372 double exhelp = -dx / r3;
5373 double eyhelp = -dy / r3;
5374 double ezhelp = -dz / r3;
5375 double vhelp = 1. / r;
5376 // Take care of a plane at constant x.
5377 double dxm = 0., dym = 0.;
5378 if (m_ynplax) {
5379 dxm = charge.x + xpos - 2 * m_coplax;
5380 const double rplan = sqrt(dxm * dxm + dy * dy);
5381 if (fabs(rplan) < Small) continue;
5382 const double rplan3 = pow(rplan, 3);
5383 exhelp += dxm / rplan3;
5384 eyhelp += dy / rplan3;
5385 ezhelp += dz / rplan3;
5386 vhelp -= 1. / rplan;
5387 }
5388 // Take care of a plane at constant y.
5389 if (m_ynplay) {
5390 dym = charge.y + ypos - 2. * m_coplay;
5391 const double rplan = sqrt(dx * dx + dym * dym);
5392 if (fabs(rplan) < Small) continue;
5393 const double rplan3 = pow(rplan, 3);
5394 exhelp += dx / rplan3;
5395 eyhelp += dym / rplan3;
5396 ezhelp += dz / rplan3;
5397 vhelp -= 1. / rplan;
5398 }
5399 // Take care of pairs of planes.
5400 if (m_ynplax && m_ynplay) {
5401 const double rplan = sqrt(dxm * dxm + dym * dym);
5402 if (fabs(rplan) < Small) continue;
5403 const double rplan3 = pow(rplan, 3);
5404 exhelp -= dxm / rplan3;
5405 eyhelp -= dym / rplan3;
5406 ezhelp -= dz / rplan3;
5407 vhelp += 1. / rplan;
5408 }
5409 // Add the terms to the electric field and the potential.
5410 ex -= charge.e * exhelp;
5411 ey -= charge.e * eyhelp;
5412 ez -= charge.e * ezhelp;
5413 volt += charge.e * vhelp;
5414 }
5415}
5416
5417void ComponentAnalyticField::Field3dB2X(const double xpos, const double ypos,
5418 const double zpos, double& ex,
5419 double& ey, double& ez, double& volt) {
5420 //-----------------------------------------------------------------------
5421 // E3DB2X - Routine calculating the potential for a 3 dimensional point
5422 // charge between two plates at constant x.
5423 // The series expansions for the modified Bessel functions
5424 // have been taken from Abramowitz and Stegun.
5425 // VARIABLES : See routine E3DA00 for most of the variables.
5426 // (Last changed on 5/12/94.)
5427 //-----------------------------------------------------------------------
5428
5429 const double rcut = 1.;
5430
5431 double rr1, rr2, rm1, rm2, err, ezz;
5432 double exsum = 0., eysum = 0., ezsum = 0., vsum = 0.;
5433 double k0r, k1r, k0rm, k1rm;
5434
5435 // Initialise the sums for the field components.
5436 ex = ey = ez = volt = 0.;
5437
5438 // Loop over all charges.
5439 for (const auto& charge : m_ch3d) {
5440 // Skip coordinates that are on the charge.
5441 if (xpos == charge.x && ypos == charge.y && zpos == charge.z) continue;
5442 const double dx = xpos - charge.x;
5443 const double dy = ypos - charge.y;
5444 const double dz = zpos - charge.z;
5445 const double dxm = xpos + charge.x - 2 * m_coplax;
5446 // In the far away zone, sum the modified Bessel function series.
5447 if (dy * dy + dz * dz > pow(rcut * 2 * m_sx, 2)) {
5448 // Initialise the per-wire sum.
5449 exsum = eysum = ezsum = vsum = 0.;
5450 // Loop over the terms in the series.
5451 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5452 // Obtain reduced coordinates.
5453 const double rr = Pi * j * sqrt(dy * dy + dz * dz) / m_sx;
5454 const double zzp = Pi * j * dx / m_sx;
5455 const double zzn = Pi * j * dxm / m_sx;
5456 // Evaluate the Bessel functions for this R.
5457 if (rr < 2.) {
5458 k0r = Numerics::BesselK0S(rr);
5459 k1r = Numerics::BesselK1S(rr);
5460 } else {
5461 k0r = Numerics::BesselK0L(rr);
5462 k1r = Numerics::BesselK1L(rr);
5463 }
5464 // Get the field components.
5465 const double czzp = cos(zzp);
5466 const double czzn = cos(zzn);
5467 vsum += (1. / m_sx) * k0r * (czzp - czzn);
5468 err = (TwoPi * j / (m_sx * m_sx)) * k1r * (czzp - czzn);
5469 ezz = (TwoPi * j / (m_sx * m_sx)) * k0r * (sin(zzp) - sin(zzn));
5470 exsum += ezz;
5471 eysum += err * dy / sqrt(dy * dy + dz * dz);
5472 ezsum += err * dz / sqrt(dy * dy + dz * dz);
5473 continue;
5474 }
5475 } else {
5476 // Direct polynomial summing, obtain reduced coordinates.
5477 // Loop over the terms.
5478 for (unsigned int j = 0; j <= m_nTermPoly; ++j) {
5479 // Simplify the references to the distances.
5480 rr1 = sqrt(pow(dx + j * 2 * m_sx, 2) + dy * dy + dz * dz);
5481 rr2 = sqrt(pow(dx - j * 2 * m_sx, 2) + dy * dy + dz * dz);
5482 rm1 = sqrt(pow(dxm - j * 2 * m_sx, 2) + dy * dy + dz * dz);
5483 rm2 = sqrt(pow(dxm + j * 2 * m_sx, 2) + dy * dy + dz * dz);
5484 const double rr13 = pow(rr1, 3);
5485 const double rm13 = pow(rm1, 3);
5486 // Initialisation of the sum: only a charge and a mirror charge.
5487 if (j == 0) {
5488 vsum = 1. / rr1 - 1. / rm1;
5489 exsum = dx / rr13 - dxm / rm13;
5490 eysum = dy * (1. / rr13 - 1. / rm13);
5491 ezsum = dz * (1. / rr13 - 1. / rm13);
5492 continue;
5493 }
5494 const double rr23 = pow(rr2, 3);
5495 const double rm23 = pow(rm2, 3);
5496 // Further terms in the series: 2 charges and 2 mirror charges.
5497 vsum += 1. / rr1 + 1. / rr2 - 1. / rm1 - 1. / rm2;
5498 exsum += (dx + j * 2 * m_sx) / rr13 + (dx - j * 2 * m_sx) / rr23 -
5499 (dxm - j * 2 * m_sx) / rm13 - (dxm + j * 2 * m_sx) / rm23;
5500 eysum += dy * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5501 ezsum += dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5502 }
5503 }
5504 // Take care of a plane at constant y.
5505 if (m_ynplay) {
5506 const double dym = ypos + charge.y - 2. * m_coplay;
5507 if (dym * dym + dz * dz > pow(rcut * 2 * m_sx, 2)) {
5508 // Bessel function series.
5509 // Loop over the terms in the series.
5510 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5511 // Obtain reduced coordinates.
5512 const double rrm = Pi * j * sqrt(dym * dym + dz * dz) / m_sx;
5513 const double zzp = Pi * j * dx / m_sx;
5514 const double zzn = Pi * j * dxm / m_sx;
5515 // Evaluate the Bessel functions for this R.
5516 if (rrm < 2.) {
5517 k0rm = Numerics::BesselK0S(rrm);
5518 k1rm = Numerics::BesselK1S(rrm);
5519 } else {
5520 k0rm = Numerics::BesselK0L(rrm);
5521 k1rm = Numerics::BesselK1L(rrm);
5522 }
5523 // Get the field components.
5524 const double czzp = cos(zzp);
5525 const double czzn = cos(zzn);
5526 vsum += (1. / m_sx) * k0rm * (czzp - czzn);
5527 err = (TwoPi / (m_sx * m_sx)) * k1rm * (czzp - czzn);
5528 ezz = (TwoPi / (m_sx * m_sx)) * k0rm * (sin(zzp) - sin(zzn));
5529 exsum += ezz;
5530 eysum += err * dym / sqrt(dym * dym + dz * dz);
5531 ezsum += err * dz / sqrt(dym * dym + dz * dz);
5532 }
5533 } else {
5534 // Polynomial sum.
5535 // Loop over the terms.
5536 for (unsigned int j = 0; j <= m_nTermPoly; ++j) {
5537 // Simplify the references to the distances.
5538 rr1 = sqrt(pow(dx + j * 2 * m_sx, 2) + dym * dym + dz * dz);
5539 rr2 = sqrt(pow(dx - j * 2 * m_sx, 2) + dym * dym + dz * dz);
5540 rm1 = sqrt(pow(dxm - j * 2 * m_sx, 2) + dym * dym + dz * dz);
5541 rm2 = sqrt(pow(dxm + j * 2 * m_sx, 2) + dym * dym + dz * dz);
5542 const double rr13 = pow(rr1, 3);
5543 const double rm13 = pow(rm1, 3);
5544 // Initialisation of the sum: only a charge and a mirror charge.
5545 if (j == 0) {
5546 vsum += -1. / rr1 + 1. / rm1;
5547 exsum += -dx / rr13 + dxm / rm13;
5548 eysum += -dym * (1. / rr13 - 1. / rm13);
5549 ezsum += -dz * (1. / rr13 - 1. / rm13);
5550 continue;
5551 }
5552 const double rr23 = pow(rr2, 3);
5553 const double rm23 = pow(rm2, 3);
5554 // Further terms in the series: 2 charges and 2 mirror charges.
5555 vsum += -1. / rr1 - 1. / rr2 + 1. / rm1 + 1. / rm2;
5556 exsum += -(dx + j * 2 * m_sx) / rr13 - (dx - j * 2 * m_sx) / rr23 +
5557 (dxm - j * 2 * m_sx) / rm13 + (dxm + j * 2 * m_sx) / rm23;
5558 eysum += -dym * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5559 ezsum += -dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5560 }
5561 }
5562 }
5563 ex += charge.e * exsum;
5564 ey += charge.e * eysum;
5565 ez += charge.e * ezsum;
5566 volt += charge.e * vsum;
5567 }
5568}
5569
5570void ComponentAnalyticField::Field3dB2Y(const double xpos, const double ypos,
5571 const double zpos, double& ex,
5572 double& ey, double& ez, double& volt) {
5573 //-----------------------------------------------------------------------
5574 // E3DB2Y - Routine calculating the potential for a 3 dimensional point
5575 // charge between two plates at constant y.
5576 // The series expansions for the modified Bessel functions
5577 // have been taken from Abramowitz and Stegun.
5578 // VARIABLES : See routine E3DA00 for most of the variables.
5579 // (Last changed on 5/12/94.)
5580 //-----------------------------------------------------------------------
5581
5582 const double rcut = 1.;
5583
5584 double rr1, rr2, rm1, rm2, err, ezz;
5585 double exsum = 0., eysum = 0., ezsum = 0., vsum = 0.;
5586 double k0r, k1r, k0rm, k1rm;
5587
5588 // Initialise the sums for the field components.
5589 ex = ey = ez = volt = 0.;
5590
5591 // Loop over all charges.
5592 for (const auto& charge : m_ch3d) {
5593 // Skip wires that are on the charge.
5594 if (xpos == charge.x && ypos == charge.y && zpos == charge.z) continue;
5595 const double dx = xpos - charge.x;
5596 const double dy = ypos - charge.y;
5597 const double dz = zpos - charge.z;
5598 const double dym = ypos + charge.y - 2 * m_coplay;
5599 // In the far away zone, sum the modified Bessel function series.
5600 if (dx * dx + dz * dz > pow(rcut * 2 * m_sy, 2)) {
5601 // Initialise the per-wire sum.
5602 exsum = eysum = ezsum = vsum = 0.;
5603 // Loop over the terms in the series.
5604 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5605 // Obtain reduced coordinates.
5606 const double rr = Pi * j * sqrt(dx * dx + dz * dz) / m_sy;
5607 const double zzp = Pi * j * dy / m_sy;
5608 const double zzn = Pi * j * dym / m_sy;
5609 // Evaluate the Bessel functions for this R.
5610 if (rr < 2.) {
5611 k0r = Numerics::BesselK0S(rr);
5612 k1r = Numerics::BesselK1S(rr);
5613 } else {
5614 k0r = Numerics::BesselK0L(rr);
5615 k1r = Numerics::BesselK1L(rr);
5616 }
5617 // Get the field components.
5618 const double czzp = cos(zzp);
5619 const double czzn = cos(zzn);
5620 vsum += (1. / m_sy) * k0r * (czzp - czzn);
5621 err = (TwoPi * j / (m_sy * m_sy)) * k1r * (czzp - czzn);
5622 ezz = (TwoPi * j / (m_sy * m_sy)) * k0r * (sin(zzp) - sin(zzn));
5623 exsum += err * dx / sqrt(dx * dx + dz * dz);
5624 ezsum += err * dz / sqrt(dx * dx + dz * dz);
5625 eysum += ezz;
5626 continue;
5627 }
5628 } else {
5629 // Direct polynomial summing, obtain reduced coordinates.
5630 // Loop over the terms.
5631 for (unsigned int j = 0; j <= m_nTermPoly; ++j) {
5632 // Simplify the references to the distances.
5633 rr1 = sqrt(dx * dx + dz * dz + pow(dy + j * 2 * m_sy, 2));
5634 rr2 = sqrt(dx * dx + dz * dz + pow(dy - j * 2 * m_sy, 2));
5635 rm1 = sqrt(dx * dx + dz * dz + pow(dym - j * 2 * m_sy, 2));
5636 rm2 = sqrt(dx * dx + dz * dz + pow(dym + j * 2 * m_sy, 2));
5637 const double rr13 = pow(rr1, 3);
5638 const double rm13 = pow(rm1, 3);
5639 // Initialisation of the sum: only a charge and a mirror charge.
5640 if (j == 0) {
5641 vsum = 1. / rr1 - 1. / rm1;
5642 exsum = dx * (1. / rr13 - 1. / rm13);
5643 ezsum = dz * (1. / rr13 - 1. / rm13);
5644 eysum = dy / rr13 - dym / rm13;
5645 continue;
5646 }
5647 // Further terms in the series: 2 charges and 2 mirror charges.
5648 const double rr23 = pow(rr2, 3);
5649 const double rm23 = pow(rm2, 3);
5650 vsum += 1. / rr1 + 1. / rr2 - 1. / rm1 - 1. / rm2;
5651 exsum += dx * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5652 ezsum += dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5653 eysum += (dy + j * 2 * m_sy) / rr13 + (dy - j * 2 * m_sy) / rr23 -
5654 (dym - j * 2 * m_sy) / rm13 - (dym + j * 2 * m_sy) / rm23;
5655 }
5656 }
5657 // Take care of a plane at constant x.
5658 if (m_ynplax) {
5659 const double dxm = xpos + charge.x - 2. * m_coplax;
5660 if (dxm * dxm + dz * dz > pow(rcut * 2 * m_sy, 2)) {
5661 // Bessel function series.
5662 // Loop over the terms in the series.
5663 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5664 // Obtain reduced coordinates.
5665 const double rrm = Pi * j * sqrt(dxm * dxm + dz * dz) / m_sy;
5666 const double zzp = Pi * j * dy / m_sy;
5667 const double zzn = Pi * j * dym / m_sy;
5668 // Evaluate the Bessel functions for this R.
5669 if (rrm < 2.) {
5670 k0rm = Numerics::BesselK0S(rrm);
5671 k1rm = Numerics::BesselK1S(rrm);
5672 } else {
5673 k0rm = Numerics::BesselK0L(rrm);
5674 k1rm = Numerics::BesselK1L(rrm);
5675 }
5676 // Get the field components.
5677 const double czzp = cos(zzp);
5678 const double czzn = cos(zzn);
5679 vsum += (1. / m_sy) * k0rm * (czzp - czzn);
5680 err = (TwoPi / (m_sy * m_sy)) * k1rm * (czzp - czzn);
5681 ezz = (TwoPi / (m_sy * m_sy)) * k0rm * (sin(zzp) - sin(zzn));
5682 exsum += err * dxm / sqrt(dxm * dxm + dz * dz);
5683 ezsum += err * dz / sqrt(dxm * dxm + dz * dz);
5684 eysum += ezz;
5685 }
5686 } else {
5687 // Polynomial sum.
5688 // Loop over the terms.
5689 for (unsigned int j = 0; j <= m_nTermPoly; ++j) {
5690 // Simplify the references to the distances.
5691 rr1 = sqrt(pow(dy + j * 2 * m_sy, 2) + dxm * dxm + dz * dz);
5692 rr2 = sqrt(pow(dy - j * 2 * m_sy, 2) + dxm * dxm + dz * dz);
5693 rm1 = sqrt(pow(dym - j * 2 * m_sy, 2) + dxm * dxm + dz * dz);
5694 rm2 = sqrt(pow(dym + j * 2 * m_sy, 2) + dxm * dxm + dz * dz);
5695 const double rr13 = pow(rr1, 3);
5696 const double rm13 = pow(rm1, 3);
5697 // Initialisation of the sum: only a charge and a mirror charge.
5698 if (j == 0) {
5699 vsum += -1. / rr1 + 1. / rm1;
5700 exsum += -dxm * (1. / rr13 - 1. / rm13);
5701 ezsum += -dz * (1. / rr13 - 1. / rm13);
5702 eysum += -dy / rr13 + dym / rm13;
5703 continue;
5704 }
5705 const double rr23 = pow(rr2, 3);
5706 const double rm23 = pow(rm2, 3);
5707 // Further terms in the series: 2 charges and 2 mirror charges.
5708 vsum += -1. / rr1 - 1. / rr2 + 1. / rm1 + 1. / rm2;
5709 exsum += -dxm * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5710 ezsum += -dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5711 eysum += -(dy + j * 2 * m_sy) / rr13 - (dy - j * 2 * m_sy) / rr23 +
5712 (dym - j * 2 * m_sy) / rm13 + (dym + j * 2 * m_sy) / rm23;
5713 }
5714 }
5715 }
5716 ex += charge.e * exsum;
5717 ey += charge.e * eysum;
5718 ez += charge.e * ezsum;
5719 volt += charge.e * vsum;
5720 }
5721}
5722
5723void ComponentAnalyticField::Field3dD10(const double xxpos, const double yypos,
5724 const double zzpos, double& eex,
5725 double& eey, double& eez,
5726 double& volt) {
5727 //-----------------------------------------------------------------------
5728 // E3DD10 - Subroutine adding 3-dimensional charges to tubes with one
5729 // wire running down the centre.
5730 // The series expansions for the modified Bessel functions
5731 // have been taken from Abramowitz and Stegun.
5732 // VARIABLES : See routine E3DA00 for most of the variables.
5733 // (Last changed on 25/11/95.)
5734 //-----------------------------------------------------------------------
5735
5736 const double rcut = 1.;
5737
5738 double x3d, y3d, z3d;
5739 double exsum = 0., eysum = 0., ezsum = 0., vsum = 0.;
5740 double rr1, rr2, rm1, rm2, err, ezz;
5741 double k0r, k1r;
5742
5743 // Initialise the sums for the field components.
5744 eex = eey = eez = volt = 0.;
5745 double ex = 0., ey = 0., ez = 0.;
5746
5747 // Ensure that the routine can actually work.
5748 if (m_nWires < 1) {
5749 std::cerr << m_className << "::Field3dD10:\n";
5750 std::cerr << " Inappropriate potential function.\n";
5751 return;
5752 }
5753
5754 // Define a periodicity and one plane in the mapped frame.
5755 const double ssx = log(m_cotube / m_w[0].r);
5756 const double cpl = log(m_w[0].r);
5757
5758 // Transform the coordinates to the mapped frame.
5759 const double xpos = 0.5 * log(xxpos * xxpos + yypos * yypos);
5760 const double ypos = atan2(yypos, xxpos);
5761 const double zpos = zzpos;
5762
5763 // Loop over all point charges.
5764 for (const auto& charge : m_ch3d) {
5765 for (int ii = -1; ii <= 1; ++ii) {
5766 // TODO
5767 x3d = 0.5 * log(charge.x * charge.x + charge.y * charge.y);
5768 y3d = atan2(charge.y, charge.x + ii * TwoPi);
5769 z3d = charge.z;
5770 const double dx = xpos - x3d;
5771 const double dy = ypos - y3d;
5772 const double dz = zpos - z3d;
5773 const double dxm = xpos + x3d - 2 * cpl;
5774 // Skip wires that are on the charge.
5775 if (xpos == x3d && ypos == y3d && zpos == z3d) continue;
5776 // In the far away zone, sum the modified Bessel function series.
5777 if (dy * dy + dz * dz > pow(rcut * 2 * ssx, 2)) {
5778 // Initialise the per-wire sum.
5779 exsum = eysum = ezsum = vsum = 0.;
5780 // Loop over the terms in the series.
5781 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5782 // Obtain reduced coordinates.
5783 const double rr = Pi * j * sqrt(dy * dy + dz * dz) / ssx;
5784 const double zzp = Pi * j * dx / ssx;
5785 const double zzn = Pi * j * dxm / ssx;
5786 // Evaluate the Bessel functions for this R.
5787 if (rr < 2.) {
5788 k0r = Numerics::BesselK0S(rr);
5789 k1r = Numerics::BesselK1S(rr);
5790 } else {
5791 k0r = Numerics::BesselK0L(rr);
5792 k1r = Numerics::BesselK1L(rr);
5793 }
5794 // Get the field components.
5795 const double czzp = cos(zzp);
5796 const double czzn = cos(zzn);
5797 vsum += (1. / ssx) * k0r * (czzp - czzn);
5798 err = (j * TwoPi / (ssx * ssx)) * k1r * (czzp - czzn);
5799 ezz = (j * TwoPi / (ssx * ssx)) * k0r * (sin(zzp) - sin(zzn));
5800 exsum += ezz;
5801 eysum += err * dy / sqrt(dy * dy + dz * dz);
5802 ezsum += err * dz / sqrt(dy * dy + dz * dz);
5803 }
5804 } else {
5805 // Direct polynomial summing, obtain reduced coordinates.
5806 // Loop over the terms.
5807 // TODO: <=?
5808 for (unsigned int j = 0; j < m_nTermPoly; ++j) {
5809 // Simplify the references to the distances.
5810 rr1 = sqrt(pow(dx + j * 2 * ssx, 2) + dy * dy + dz * dz);
5811 rr2 = sqrt(pow(dx - j * 2 * ssx, 2) + dy * dy + dz * dz);
5812 rm1 = sqrt(pow(dxm - j * 2 * ssx, 2) + dy * dy + dz * dz);
5813 rm2 = sqrt(pow(dxm + j * 2 * ssx, 2) + dy * dy + dz * dz);
5814 const double rr13 = pow(rr1, 3);
5815 const double rm13 = pow(rm1, 3);
5816 // Initialisation of the sum: only a charge and a mirror charge.
5817 if (j == 0) {
5818 vsum = 1. / rr1 - 1. / rm1;
5819 exsum = dxm / rr13 - dxm / rm13;
5820 eysum = dy * (1. / rr13 - 1. / rm13);
5821 ezsum = dz * (1. / rr13 - 1. / rm13);
5822 continue;
5823 }
5824 const double rr23 = pow(rr2, 3);
5825 const double rm23 = pow(rm2, 3);
5826 // Further terms in the series: 2 charges and 2 mirror charges.
5827 vsum += 1. / rr1 + 1. / rr2 - 1. / rm1 - 1. / rm2;
5828 exsum += (dx + j * 2 * ssx) / rr13 + (dx - j * 2 * ssx) / rr23 -
5829 (dxm - j * 2 * ssx) / rm13 - (dxm + j * 2 * ssx) / rm23;
5830 eysum += dy * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5831 ezsum += dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5832 }
5833 }
5834 ex += charge.e * exsum;
5835 ey += charge.e * eysum;
5836 ez += charge.e * ezsum;
5837 // Finish the loop over the charges.
5838 }
5839 }
5840
5841 // Transform the field vectors back to Cartesian coordinates.
5842 eex = exp(-xpos) * (ex * cos(ypos) - ey * sin(ypos));
5843 eey = exp(-ypos) * (ex * sin(ypos) + ey * cos(ypos));
5844 eez = ez;
5845}
5846
5847bool ComponentAnalyticField::PrepareSignals() {
5848 //-----------------------------------------------------------------------
5849 // SIGINI - Initialises signal calculations.
5850 // (Last changed on 11/10/06.)
5851 //-----------------------------------------------------------------------
5852
5853 if (m_readout.empty()) {
5854 std::cerr << m_className << "::PrepareSignals:\n";
5855 std::cerr << " There are no readout groups defined.\n";
5856 std::cerr << " Calculation of weighting fields makes no sense.\n";
5857 return false;
5858 }
5859
5860 if (!m_cellset && !Prepare()) {
5861 std::cerr << m_className << "::PrepareSignals: Cell not set up.\n";
5862 return false;
5863 }
5864
5865 // If using natural periodicity, copy the cell type.
5866 // Otherwise, eliminate true periodicities.
5867 if (m_nFourier == 0) {
5868 m_cellTypeFourier = m_cellType;
5869 } else if (m_cellType == A00 || m_cellType == B1X || m_cellType == B1Y ||
5870 m_cellType == C10) {
5871 m_cellTypeFourier = A00;
5872 } else if (m_cellType == B2X || m_cellType == C2X) {
5873 m_cellTypeFourier = B2X;
5874 } else if (m_cellType == B2Y || m_cellType == C2Y) {
5875 m_cellTypeFourier = B2Y;
5876 } else if (m_cellType == C30) {
5877 m_cellTypeFourier = C30;
5878 } else if (m_cellType == D10) {
5879 m_cellTypeFourier = D10;
5880 } else if (m_cellType == D30) {
5881 m_cellTypeFourier = D30;
5882 } else {
5883 // Other cases.
5884 std::cerr << m_className << "::PrepareSignals:\n"
5885 << " No potentials available to handle cell type "
5886 << GetCellType(m_cellType) << "\n.";
5887 return false;
5888 }
5889
5890 // Establish the directions in which convolutions occur.
5891 m_fperx = m_fpery = false;
5892 if (m_nFourier == 0) {
5893 m_mfexp = 0;
5894 } else {
5895 if (m_cellType == B1X || m_cellType == C10 || m_cellType == C2Y) {
5896 m_fperx = true;
5897 }
5898 if (m_cellType == B1Y || m_cellType == C10 || m_cellType == C2X) {
5899 m_fpery = true;
5900 }
5901 m_mfexp = int(0.1 + log(1. * m_nFourier) / log(2.));
5902 if (m_mfexp == 0) {
5903 m_fperx = m_fpery = false;
5904 }
5905 }
5906 // Set maximum and minimum Fourier terms.
5907 m_mxmin = m_mymin = m_mxmax = m_mymax = 0;
5908 if (m_fperx) {
5909 m_mxmin = std::min(0, 1 - m_nFourier / 2);
5910 m_mxmax = m_nFourier / 2;
5911 }
5912 if (m_fpery) {
5913 m_mymin = std::min(0, 1 - m_nFourier / 2);
5914 m_mymax = m_nFourier / 2;
5915 }
5916
5917 // Print some debugging output if requested.
5918 if (m_debug) {
5919 std::cout << m_className << "::PrepareSignals:\n"
5920 << " Cell type: " << GetCellType(m_cellType) << "\n"
5921 << " Fourier cell type: " << GetCellType(m_cellTypeFourier)
5922 << "\n";
5923 std::cout << " x convolutions: " << m_fperx << "\n"
5924 << " y convolutions: " << m_fpery << "\n";
5925 std::cout << " No of Fourier terms: " << m_nFourier << " (= 2**"
5926 << m_mfexp << ")\n";
5927 }
5928
5929 // Prepare the signal matrices.
5930 if (!SetupWireSignals()) {
5931 std::cerr << m_className << "::PrepareSignals:\n";
5932 std::cerr << " Preparing wire signal capacitance matrices failed.\n";
5933 m_sigmat.clear();
5934 return false;
5935 }
5936 if (!SetupPlaneSignals()) {
5937 std::cerr << m_className << "::PrepareSignals:\n";
5938 std::cerr << " Preparing plane charges failed.\n";
5939 m_sigmat.clear();
5940 m_qplane.clear();
5941 return false;
5942 }
5943
5944 // And open the signal file.
5945 // CALL SIGIST('OPEN',0,DUMMY,DUMMY,0,0,0,0,IFAIL1)
5946
5947 // Associate wires, planes and strips with readout groups
5948 const unsigned int nReadout = m_readout.size();
5949 for (unsigned int i = 0; i < nReadout; ++i) {
5950 for (auto& wire : m_w) {
5951 if (wire.type == m_readout[i]) wire.ind = i;
5952 }
5953 for (unsigned int j = 0; j < 5; ++j) {
5954 if (m_planes[j].type == m_readout[i]) m_planes[j].ind = i;
5955 for (auto& strip : m_planes[j].strips1) {
5956 if (strip.type == m_readout[i]) strip.ind = i;
5957 }
5958 for (auto& strip : m_planes[j].strips2) {
5959 if (strip.type == m_readout[i]) strip.ind = i;
5960 }
5961 for (auto& pixel : m_planes[j].pixels) {
5962 if (pixel.type == m_readout[i]) pixel.ind = i;
5963 }
5964 }
5965 }
5966
5967 // Seems to have worked.
5968 m_sigset = true;
5969 return true;
5970}
5971
5972bool ComponentAnalyticField::SetupWireSignals() {
5973 //-----------------------------------------------------------------------
5974 // SIGIPR - Prepares the ion tail calculation by filling the signal
5975 // matrices (ie non-periodic capacitance matrices),
5976 // Fourier transforming them if necessary, inverting them and
5977 // Fourier transforming them back. Because of the large number
5978 // of terms involved, a (scratch) external file on unit 13 is
5979 // used to store the intermediate and final results. This file
5980 // is handled by the routines IONBGN and IONIO.
5981 // VARIABLES : FFTMAT : Matrix used for Fourier transforms.
5982 // (Last changed on 4/10/06.)
5983 //-----------------------------------------------------------------------
5984
5985 m_sigmat.assign(m_nWires, std::vector<std::complex<double> >(m_nWires));
5986
5987 std::vector<std::vector<std::complex<double> > > fftmat;
5988
5989 if (m_fperx || m_fpery) {
5990 fftmat.resize(m_nFourier);
5991 for (int i = 0; i < m_nFourier; ++i) {
5992 fftmat[i].resize(m_nWires);
5993 }
5994 }
5995
5996 if (m_fperx || m_fpery) {
5997 // CALL IONBGN(IFAIL)
5998 // IF(IFAIL.EQ.1)THEN
5999 // PRINT *,' !!!!!! SIGIPR WARNING : No storage'
6000 // ' available for the signal matrices; no'
6001 // ' induced currents.'
6002 // RETURN
6003 // ENDIF
6004 }
6005
6006 // Have the matrix/matrices filled (and stored).
6007 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6008 for (int my = m_mymin; my <= m_mymax; ++my) {
6009 // Select layer to be produced.
6010 if (m_cellTypeFourier == A00) {
6011 IprA00(mx, my);
6012 } else if (m_cellTypeFourier == B2X) {
6013 IprB2X(my);
6014 } else if (m_cellTypeFourier == B2Y) {
6015 IprB2Y(mx);
6016 } else if (m_cellTypeFourier == C2X) {
6017 IprC2X();
6018 } else if (m_cellTypeFourier == C2Y) {
6019 IprC2Y();
6020 } else if (m_cellTypeFourier == C30) {
6021 IprC30();
6022 } else if (m_cellTypeFourier == D10) {
6023 IprD10();
6024 } else if (m_cellTypeFourier == D30) {
6025 IprD30();
6026 } else {
6027 std::cerr << m_className << "::SetupWireSignals:\n";
6028 std::cerr << " Unknown signal cell type " << m_cellTypeFourier
6029 << "\n";
6030 return false;
6031 }
6032 if (m_debug) {
6033 std::cout << m_className << "::SetupWireSignals:\n";
6034 std::cout << " Signal matrix MX = " << mx << ", MY = " << my
6035 << " has been calculated.\n";
6036 }
6037 if (m_fperx || m_fpery) {
6038 // Store the matrix.
6039 // CALL IONIO(MX,MY,1,0,IFAIL)
6040 // Quit if storing failed.
6041 // IF(IFAIL.NE.0)GOTO 2010
6042 }
6043 // Dump the signal matrix before inversion, if DEBUG is requested.
6044 if (m_debug) {
6045 std::cout << m_className << "::SetupWireSignals:\n";
6046 std::cout << " Dump of signal matrix (" << mx << ", " << my
6047 << ") before inversion:\n";
6048 for (unsigned int i = 0; i < m_nWires; i += 10) {
6049 for (unsigned int j = 0; j < m_nWires; j += 10) {
6050 std::cout << " (Re-Block " << i / 10 << ", " << j / 10 << ")\n";
6051 for (unsigned int ii = 0; ii < 10; ++ii) {
6052 if (i + ii >= m_nWires) break;
6053 for (unsigned int jj = 0; jj < 10; ++jj) {
6054 if (j + jj >= m_nWires) break;
6055 std::cout << real(m_sigmat[i + ii][j + jj]) << " ";
6056 }
6057 std::cout << "\n";
6058 }
6059 std::cout << "\n";
6060 std::cout << " (Im-Block " << i / 10 << ", " << j / 10 << ")\n";
6061 for (unsigned int ii = 0; ii < 10; ++ii) {
6062 if (i + ii >= m_nWires) break;
6063 for (unsigned int jj = 0; jj < 10; ++jj) {
6064 if (j + jj >= m_nWires) break;
6065 std::cout << imag(m_sigmat[i + ii][j + jj]) << " ";
6066 }
6067 std::cout << "\n";
6068 }
6069 std::cout << "\n";
6070 }
6071 }
6072 std::cout << m_className << "::SetupWireSignals:\n";
6073 std::cout << " End of the uninverted capacitance matrix dump.\n";
6074 }
6075 // Next layer.
6076 }
6077 }
6078
6079 // Have them fourier transformed (singly periodic case).
6080 if ((m_fperx && !m_fpery) || (m_fpery && !m_fperx)) {
6081 for (unsigned int i = 0; i < m_nWires; ++i) {
6082 for (int m = -m_nFourier / 2; m < m_nFourier / 2; ++m) {
6083 // CALL IONIO(M,M,2,I,IFAIL)
6084 // IF(IFAIL.NE.0)GOTO 2010
6085 for (unsigned int j = 0; j < m_nWires; ++j) {
6086 fftmat[m + m_nFourier / 2][j] = m_sigmat[i][j];
6087 }
6088 }
6089 for (unsigned int j = 0; j < m_nWires; ++j) {
6090 // CALL CFFT(FFTMAT(1,J),MFEXP)
6091 }
6092 for (int m = -m_nFourier / 2; m < m_nFourier / 2; ++m) {
6093 // CALL IONIO(M,M,2,I,IFAIL)
6094 // IF(IFAIL.NE.0)GOTO 2010
6095 for (unsigned int j = 0; j < m_nWires; ++j) {
6096 m_sigmat[i][j] = fftmat[m + m_nFourier / 2][j];
6097 }
6098 // CALL IONIO(M,M,1,I,IFAIL)
6099 // IF(IFAIL.NE.0)GOTO 2010
6100 }
6101 }
6102 }
6103 // Have them fourier transformed (doubly periodic case).
6104 if (m_fperx || m_fpery) {
6105 for (unsigned int i = 0; i < m_nWires; ++i) {
6106 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6107 for (int my = m_mymin; my <= m_mymax; ++my) {
6108 // CALL IONIO(MX,MY,2,I,IFAIL)
6109 // IF(IFAIL.NE.0)GOTO 2010
6110 for (unsigned int j = 0; j < m_nWires; ++j) {
6111 fftmat[my + m_nFourier / 2 - 1][j] = m_sigmat[i][j];
6112 }
6113 }
6114 for (unsigned int j = 0; j < m_nWires; ++j) {
6115 // CALL CFFT(FFTMAT(1,J),MFEXP)
6116 }
6117 for (int my = m_mymin; my <= m_mymax; ++my) {
6118 // CALL IONIO(MX,MY,2,I,IFAIL)
6119 // IF(IFAIL.NE.0)GOTO 2010
6120 for (unsigned int j = 0; j < m_nWires; ++j) {
6121 m_sigmat[i][j] = fftmat[my + m_nFourier / 2 - 1][j];
6122 }
6123 // CALL IONIO(MX,MY,1,I,IFAIL)
6124 // IF(IFAIL.NE.0)GOTO 2010
6125 }
6126 }
6127 for (int my = m_mymin; my <= m_mymax; ++my) {
6128 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6129 // CALL IONIO(MX,MY,2,I,IFAIL)
6130 // IF(IFAIL.NE.0)GOTO 2010
6131 for (unsigned int j = 0; j < m_nWires; ++j) {
6132 fftmat[mx + m_nFourier / 2 - 1][j] = m_sigmat[i][j];
6133 }
6134 }
6135 for (unsigned int j = 0; j < m_nWires; ++j) {
6136 // CALL CFFT(FFTMAT(1,J),MFEXP)
6137 }
6138 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6139 // CALL IONIO(MX,MY,2,I,IFAIL)
6140 // IF(IFAIL.NE.0)GOTO 2010
6141 for (unsigned int j = 0; j < m_nWires; ++j) {
6142 m_sigmat[i][j] = fftmat[mx + m_nFourier / 2 - 1][j];
6143 }
6144 // CALL IONIO(MX,MY,1,I,IFAIL)
6145 // IF(IFAIL.NE.0)GOTO 2010
6146 }
6147 }
6148 }
6149 }
6150
6151 // Invert the matrices.
6152 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6153 for (int my = m_mymin; my <= m_mymax; ++my) {
6154 // Retrieve the layer.
6155 if (m_fperx || m_fpery) {
6156 // CALL IONIO(MX,MY,2,0,IFAIL)
6157 // IF(IFAIL.NE.0)GOTO 2010
6158 }
6159 // Invert.
6160 if (m_nWires >= 1) {
6161 if (Numerics::CERNLIB::cinv(m_nWires, m_sigmat) != 0) {
6162 std::cerr << m_className << "::PrepareWireSignals:\n";
6163 std::cerr << " Inversion of signal matrix (" << mx << ", " << my
6164 << ") failed.\n";
6165 std::cerr << " No reliable results.\n";
6166 std::cerr << " Preparation of weighting fields is abandoned.\n";
6167 return false;
6168 }
6169 }
6170 // Store the matrix back.
6171 if (m_fperx || m_fpery) {
6172 // CALL IONIO(MX,MY,1,0,IFAIL)
6173 // IF(IFAIL.NE.0)GOTO 2010
6174 }
6175 // Next layer.
6176 }
6177 }
6178
6179 // And transform the matrices back to the original domain.
6180 if ((m_fperx && !m_fpery) || (m_fpery && !m_fperx)) {
6181 for (unsigned int i = 0; i < m_nWires; ++i) {
6182 for (int m = -m_nFourier / 2; m < m_nFourier / 2; ++m) {
6183 // CALL IONIO(M,M,2,I,IFAIL)
6184 // IF(IFAIL.NE.0)GOTO 2010
6185 for (unsigned int j = 0; j < m_nWires; ++j) {
6186 fftmat[m + m_nFourier / 2][j] = m_sigmat[i][j];
6187 }
6188 }
6189 for (unsigned int j = 0; j < m_nWires; ++j) {
6190 // CALL CFFT(FFTMAT(1,J),-MFEXP)
6191 }
6192 for (int m = -m_nFourier / 2; m < m_nFourier / 2; ++m) {
6193 // CALL IONIO(M,M,2,I,IFAIL)
6194 // IF(IFAIL.NE.0)GOTO 2010
6195 for (unsigned int j = 0; j < m_nWires; ++j) {
6196 m_sigmat[i][j] = fftmat[m + m_nFourier / 2][j] / double(m_nFourier);
6197 }
6198 // CALL IONIO(M,M,1,I,IFAIL)
6199 // IF(IFAIL.NE.0)GOTO 2010
6200 }
6201 }
6202 }
6203 // Have them transformed to the original domain (doubly periodic).
6204 if (m_fperx && m_fpery) {
6205 for (unsigned int i = 0; i < m_nWires; ++i) {
6206 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6207 for (int my = m_mymin; my <= m_mymax; ++my) {
6208 // CALL IONIO(MX,MY,2,I,IFAIL)
6209 // IF(IFAIL.NE.0)GOTO 2010
6210 for (unsigned int j = 0; j < m_nWires; ++j) {
6211 fftmat[my + m_nFourier / 2 - 1][j] = m_sigmat[i][j];
6212 }
6213 }
6214 for (unsigned int j = 0; j < m_nWires; ++j) {
6215 // CFFT(FFTMAT(1,J),-MFEXP)
6216 }
6217 for (int my = m_mymin; my <= m_mymax; ++my) {
6218 // CALL IONIO(MX,MY,2,I,IFAIL)
6219 // IF(IFAIL.NE.0)GOTO 2010
6220 for (unsigned int j = 0; j < m_nWires; ++j) {
6221 m_sigmat[i][j] =
6222 fftmat[my + m_nFourier / 2 - 1][j] / double(m_nFourier);
6223 }
6224 // CALL IONIO(MX,MY,1,I,IFAIL)
6225 // IF(IFAIL.NE.0)GOTO 2010
6226 }
6227 }
6228 for (int my = m_mymin; my <= m_mymax; ++my) {
6229 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6230 // CALL IONIO(MX,MY,2,I,IFAIL)
6231 // IF(IFAIL.NE.0)GOTO 2010
6232 for (unsigned int j = 0; j < m_nWires; ++j) {
6233 fftmat[mx + m_nFourier / 2 - 1][j] = m_sigmat[i][j];
6234 }
6235 }
6236 for (unsigned int j = 0; j < m_nWires; ++j) {
6237 // CALL CFFT(FFTMAT(1,J),-MFEXP)
6238 }
6239 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6240 // CALL IONIO(MX,MY,2,I,IFAIL)
6241 // IF(IFAIL.NE.0)GOTO 2010
6242 for (unsigned int j = 0; j < m_nWires; ++j) {
6243 m_sigmat[i][j] =
6244 fftmat[mx + m_nFourier / 2 - 1][j] / double(m_nFourier);
6245 }
6246 // CALL IONIO(MX,MY,1,I,IFAIL)
6247 // IF(IFAIL.NE.0)GOTO 2010
6248 }
6249 }
6250 }
6251 }
6252
6253 // Dump the signal matrix after inversion, if DEBUG is requested.
6254 if (m_debug) {
6255 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6256 for (int my = m_mymin; my <= m_mymax; ++my) {
6257 std::cout << m_className << "::SetupWireSignals:\n";
6258 std::cout << " Dump of signal matrix (" << mx << ", " << my
6259 << ") after inversion:\n";
6260 for (unsigned int i = 0; i < m_nWires; i += 10) {
6261 for (unsigned int j = 0; j < m_nWires; j += 10) {
6262 std::cout << " (Re-Block " << i / 10 << ", " << j / 10 << ")\n";
6263 for (unsigned int ii = 0; ii < 10; ++ii) {
6264 if (i + ii >= m_nWires) break;
6265 for (unsigned int jj = 0; jj < 10; ++jj) {
6266 if (j + jj >= m_nWires) break;
6267 std::cout << real(m_sigmat[i + ii][j + jj]) << " ";
6268 }
6269 std::cout << "\n";
6270 }
6271 std::cout << "\n";
6272 std::cout << " (Im-Block " << i / 10 << ", " << j / 10 << ")\n";
6273 for (int ii = 0; ii < 10; ++ii) {
6274 if (i + ii >= m_nWires) break;
6275 for (int jj = 0; jj < 10; ++jj) {
6276 if (j + jj >= m_nWires) break;
6277 std::cout << imag(m_sigmat[i + ii][j + jj]) << " ";
6278 }
6279 std::cout << "\n";
6280 }
6281 std::cout << "\n";
6282 }
6283 }
6284 std::cout << m_className << "::SetupWireSignals:\n";
6285 std::cout << " End of the inverted capacitance matrix dump.\n";
6286 }
6287 }
6288 }
6289 return true;
6290}
6291
6292bool ComponentAnalyticField::SetupPlaneSignals() {
6293 //-----------------------------------------------------------------------
6294 // SIGPLP - Computes the weighting field charges for the planes and
6295 // the tube.
6296 // (Last changed on 14/10/99.)
6297 //-----------------------------------------------------------------------
6298
6299 const int nPlanes = 5;
6300 m_qplane.assign(nPlanes, std::vector<double>(m_nWires, 0.));
6301
6302 double vw;
6303
6304 // Loop over the signal layers.
6305 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6306 for (int my = m_mymin; my <= m_mymax; ++my) {
6307 // Load the layers of the signal matrices.
6308 // CALL IONIO(MX,MY,2,0,IFAIL1)
6309 // IF(IFAIL1.NE.0)THEN
6310 // PRINT *,' !!!!!! SIGPLP WARNING : Signal matrix'//
6311 // ' store error; field for planes not prepared.'
6312 // RETURN
6313 // ENDIF
6314 // Initialise the plane matrices.
6315 m_qplane.assign(nPlanes, std::vector<double>(m_nWires, 0.));
6316 // Charges for plane 1, if present.
6317 if (m_ynplan[0]) {
6318 // Set the weighting field voltages.
6319 for (unsigned int i = 0; i < m_nWires; ++i) {
6320 if (m_ynplan[1]) {
6321 vw = -(m_coplan[1] - m_w[i].x) / (m_coplan[1] - m_coplan[0]);
6322 } else if (m_perx) {
6323 vw = -(m_coplan[0] + m_sx - m_w[i].x) / m_sx;
6324 } else {
6325 vw = -1;
6326 }
6327 // Multiply with the matrix.
6328 for (unsigned int j = 0; j < m_nWires; ++j) {
6329 m_qplane[0][j] += real(m_sigmat[i][j]) * vw;
6330 }
6331 }
6332 }
6333 // Charges for plane 2, if present.
6334 if (m_ynplan[1]) {
6335 // Set the weighting field voltages.
6336 for (unsigned int i = 0; i < m_nWires; ++i) {
6337 if (m_ynplan[0]) {
6338 vw = -(m_coplan[0] - m_w[i].x) / (m_coplan[0] - m_coplan[1]);
6339 } else if (m_perx) {
6340 vw = -(m_w[i].x - m_coplan[1] + m_sx) / m_sx;
6341 } else {
6342 vw = -1.;
6343 }
6344 // Multiply with the matrix.
6345 for (unsigned int j = 0; j < m_nWires; ++j) {
6346 m_qplane[1][j] += real(m_sigmat[i][j]) * vw;
6347 }
6348 }
6349 }
6350 // Charges for plane 3, if present.
6351 if (m_ynplan[2]) {
6352 // Set the weighting field voltages.
6353 for (unsigned int i = 0; i < m_nWires; ++i) {
6354 if (m_ynplan[3]) {
6355 vw = -(m_coplan[3] - m_w[i].y) / (m_coplan[3] - m_coplan[2]);
6356 } else if (m_pery) {
6357 vw = -(m_coplan[2] + m_sy - m_w[i].y) / m_sy;
6358 } else {
6359 vw = -1.;
6360 }
6361 // Multiply with the matrix.
6362 for (unsigned int j = 0; j < m_nWires; ++j) {
6363 m_qplane[2][i] += real(m_sigmat[i][j]) * vw;
6364 }
6365 }
6366 }
6367 // Charges for plane 4, if present.
6368 if (m_ynplan[3]) {
6369 // Set the weighting field voltages.
6370 for (unsigned int i = 0; i < m_nWires; ++i) {
6371 if (m_ynplan[2]) {
6372 vw = -(m_coplan[2] - m_w[i].y) / (m_coplan[2] - m_coplan[3]);
6373 } else if (m_pery) {
6374 vw = -(m_w[i].y - m_coplan[3] + m_sy) / m_sy;
6375 } else {
6376 vw = -1.;
6377 }
6378 // Multiply with the matrix.
6379 for (unsigned int j = 0; j < m_nWires; ++j) {
6380 m_qplane[3][i] += real(m_sigmat[i][j]) * vw;
6381 }
6382 }
6383 }
6384 // Charges for the tube, if present.
6385 if (m_tube) {
6386 for (unsigned int i = 0; i < m_nWires; ++i) {
6387 for (unsigned int j = 0; j < m_nWires; ++j) {
6388 m_qplane[4][i] -= real(m_sigmat[i][j]);
6389 }
6390 }
6391 }
6392 // Store the plane charges.
6393 // CALL IPLIO(MX,MY,1,IFAIL1)
6394 // IF(IFAIL1.NE.0)THEN
6395 // PRINT *,' !!!!!! SIGPLP WARNING : Plane matrix'//
6396 // ' store error; field for planes not prepared.'
6397 // RETURN
6398 // ENDIF
6399 // Next layer.
6400 }
6401 }
6402 // Compute the background weighting fields, first in x.
6403 if (m_ynplan[0] && m_ynplan[1]) {
6404 m_planes[0].ewxcor = 1. / (m_coplan[1] - m_coplan[0]);
6405 m_planes[1].ewxcor = 1. / (m_coplan[0] - m_coplan[1]);
6406 } else if (m_ynplan[0] && m_perx) {
6407 m_planes[0].ewxcor = 1. / m_sx;
6408 m_planes[1].ewxcor = 0.;
6409 } else if (m_ynplan[1] && m_perx) {
6410 m_planes[0].ewxcor = 0.;
6411 m_planes[1].ewxcor = -1. / m_sx;
6412 } else {
6413 m_planes[0].ewxcor = m_planes[1].ewxcor = 0.;
6414 }
6415 m_planes[2].ewxcor = m_planes[3].ewxcor = m_planes[4].ewxcor = 0.;
6416 // Next also in y.
6417 m_planes[0].ewycor = m_planes[1].ewycor = 0.;
6418 if (m_ynplan[2] && m_ynplan[3]) {
6419 m_planes[2].ewycor = 1. / (m_coplan[3] - m_coplan[2]);
6420 m_planes[3].ewycor = 1. / (m_coplan[2] - m_coplan[3]);
6421 } else if (m_ynplan[2] && m_pery) {
6422 m_planes[2].ewycor = 1. / m_sy;
6423 m_planes[3].ewycor = 0.;
6424 } else if (m_ynplan[3] && m_pery) {
6425 m_planes[2].ewycor = 0.;
6426 m_planes[3].ewycor = -1. / m_sy;
6427 } else {
6428 m_planes[2].ewycor = m_planes[3].ewycor = 0.;
6429 }
6430 // The tube has no correction field.
6431 m_planes[4].ewycor = 0.;
6432
6433 // Debugging output.
6434 if (m_debug) {
6435 std::cout << m_className << "::SetupPlaneSignals:\n";
6436 std::cout << " Charges for currents induced in the planes:\n";
6437 std::cout << " Wire x-Plane 1 x-Plane 2"
6438 << " y-Plane 1 y-Plane 2"
6439 << " Tube\n";
6440 for (unsigned int i = 0; i < m_nWires; ++i) {
6441 std::cout << " " << i << " " << m_qplane[0][i] << " "
6442 << m_qplane[1][i] << " " << m_qplane[2][i] << " "
6443 << m_qplane[3][i] << " " << m_qplane[4][i] << "\n";
6444 }
6445 std::cout << m_className << "::SetupPlaneSignals:\n";
6446 std::cout << " Bias fields:\n";
6447 std::cout << " Plane x-Bias [1/cm] y-Bias [1/cm]\n";
6448 for (int i = 0; i < 4; ++i) {
6449 std::cout << " " << i << " " << m_planes[i].ewxcor << " "
6450 << m_planes[i].ewycor << "\n";
6451 }
6452 }
6453
6454 return true;
6455}
6456
6457bool ComponentAnalyticField::IprA00(const int mx, const int my) {
6458 //-----------------------------------------------------------------------
6459 // IPRA00 - Routine filling the (MX,MY) th layer of the signal matrix
6460 // for cells with non-periodic type A (see SIGIPR).
6461 //-----------------------------------------------------------------------
6462
6463 const double dx = mx * m_sx;
6464 const double dy = my * m_sy;
6465 double aa = 0.;
6466
6467 for (unsigned int i = 0; i < m_nWires; ++i) {
6468 // Diagonal terms.
6469 if (dx != 0. || dy != 0.) {
6470 aa = dx * dx + dy * dy;
6471 } else {
6472 aa = m_w[i].r * m_w[i].r;
6473 }
6474 // Take care of single equipotential planes.
6475 if (m_ynplax) aa /= 2. * pow(m_w[i].x - m_coplax, 2) + dy * dy;
6476 if (m_ynplay) aa /= 2. * pow(m_w[i].y - m_coplay, 2) + dx * dx;
6477 // Take care of pairs of equipotential planes.
6478 if (m_ynplax && m_ynplay)
6479 aa *= 4. * (pow(m_w[i].x - m_coplax, 2) + pow(m_w[i].y - m_coplay, 2));
6480 // Define the final version of a[i][i].
6481 m_sigmat[i][i] = -0.5 * log(aa);
6482 for (unsigned int j = i + 1; j < m_nWires; ++j) {
6483 aa = pow(m_w[i].x + dx - m_w[j].x, 2) + pow(m_w[i].y + dy - m_w[j].y, 2);
6484 // Take care of single planes.
6485 if (m_ynplax)
6486 aa /= pow(2. * m_coplax - m_w[i].x - dx - m_w[j].x, 2) +
6487 pow(m_w[i].y + dy - m_w[j].y, 2);
6488 if (m_ynplay)
6489 aa /= pow(m_w[i].x + dx - m_w[j].x, 2) +
6490 pow(2. * m_coplay - m_w[i].y - dy - m_w[j].y, 2);
6491 // Take care of pairs of planes.
6492 if (m_ynplax && m_ynplay) {
6493 aa *= pow(2. * m_coplax - m_w[i].x - dx - m_w[j].x, 2) +
6494 pow(2. * m_coplay - m_w[i].y - dy - m_w[j].y, 2);
6495 }
6496 // Store the true versions after taking LOGs and SQRT's.
6497 m_sigmat[i][j] = -0.5 * log(aa);
6498 m_sigmat[j][i] = m_sigmat[i][j];
6499 }
6500 }
6501 return true;
6502}
6503
6504bool ComponentAnalyticField::IprB2X(const int my) {
6505 //-----------------------------------------------------------------------
6506 // IPRB2X - Routine filling the MY th layer of the signal matrix
6507 // for cells with non-periodic type B2X (see SIGIPR).
6508 // (Last changed on 26/ 4/92.)
6509 //-----------------------------------------------------------------------
6510
6511 m_b2sin.resize(m_nWires);
6512
6513 const double dy = my * m_sy;
6514 double aa = 0.;
6515 double xxneg;
6516
6517 // Loop over all wires and calculate the diagonal elements first.
6518 for (unsigned int i = 0; i < m_nWires; ++i) {
6519 double xx = (Pi / m_sx) * (m_w[i].x - m_coplan[0]);
6520 if (dy != 0.) {
6521 aa = pow(sinh(Pi * dy / m_sx) / sin(xx), 2);
6522 } else {
6523 aa = pow((0.5 * m_w[i].r * Pi / m_sx) / sin(xx), 2);
6524 }
6525 // Take care of a planes at constant y (no dy in this case).
6526 if (m_ynplay) {
6527 const double yymirr = (Pi / m_sx) * (m_w[i].y - m_coplay);
6528 if (fabs(yymirr) <= 20.) {
6529 const double sinhy = sinh(yymirr);
6530 const double sinxx = sin(xx);
6531 aa *= (sinhy * sinhy + sinxx * sinxx) / (sinhy * sinhy);
6532 }
6533 }
6534 // Store the true value of A[i][i].
6535 m_sigmat[i][i] = -0.5 * log(aa);
6536 // Loop over all other wires to obtain off-diagonal elements.
6537 for (unsigned int j = i + 1; j < m_nWires; ++j) {
6538 const double yy = HalfPi * (m_w[i].y + dy - m_w[j].y) / m_sx;
6539 xx = HalfPi * (m_w[i].x - m_w[j].x) / m_sx;
6540 xxneg = HalfPi * (m_w[i].x + m_w[j].x - 2. * m_coplan[0]) / m_sx;
6541 if (fabs(yy) <= 20.) {
6542 const double sinhy = sinh(yy);
6543 const double sinxx = sin(xx);
6544 const double sinxxneg = sin(xxneg);
6545 aa = (sinhy * sinhy + sinxx * sinxx) /
6546 (sinhy * sinhy + sinxxneg * sinxxneg);
6547 } else {
6548 aa = 1.;
6549 }
6550 // Take equipotential planes into account (no dy anyhow).
6551 if (m_ynplay) {
6552 const double yymirr =
6553 HalfPi * (m_w[i].y + m_w[j].y - 2. * m_coplay) / m_sx;
6554 if (fabs(yymirr) <= 20.) {
6555 const double sinhy = sinh(yymirr);
6556 const double sinxx = sin(xx);
6557 const double sinxxneg = sin(xxneg);
6558 aa *= (sinhy * sinhy + sinxxneg * sinxxneg) /
6559 (sinhy * sinhy + sinxx * sinxx);
6560 }
6561 }
6562 // Store the true value of a[i][j] in both a[i][j] and a[j][i].
6563 m_sigmat[i][j] = -0.5 * log(aa);
6564 m_sigmat[j][i] = m_sigmat[i][j];
6565 }
6566 // Fill the B2SIN vector.
6567 m_b2sin[i] = sin(Pi * (m_coplan[0] - m_w[i].x) / m_sx);
6568 }
6569
6570 return true;
6571}
6572
6573bool ComponentAnalyticField::IprB2Y(const int mx) {
6574 //-----------------------------------------------------------------------
6575 // IPRB2Y - Routine filling the MX th layer of the signal matrix
6576 // for cells with non-periodic type B2Y (see SIGIPR).
6577 // (Last changed on 26/ 4/92.)
6578 //-----------------------------------------------------------------------
6579
6580 m_b2sin.resize(m_nWires);
6581
6582 const double dx = mx * m_sx;
6583 double aa = 0.;
6584 double xx, yy, xxmirr, yyneg;
6585
6586 // Loop over all wires and calculate the diagonal elements first.
6587 for (unsigned int i = 0; i < m_nWires; ++i) {
6588 yy = (Pi / m_sy) * (m_w[i].y - m_coplan[2]);
6589 if (dx != 0.) {
6590 aa = pow(sinh(Pi * dx / m_sy) / sin(yy), 2);
6591 } else {
6592 aa = pow((0.5 * m_w[i].r * Pi / m_sy) / sin(yy), 2);
6593 }
6594 // Take care of a planes at constant x (no dx in this case).
6595 if (m_ynplax) {
6596 xxmirr = (Pi / m_sy) * (m_w[i].x - m_coplax);
6597 if (fabs(xxmirr) <= 20.) {
6598 aa *= (pow(sinh(xxmirr), 2) + pow(sin(yy), 2)) / pow(sinh(xxmirr), 2);
6599 }
6600 }
6601 // Store the true value of A[i][i].
6602 m_sigmat[i][i] = -0.5 * log(aa);
6603 // Loop over all other wires to obtain off-diagonal elements.
6604 for (unsigned int j = i + 1; j < m_nWires; ++j) {
6605 xx = HalfPi * (m_w[i].x + dx - m_w[j].x) / m_sy;
6606 yy = HalfPi * (m_w[i].y - m_w[j].y) / m_sy;
6607 yyneg = HalfPi * (m_w[i].y + m_w[j].y - 2. * m_coplan[2]) / m_sy;
6608 if (fabs(xx) <= 20.) {
6609 aa = (pow(sinh(xx), 2) + pow(sin(yy), 2)) /
6610 (pow(sinh(xx), 2) + pow(sin(yyneg), 2));
6611 } else {
6612 aa = 1.;
6613 }
6614 // Take equipotential planes into account (no dx anyhow).
6615 if (m_ynplax) {
6616 xxmirr = HalfPi * (m_w[i].x + m_w[j].x - 2. * m_coplax) / m_sy;
6617 if (fabs(xxmirr) <= 20.) {
6618 aa *= (pow(sinh(xxmirr), 2) + pow(sin(yyneg), 2)) /
6619 (pow(sinh(xxmirr), 2) + pow(sin(yy), 2));
6620 }
6621 }
6622 // Store the true value of a[i][j] in both a[i][j] and a[j][i].
6623 m_sigmat[i][j] = -0.5 * log(aa);
6624 m_sigmat[j][i] = m_sigmat[i][j];
6625 }
6626 // Fill the B2SIN vector.
6627 m_b2sin[i] = sin(Pi * (m_coplan[2] - m_w[i].y) / m_sy);
6628 }
6629 return true;
6630}
6631
6632bool ComponentAnalyticField::IprC2X() {
6633 //-----------------------------------------------------------------------
6634 // IPRC2X - This initializing subroutine stores the capacitance matrix
6635 // for the configuration:
6636 // wires at zw(j)+cmplx(lx*2*sx,ly*sy),
6637 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
6638 // but the signs of the charges alternate in the x-direction
6639 // (Last changed on 4/10/06.)
6640 //-----------------------------------------------------------------------
6641
6642 // Fill the capacitance matrix.
6643 for (unsigned int i = 0; i < m_nWires; ++i) {
6644 double cx = m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
6645 for (unsigned int j = 0; j < m_nWires; ++j) {
6646 double temp = 0.;
6647 if (m_mode == 0) {
6648 temp = (m_w[i].x - cx) * (m_w[j].x - cx) * TwoPi / (m_sx * m_sy);
6649 }
6650 if (i == j) {
6651 m_sigmat[i][j] =
6652 Ph2Lim(m_w[i].r) - Ph2(2. * (m_w[j].x - cx), 0.) - temp;
6653 } else {
6654 m_sigmat[i][j] =
6655 Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
6656 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y - m_w[j].y) - temp;
6657 }
6658 }
6659 }
6660 return true;
6661}
6662
6663bool ComponentAnalyticField::IprC2Y() {
6664 //-----------------------------------------------------------------------
6665 // IPRC2Y - This initializing subroutine stores the capacitance matrix
6666 // for the configuration:
6667 // wires at zw(j)+cmplx(lx*sx,ly*2*sy),
6668 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
6669 // but the signs of the charges alternate in the y-direction
6670 // (Last changed on 4/10/06.)
6671 //-----------------------------------------------------------------------
6672
6673 // Fill the capacitance matrix.
6674 for (unsigned int i = 0; i < m_nWires; ++i) {
6675 const double cy =
6676 m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
6677 for (unsigned int j = 0; j < m_nWires; ++j) {
6678 double temp = 0.;
6679 if (m_mode == 1) {
6680 temp = (m_w[i].y - cy) * (m_w[j].y - cy) * TwoPi / (m_sx * m_sy);
6681 }
6682 if (i == j) {
6683 m_sigmat[i][j] =
6684 Ph2Lim(m_w[i].r) - Ph2(0., 2. * (m_w[j].y - cy)) - temp;
6685 } else {
6686 m_sigmat[i][j] =
6687 Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
6688 Ph2(m_w[i].x - m_w[j].x, m_w[i].y + m_w[j].y - 2. * cy) - temp;
6689 }
6690 }
6691 }
6692 return true;
6693}
6694
6695bool ComponentAnalyticField::IprC30() {
6696 //-----------------------------------------------------------------------
6697 // IPRC30 - Routine filling the signal matrix for cells of type C30.
6698 // Since the signal matrix equals the capacitance matrix for
6699 // this potential, the routine is identical to SETC30 except
6700 // for the C and P parameters.
6701 // (Last changed on 11/11/97.)
6702 //-----------------------------------------------------------------------
6703
6704 // Fill the capacitance matrix.
6705 for (unsigned int i = 0; i < m_nWires; ++i) {
6706 const double cx =
6707 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
6708 const double cy =
6709 m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
6710 for (unsigned int j = 0; j < m_nWires; ++j) {
6711 if (i == j) {
6712 m_sigmat[i][i] = Ph2Lim(m_w[i].r) -
6713 Ph2(0., 2. * (m_w[i].y - cy)) -
6714 Ph2(2. * (m_w[i].x - cx), 0.) +
6715 Ph2(2. * (m_w[i].x - cx), 2. * (m_w[i].y - cy));
6716 } else {
6717 m_sigmat[i][j] =
6718 Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
6719 Ph2(m_w[i].x - m_w[j].x, m_w[i].y + m_w[j].y - 2. * cy) -
6720 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y - m_w[j].y) +
6721 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y + m_w[j].y - 2. * cy);
6722 }
6723 }
6724 }
6725 return true;
6726}
6727
6728bool ComponentAnalyticField::IprD10() {
6729 //-----------------------------------------------------------------------
6730 // IPRD10 - Signal matrix preparation for D1 cells.
6731 // VARIABLES :
6732 // (Last changed on 2/ 2/93.)
6733 //-----------------------------------------------------------------------
6734
6735 // Loop over all wires.
6736 for (unsigned int i = 0; i < m_nWires; ++i) {
6737 // Set the diagonal terms.
6738 m_sigmat[i][i] = -log(
6739 m_w[i].r /
6740 (m_cotube - (m_w[i].x * m_w[i].x + m_w[i].y * m_w[i].y) / m_cotube));
6741 // Set a complex wire-coordinate to make things a little easier.
6742 std::complex<double> zi(m_w[i].x, m_w[i].y);
6743 // Loop over all other wires for the off-diagonal elements.
6744 for (unsigned int j = i + 1; j < m_nWires; ++j) {
6745 // Set a complex wire-coordinate to make things a little easier.
6746 std::complex<double> zj(m_w[j].x, m_w[j].y);
6747 m_sigmat[i][j] = -log(
6748 abs((1. / m_cotube) * (zi - zj) / (1. - conj(zi) * zj / m_cotube2)));
6749 // Copy this to a[j][i] since the capacitance matrix is symmetric.
6750 m_sigmat[j][i] = m_sigmat[i][j];
6751 }
6752 }
6753 return true;
6754}
6755
6756bool ComponentAnalyticField::IprD30() {
6757 //-----------------------------------------------------------------------
6758 // IPRD30 - Signal matrix preparation for polygonal cells (type D3).
6759 // Variables :
6760 // (Last changed on 19/ 6/97.)
6761 //-----------------------------------------------------------------------
6762
6763 wmap.resize(m_nWires);
6764
6765 std::complex<double> wd;
6766
6767 // Loop over all wire combinations.
6768 for (int i = 0; i < int(m_nWires); ++i) {
6769 // We need to compute the wire mapping again to obtain WD.
6770 ConformalMap(std::complex<double>(m_w[i].x, m_w[i].y) / m_cotube, wmap[i],
6771 wd);
6772 // Diagonal elements.
6773 m_sigmat[i][i] = -log(
6774 abs((m_w[i].r / m_cotube) * wd / (1. - pow(abs(wmap[i]), 2))));
6775 // Loop over all other wires for the off-diagonal elements.
6776 for (int j = 0; j < i - 1; ++j) {
6777 m_sigmat[i][j] =
6778 -log(abs((wmap[i] - wmap[j]) / (1. - conj(wmap[i]) * wmap[j])));
6779 // Copy this to a[j][i] since the capacitance matrix is symmetric.
6780 m_sigmat[j][i] = m_sigmat[i][j];
6781 }
6782 }
6783 return true;
6784}
6785
6786bool ComponentAnalyticField::Wfield(const double xin, const double yin,
6787 const double zpos, double& exsum,
6788 double& eysum, double& ezsum, double& vsum,
6789 const std::string& label,
6790 const bool opt) const {
6791 //-----------------------------------------------------------------------
6792 // SIGFLS - Sums the weighting field components at (XPOS,YPOS,ZPOS).
6793 // (Last changed on 11/10/06.)
6794 //-----------------------------------------------------------------------
6795
6796 // Initialise the sums.
6797 exsum = eysum = ezsum = vsum = 0.;
6798 double ex = 0., ey = 0., ez = 0.;
6799 double volt = 0.;
6800
6801 double xpos = xin, ypos = yin;
6802 if (m_polar) Cartesian2Internal(xin, yin, xpos, ypos);
6803 // Stop here if there are no weighting fields defined.
6804 if (m_readout.empty()) return false;
6805 if (!m_sigset) {
6806 std::cerr << m_className << "::Wfield: No weighting fields available.\n";
6807 return false;
6808 }
6809
6810 if (label.empty()) return volt;
6811 const auto it = std::find(m_readout.cbegin(), m_readout.cend(), label);
6812 if (it == m_readout.end()) return false;
6813 const auto isw = it - m_readout.begin();
6814
6815 // Loop over the signal layers.
6816 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6817 for (int my = m_mymin; my <= m_mymax; ++my) {
6818 // Load the layers of the wire matrices.
6819 // CALL IONIO(MX,MY,2,0,IFAIL)
6820 // if (!LoadWireLayers(mx, my)) {
6821 // std::cerr << m_className << "::LoadWireLayers:\n";
6822 // std::cerr << " Wire matrix store error.\n";
6823 // std::cerr << " No weighting field returned.\n";
6824 // exsum = eysum = ezsum = 0.;
6825 // return false;
6826 //}
6827 // Loop over all wires.
6828 for (int iw = m_nWires; iw--;) {
6829 // Pick out those wires that are part of this read out group.
6830 if (m_w[iw].ind != isw) continue;
6831 ex = ey = ez = 0.;
6832 if (m_cellTypeFourier == A00) {
6833 WfieldWireA00(xpos, ypos, ex, ey, volt, mx, my, iw, opt);
6834 } else if (m_cellTypeFourier == B2X) {
6835 WfieldWireB2X(xpos, ypos, ex, ey, volt, my, iw, opt);
6836 } else if (m_cellTypeFourier == B2Y) {
6837 WfieldWireB2Y(xpos, ypos, ex, ey, volt, mx, iw, opt);
6838 } else if (m_cellTypeFourier == C2X) {
6839 WfieldWireC2X(xpos, ypos, ex, ey, volt, iw, opt);
6840 } else if (m_cellTypeFourier == C2Y) {
6841 WfieldWireC2Y(xpos, ypos, ex, ey, volt, iw, opt);
6842 } else if (m_cellTypeFourier == C30) {
6843 WfieldWireC30(xpos, ypos, ex, ey, volt, iw, opt);
6844 } else if (m_cellTypeFourier == D10) {
6845 WfieldWireD10(xpos, ypos, ex, ey, volt, iw, opt);
6846 } else if (m_cellTypeFourier == D30) {
6847 WfieldWireD30(xpos, ypos, ex, ey, volt, iw, opt);
6848 } else {
6849 std::cerr << m_className << "::Wfield:\n";
6850 std::cerr << " Unknown signal field type " << m_cellTypeFourier
6851 << " received. Program error!\n";
6852 std::cerr << " Encountered for wire " << iw
6853 << ", readout group = " << m_w[iw].ind << "\n";
6854 exsum = eysum = ezsum = vsum = 0.;
6855 return false;
6856 }
6857 exsum += ex;
6858 eysum += ey;
6859 ezsum += ez;
6860 if (opt) vsum += volt;
6861 }
6862 // Load the layers of the plane matrices.
6863 // CALL IPLIO(MX,MY,2,IFAIL)
6864 // if (!LoadPlaneLayers(mx, my)) {
6865 // std::cerr << m_className << "::Wfield:\n";
6866 // std::cerr << " Plane matrix store error.\n";
6867 // std::cerr << " No weighting field returned.\n";
6868 // exsum = eysum = ezsum = 0.;
6869 // return;
6870 //}
6871 // Loop over all planes.
6872 for (int ip = 0; ip < 5; ++ip) {
6873 // Pick out those that are part of this read out group.
6874 if (m_planes[ip].ind != isw) continue;
6875 ex = ey = ez = 0.;
6876 if (m_cellTypeFourier == A00) {
6877 WfieldPlaneA00(xpos, ypos, ex, ey, volt, mx, my, ip, opt);
6878 } else if (m_cellTypeFourier == B2X) {
6879 WfieldPlaneB2X(xpos, ypos, ex, ey, volt, my, ip, opt);
6880 } else if (m_cellTypeFourier == B2Y) {
6881 WfieldPlaneB2Y(xpos, ypos, ex, ey, volt, mx, ip, opt);
6882 } else if (m_cellTypeFourier == C2X) {
6883 WfieldPlaneC2X(xpos, ypos, ex, ey, volt, ip, opt);
6884 } else if (m_cellTypeFourier == C2Y) {
6885 WfieldPlaneC2Y(xpos, ypos, ex, ey, volt, ip, opt);
6886 } else if (m_cellTypeFourier == D10) {
6887 WfieldPlaneD10(xpos, ypos, ex, ey, volt, ip, opt);
6888 } else if (m_cellTypeFourier == D30) {
6889 WfieldPlaneD30(xpos, ypos, ex, ey, volt, ip, opt);
6890 } else {
6891 std::cerr << m_className << "::Wfield:\n";
6892 std::cerr << " Unkown field type " << m_cellTypeFourier
6893 << " received. Program error!\n";
6894 std::cerr << " Encountered for plane " << ip
6895 << ", readout group = " << m_planes[ip].ind << "\n";
6896 exsum = eysum = ezsum = 0.;
6897 return false;
6898 }
6899 exsum += ex;
6900 eysum += ey;
6901 ezsum += ez;
6902 if (opt) vsum += volt;
6903 }
6904 // Next signal layer.
6905 }
6906 }
6907 // Add the field due to the planes themselves.
6908 for (int ip = 0; ip < 5; ++ip) {
6909 if (m_planes[ip].ind != isw) continue;
6910 exsum += m_planes[ip].ewxcor;
6911 eysum += m_planes[ip].ewycor;
6912 if (!opt) continue;
6913 if (ip == 0 || ip == 1) {
6914 double xx = xpos;
6915 if (m_perx) {
6916 xx -= m_sx * int(round(xpos / m_sx));
6917 if (m_ynplan[0] && xx <= m_coplan[0]) xx += m_sx;
6918 if (m_ynplan[1] && xx >= m_coplan[1]) xx -= m_sx;
6919 }
6920 vsum += 1. - m_planes[ip].ewxcor * (xx - m_coplan[ip]);
6921 } else if (ip == 2 || ip == 3) {
6922 double yy = ypos;
6923 if (m_pery) {
6924 yy -= m_sy * int(round(ypos / m_sy));
6925 if (m_ynplan[2] && yy <= m_coplan[2]) yy += m_sy;
6926 if (m_ynplan[3] && yy >= m_coplan[3]) yy -= m_sy;
6927 }
6928 vsum += 1. - m_planes[ip].ewycor * (yy - m_coplan[ip]);
6929 }
6930 }
6931
6932 // Add strips and pixels, if there are any.
6933 for (unsigned int ip = 0; ip < 5; ++ip) {
6934 for (const auto& strip : m_planes[ip].strips1) {
6935 if (strip.ind != isw) continue;
6936 WfieldStripXy(xpos, ypos, zpos, ex, ey, ez, volt, ip, strip, opt);
6937 exsum += ex;
6938 eysum += ey;
6939 ezsum += ez;
6940 if (opt) vsum += volt;
6941 }
6942 for (const auto& strip : m_planes[ip].strips2) {
6943 if (strip.ind != isw) continue;
6944 WfieldStripZ(xpos, ypos, ex, ey, volt, ip, strip, opt);
6945 exsum += ex;
6946 eysum += ey;
6947 if (opt) vsum += volt;
6948 }
6949 for (const auto& pixel : m_planes[ip].pixels) {
6950 if (pixel.ind != isw) continue;
6951 WfieldPixel(xpos, ypos, zpos, ex, ey, ez, volt, ip, pixel, opt);
6952 exsum += ex;
6953 eysum += ey;
6954 ezsum += ez;
6955 if (opt) vsum += volt;
6956 }
6957 }
6958 if (m_polar) {
6959 const double r = exp(xpos);
6960 const double er = exsum / r;
6961 const double ep = eysum / r;
6962 const double theta = atan2(yin, xin);
6963 const double ct = cos(theta);
6964 const double st = sin(theta);
6965 exsum = +ct * er - st * ep;
6966 eysum = +st * er + ct * ep;
6967 }
6968 return true;
6969}
6970
6971void ComponentAnalyticField::WfieldWireA00(const double xpos, const double ypos,
6972 double& ex, double& ey, double& volt,
6973 const int mx, const int my,
6974 const int isw,
6975 const bool opt) const {
6976 //-----------------------------------------------------------------------
6977 // IONA00 - Routine returning the A I,J [MX,MY] * E terms for A cells.
6978 // VARIABLES : R2 : Potential before taking -Log(Sqrt(...))
6979 // EX,EY : x,y-Component of the electric field.
6980 // ETOT : Magnitude of the electric field.
6981 // VOLT : Potential.
6982 // EXHELP ETC : One term in the summing series.
6983 // (XPOS,YPOS): Position where the field is needed.
6984 // (Last changed on 14/ 8/98.)
6985 //-----------------------------------------------------------------------
6986
6987 // Initialise the electric field and potential.
6988 ex = ey = volt = 0.;
6989
6990 double xxmirr = 0., yymirr = 0.;
6991 // Loop over all wires.
6992 for (int i = m_nWires; i--;) {
6993 // Define a few reduced variables.
6994 const double xx = xpos - m_w[i].x - mx * m_sx;
6995 const double yy = ypos - m_w[i].y - my * m_sy;
6996 // Calculate the field in case there are no planes.
6997 double r2 = xx * xx + yy * yy;
6998 if (r2 <= 0.) continue;
6999 double exhelp = xx / r2;
7000 double eyhelp = yy / r2;
7001 // Take care of a plane at constant x.
7002 if (m_ynplax) {
7003 xxmirr = xpos + m_w[i].x - 2. * m_coplax;
7004 const double r2plan = xxmirr * xxmirr + yy * yy;
7005 if (r2plan <= 0.) continue;
7006 exhelp -= xxmirr / r2plan;
7007 eyhelp -= yy / r2plan;
7008 r2 /= r2plan;
7009 }
7010 // Take care of a plane at constant y.
7011 if (m_ynplay) {
7012 yymirr = ypos + m_w[i].y - 2. * m_coplay;
7013 const double r2plan = xx * xx + yymirr * yymirr;
7014 if (r2plan <= 0.) continue;
7015 exhelp -= xx / r2plan;
7016 eyhelp -= yymirr / r2plan;
7017 r2 /= r2plan;
7018 }
7019 // Take care of pairs of planes.
7020 if (m_ynplax && m_ynplay) {
7021 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
7022 if (r2plan <= 0.) continue;
7023 exhelp += xxmirr / r2plan;
7024 eyhelp += yymirr / r2plan;
7025 r2 *= r2plan;
7026 }
7027 // Calculate the electric field and the potential.
7028 if (opt) volt -= 0.5 * real(m_sigmat[isw][i]) * log(r2);
7029 ex += real(m_sigmat[isw][i]) * exhelp;
7030 ey += real(m_sigmat[isw][i]) * eyhelp;
7031 }
7032}
7033
7034void ComponentAnalyticField::WfieldWireB2X(const double xpos, const double ypos,
7035 double& ex, double& ey, double& volt,
7036 const int my, const int isw,
7037 const bool opt) const {
7038 //-----------------------------------------------------------------------
7039 // IONB2X - Routine calculating the MY contribution to the signal on
7040 // wire ISW due to a charge at (XPOS,YPOS) for F-B2Y cells.
7041 // VARIABLES : See routine EFCA00 for most of the variables.
7042 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
7043 // ECOMPL : EX + I*EY ; I**2=-1
7044 // (Last changed on 20/ 2/90.)
7045 //-----------------------------------------------------------------------
7046
7047 // Initialise the electric field and potential.
7048 ex = ey = volt = 0.;
7049
7050 const double tx = HalfPi / m_sx;
7051 // Loop over all wires.
7052 for (unsigned int i = 0; i < m_nWires; ++i) {
7053 const double xx = tx * (xpos - m_w[i].x);
7054 const double yy = tx * (ypos - m_w[i].y - my * m_sy);
7055 const double xxneg = tx * (xpos + m_w[i].x - 2 * m_coplan[0]);
7056 const std::complex<double> zz(xx, yy);
7057 const std::complex<double> zzneg(xxneg, yy);
7058 // Calculate the field in case there are no equipotential planes.
7059 std::complex<double> ecompl(0., 0.);
7060 double r2 = 1.;
7061 if (fabs(yy) <= 20.) {
7062 ecompl = -m_b2sin[i] / (sin(zz) * sin(zzneg));
7063 if (opt) {
7064 const double sinhy = sinh(yy);
7065 const double sinxx = sin(xx);
7066 const double sinxxneg = sin(xxneg);
7067 r2 = (sinhy * sinhy + sinxx * sinxx) /
7068 (sinhy * sinhy + sinxxneg * sinxxneg);
7069 }
7070 }
7071 // Take care of a plane at constant y.
7072 if (m_ynplay) {
7073 const double yymirr = tx * (ypos + m_w[i].y - 2. * m_coplay);
7074 const std::complex<double> zzmirr(xx, yymirr);
7075 const std::complex<double> zznmirr(xxneg, yymirr);
7076 if (fabs(yymirr) <= 20.) {
7077 ecompl += m_b2sin[i] / (sin(zzmirr) * sin(zznmirr));
7078 if (opt) {
7079 const double sinhy = sinh(yymirr);
7080 const double sinxx = sin(xx);
7081 const double sinxxneg = sin(xxneg);
7082 const double r2plan = (sinhy * sinhy + sinxx * sinxx) /
7083 (sinhy * sinhy + sinxxneg * sinxxneg);
7084 r2 /= r2plan;
7085 }
7086 }
7087 }
7088 // Calculate the electric field and potential.
7089 ex += real(m_sigmat[isw][i]) * real(ecompl);
7090 ey -= real(m_sigmat[isw][i]) * imag(ecompl);
7091 if (opt) volt -= 0.5 * real(m_sigmat[isw][i]) * log(r2);
7092 }
7093 ex *= tx;
7094 ey *= tx;
7095}
7096
7097void ComponentAnalyticField::WfieldWireB2Y(const double xpos, const double ypos,
7098 double& ex, double& ey, double& volt,
7099 const int mx, const int isw,
7100 const bool opt) const {
7101 //-----------------------------------------------------------------------
7102 // IONB2Y - Routine calculating the MX contribution to the signal on
7103 // wire ISW due to a charge at (XPOS,YPOS) for F-B2X cells.
7104 // VARIABLES : See routine EFCA00 for most of the variables.
7105 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
7106 // ECOMPL : EX + I*EY ; I**2=-1
7107 // (Last changed on 20/ 2/90.)
7108 //-----------------------------------------------------------------------
7109
7110 constexpr std::complex<double> icons(0., 1.);
7111
7112 // Initialise the electric field and potential.
7113 ex = ey = volt = 0.;
7114
7115 const double ty = HalfPi / m_sy;
7116 // Loop over all wires.
7117 for (unsigned int i = 0; i < m_nWires; ++i) {
7118 const double xx = ty * (xpos - m_w[i].x - mx * m_sx);
7119 const double yy = ty * (ypos - m_w[i].y);
7120 const double yyneg = ty * (ypos + m_w[i].y - 2. * m_coplan[2]);
7121 const std::complex<double> zz(xx, yy);
7122 const std::complex<double> zzneg(xx, yyneg);
7123 // Calculate the field in case there are no equipotential planes.
7124 std::complex<double> ecompl(0., 0.);
7125 double r2 = 1.;
7126 if (fabs(xx) <= 20.) {
7127 ecompl = icons * m_b2sin[i] / (sin(icons * zz) * sin(icons * zzneg));
7128 if (opt) {
7129 const double sinhx = sinh(xx);
7130 const double sinyy = sin(yy);
7131 const double sinyyneg = sin(yyneg);
7132 r2 = (sinhx * sinhx + sinyy * sinyy) /
7133 (sinhx * sinhx + sinyyneg * sinyyneg);
7134 }
7135 }
7136 // Take care of a plane at constant x.
7137 if (m_ynplax) {
7138 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
7139 const std::complex<double> zzmirr(xxmirr, yy);
7140 const std::complex<double> zznmirr(xxmirr, yyneg);
7141 if (fabs(xxmirr) <= 20.) {
7142 ecompl -=
7143 icons * m_b2sin[i] / (sin(icons * zzmirr) * sin(icons * zznmirr));
7144 if (opt) {
7145 const double sinhx = sinh(xxmirr);
7146 const double sinyy = sin(yy);
7147 const double sinyyneg = sin(yyneg);
7148 const double r2plan = (sinhx * sinhx + sinyy * sinyy) /
7149 (sinhx * sinhx + sinyyneg * sinyyneg);
7150 r2 /= r2plan;
7151 }
7152 }
7153 }
7154 // Calculate the electric field and potential.
7155 ex += real(m_sigmat[isw][i]) * real(ecompl);
7156 ey -= real(m_sigmat[isw][i]) * imag(ecompl);
7157 if (opt) volt -= 0.5 * real(m_sigmat[isw][i]) * log(r2);
7158 }
7159 ex *= ty;
7160 ey *= ty;
7161}
7162
7163void ComponentAnalyticField::WfieldWireC2X(const double xpos, const double ypos,
7164 double& ex, double& ey, double& volt,
7165 const int isw,
7166 const bool opt) const {
7167 //-----------------------------------------------------------------------
7168 // IONC2X - Routine returning the potential and electric field in a
7169 // configuration with 2 x planes and y periodicity.
7170 // VARIABLES : see the writeup
7171 // (Last changed on 12/10/06.)
7172 //-----------------------------------------------------------------------
7173
7174 constexpr std::complex<double> icons(0., 1.);
7175
7176 // Initial values.
7177 std::complex<double> wsum1 = 0.;
7178 std::complex<double> wsum2 = 0.;
7179 double s = 0.;
7180 volt = 0.;
7181
7182 // Wire loop.
7183 for (unsigned int i = 0; i < m_nWires; ++i) {
7184 // Compute the direct contribution.
7185 auto zeta =
7186 m_zmult * std::complex<double>(xpos - m_w[i].x, ypos - m_w[i].y);
7187 if (imag(zeta) > 15.) {
7188 wsum1 -= real(m_sigmat[isw][i]) * icons;
7189 if (opt) volt -= real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7190 } else if (imag(zeta) < -15.) {
7191 wsum1 += real(m_sigmat[isw][i]) * icons;
7192 if (opt) volt -= real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7193 } else {
7194 const auto zterm = Th1(zeta, m_p1, m_p2);
7195 wsum1 += real(m_sigmat[isw][i]) * (zterm.second / zterm.first);
7196 if (opt) volt -= real(m_sigmat[isw][i]) * log(abs(zterm.first));
7197 }
7198 // Find the plane nearest to the wire.
7199 const double cx =
7200 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
7201 // Constant terms sum
7202 s += real(m_sigmat[isw][i]) * (m_w[i].x - cx);
7203 // Mirror contribution.
7204 zeta = m_zmult *
7205 std::complex<double>(2. * cx - xpos - m_w[i].x, ypos - m_w[i].y);
7206 if (imag(zeta) > +15.) {
7207 wsum2 -= real(m_sigmat[isw][i]) * icons;
7208 if (opt) volt += real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7209 } else if (imag(zeta) < -15.) {
7210 wsum2 += real(m_sigmat[isw][i]) * icons;
7211 if (opt) volt += real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7212 } else {
7213 const auto zterm = Th1(zeta, m_p1, m_p2);
7214 wsum2 += real(m_sigmat[isw][i]) * (zterm.second / zterm.first);
7215 if (opt) volt += real(m_sigmat[isw][i]) * log(abs(zterm.first));
7216 }
7217 // Correct the voltage, if needed (MODE).
7218 if (opt && m_mode == 0) {
7219 volt -= TwoPi * real(m_sigmat[isw][i]) * (xpos - cx) * (m_w[i].x - cx) /
7220 (m_sx * m_sy);
7221 }
7222 }
7223 // Convert the two contributions to a real field.
7224 ex = real(m_zmult * (wsum1 + wsum2));
7225 ey = -imag(m_zmult * (wsum1 - wsum2));
7226 // Constant correction terms.
7227 if (m_mode == 0) ex += s * TwoPi / (m_sx * m_sy);
7228}
7229
7230void ComponentAnalyticField::WfieldWireC2Y(const double xpos, const double ypos,
7231 double& ex, double& ey, double& volt,
7232 const int isw,
7233 const bool opt) const {
7234 //-----------------------------------------------------------------------
7235 // IONC2Y - Routine returning the potential and electric field in a
7236 // configuration with 2 y planes and x periodicity.
7237 // VARIABLES : see the writeup
7238 // (Last changed on 12/10/06.)
7239 //-----------------------------------------------------------------------
7240
7241 constexpr std::complex<double> icons(0., 1.);
7242
7243 // Initial values.
7244 std::complex<double> wsum1 = 0.;
7245 std::complex<double> wsum2 = 0.;
7246 double s = 0.;
7247 volt = 0.;
7248
7249 // Wire loop.
7250 for (unsigned int i = 0; i < m_nWires; ++i) {
7251 // Compute the direct contribution.
7252 auto zeta =
7253 m_zmult * std::complex<double>(xpos - m_w[i].x, ypos - m_w[i].y);
7254 if (imag(zeta) > +15.) {
7255 wsum1 -= real(m_sigmat[isw][i]) * icons;
7256 if (opt) volt -= real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7257 } else if (imag(zeta) < -15.) {
7258 wsum1 += real(m_sigmat[isw][i]) * icons;
7259 if (opt) volt -= real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7260 } else {
7261 const auto zterm = Th1(zeta, m_p1, m_p2);
7262 wsum1 += real(m_sigmat[isw][i]) * (zterm.second / zterm.first);
7263 if (opt) volt -= real(m_sigmat[isw][i]) * log(abs(zterm.first));
7264 }
7265 // Find the plane nearest to the wire.
7266 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
7267 // Constant terms sum
7268 s += real(m_sigmat[isw][i]) * (m_w[i].y - cy);
7269 // Mirror contribution.
7270 zeta = m_zmult *
7271 std::complex<double>(xpos - m_w[i].x, 2. * cy - ypos - m_w[i].y);
7272 if (imag(zeta) > +15.) {
7273 wsum2 -= real(m_sigmat[isw][i]) * icons;
7274 if (opt) volt += real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7275 } else if (imag(zeta) < -15.) {
7276 wsum2 += real(m_sigmat[isw][i]) * icons;
7277 if (opt) volt += real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7278 } else {
7279 const auto zterm = Th1(zeta, m_p1, m_p2);
7280 wsum2 += real(m_sigmat[isw][i]) * (zterm.second / zterm.first);
7281 if (opt) volt += real(m_sigmat[isw][i]) * log(abs(zterm.first));
7282 }
7283 // Correct the voltage, if needed (MODE).
7284 if (opt && m_mode == 1) {
7285 volt -= TwoPi * real(m_sigmat[isw][i]) * (ypos - cy) * (m_w[i].y - cy) /
7286 (m_sx * m_sy);
7287 }
7288 }
7289 // Convert the two contributions to a real field.
7290 ex = real(m_zmult * (wsum1 - wsum2));
7291 ey = -imag(m_zmult * (wsum1 + wsum2));
7292 // Constant correction terms.
7293 if (m_mode == 1) ey += s * TwoPi / (m_sx * m_sy);
7294}
7295
7296void ComponentAnalyticField::WfieldWireC30(const double xpos, const double ypos,
7297 double& ex, double& ey, double& volt,
7298 const int isw,
7299 const bool opt) const {
7300 //-----------------------------------------------------------------------
7301 // IONC30 - Routine returning the weighting field field in a
7302 // configuration with 2 y and 2 x planes. This routine is
7303 // basically the same as EFCC30.
7304 // (Last changed on 11/11/97.)
7305 //-----------------------------------------------------------------------
7306
7307 constexpr std::complex<double> icons(0., 1.);
7308
7309 // Initial values.
7310 std::complex<double> wsum1 = 0.;
7311 std::complex<double> wsum2 = 0.;
7312 std::complex<double> wsum3 = 0.;
7313 std::complex<double> wsum4 = 0.;
7314 volt = 0.;
7315
7316 // Wire loop.
7317 for (unsigned int i = 0; i < m_nWires; ++i) {
7318 // Compute the direct contribution.
7319 auto zeta =
7320 m_zmult * std::complex<double>(xpos - m_w[i].x, ypos - m_w[i].y);
7321 if (imag(zeta) > +15.) {
7322 wsum1 -= real(m_sigmat[isw][i]) * icons;
7323 if (opt) volt -= real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7324 } else if (imag(zeta) < -15.) {
7325 wsum1 += real(m_sigmat[isw][i]) * icons;
7326 if (opt) volt -= real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7327 } else {
7328 const auto zterm = Th1(zeta, m_p1, m_p2);
7329 wsum1 += real(m_sigmat[isw][i]) * (zterm.second / zterm.first);
7330 if (opt) volt -= real(m_sigmat[isw][i]) * log(abs(zterm.first));
7331 }
7332 // Find the plane nearest to the wire.
7333 const double cx =
7334 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
7335 // Mirror contribution from the x plane.
7336 zeta = m_zmult *
7337 std::complex<double>(2. * cx - xpos - m_w[i].x, ypos - m_w[i].y);
7338 if (imag(zeta) > +15.) {
7339 wsum2 -= real(m_sigmat[isw][i]) * icons;
7340 if (opt) volt += real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7341 } else if (imag(zeta) < -15.) {
7342 wsum2 += real(m_sigmat[isw][i]) * icons;
7343 if (opt) volt += real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7344 } else {
7345 const auto zterm = Th1(zeta, m_p1, m_p2);
7346 wsum2 += real(m_sigmat[isw][i]) * (zterm.second / zterm.first);
7347 if (opt) volt += real(m_sigmat[isw][i]) * log(abs(zterm.first));
7348 }
7349 // Find the plane nearest to the wire.
7350 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
7351 // Mirror contribution from the y plane.
7352 zeta = m_zmult *
7353 std::complex<double>(xpos - m_w[i].x, 2. * cy - ypos - m_w[i].y);
7354 if (imag(zeta) > +15.) {
7355 wsum3 -= real(m_sigmat[isw][i]) * icons;
7356 if (opt) volt += real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7357 } else if (imag(zeta) < -15.) {
7358 wsum3 += real(m_sigmat[isw][i]) * icons;
7359 if (opt) volt += real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7360 } else {
7361 const auto zterm = Th1(zeta, m_p1, m_p2);
7362 wsum3 += real(m_sigmat[isw][i]) * (zterm.second / zterm.first);
7363 if (opt) volt += real(m_sigmat[isw][i]) * log(abs(zterm.first));
7364 }
7365 // Mirror contribution from both the x and the y plane.
7366 zeta = m_zmult * std::complex<double>(2. * cx - xpos - m_w[i].x,
7367 2. * cy - ypos - m_w[i].y);
7368 if (imag(zeta) > +15.) {
7369 wsum4 -= real(m_sigmat[isw][i]) * icons;
7370 if (opt) volt -= real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7371 } else if (imag(zeta) < -15.) {
7372 wsum4 += real(m_sigmat[isw][i]) * icons;
7373 if (opt) volt -= real(m_sigmat[isw][i]) * (fabs(imag(zeta)) - CLog2);
7374 } else {
7375 const auto zterm = Th1(zeta, m_p1, m_p2);
7376 wsum4 += real(m_sigmat[isw][i]) * (zterm.second / zterm.first);
7377 if (opt) volt -= real(m_sigmat[isw][i]) * log(abs(zterm.first));
7378 }
7379 }
7380 // Convert the two contributions to a real field.
7381 ex = real(m_zmult * (wsum1 + wsum2 - wsum3 - wsum4));
7382 ey = -imag(m_zmult * (wsum1 - wsum2 + wsum3 - wsum4));
7383}
7384
7385void ComponentAnalyticField::WfieldWireD10(const double xpos, const double ypos,
7386 double& ex, double& ey, double& volt,
7387 const int isw,
7388 const bool opt) const {
7389 //-----------------------------------------------------------------------
7390 // IOND10 - Subroutine computing the signal on wire ISW due to a charge
7391 // at (XPOS,YPOS). This is effectively routine EFCD10.
7392 // VARIABLES : EX, EY, VOLT:Electric field and potential.
7393 // ETOT, VOLT : Magnitude of electric field, potential.
7394 // (XPOS,YPOS): The position where the field is calculated.
7395 // ZI, ZPOS : Shorthand complex notations.
7396 // (Last changed on 2/ 2/93.)
7397 //-----------------------------------------------------------------------
7398
7399 // Initialise the electric field and potential.
7400 ex = ey = volt = 0.;
7401
7402 // Set the complex position coordinates.
7403 std::complex<double> zpos = std::complex<double>(xpos, ypos);
7404 std::complex<double> zi;
7405 std::complex<double> wi;
7406 // Loop over all wires.
7407 for (int i = m_nWires; i--;) {
7408 // Set the complex version of the wire-coordinate for simplicity.
7409 zi = std::complex<double>(m_w[i].x, m_w[i].y);
7410 // Compute the contribution to the potential, if needed.
7411 if (opt) {
7412 volt -= real(m_sigmat[isw][i]) *
7413 log(abs(m_cotube * (zpos - zi) / (m_cotube2 - zpos * conj(zi))));
7414 }
7415 // Compute the contribution to the electric field.
7416 wi = 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
7417 ex += real(m_sigmat[isw][i]) * real(wi);
7418 ey += real(m_sigmat[isw][i]) * imag(wi);
7419 }
7420}
7421
7422void ComponentAnalyticField::WfieldWireD30(const double xpos, const double ypos,
7423 double& ex, double& ey, double& volt,
7424 const int isw,
7425 const bool opt) const {
7426 //-----------------------------------------------------------------------
7427 // IOND30 - Subroutine computing the weighting field for a polygonal
7428 // cells without periodicities, type D3.
7429 // VARIABLES : EX, EY :Electric field
7430 // (XPOS,YPOS): The position where the field is calculated.
7431 // ZI, ZPOS : Shorthand complex notations.
7432 // (Last changed on 19/ 6/97.)
7433 //-----------------------------------------------------------------------
7434
7435 // Initialise the electric field and potential.
7436 ex = ey = volt = 0.;
7437
7438 std::complex<double> whelp;
7439
7440 // Get the mapping of the position.
7441 std::complex<double> wpos, wdpos;
7442 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
7443 // Loop over all wires.
7444 for (int i = m_nWires; i--;) {
7445 // Compute the contribution to the potential, if needed.
7446 if (opt) {
7447 volt -= real(m_sigmat[isw][i]) *
7448 log(abs((wpos - wmap[i]) / (1. - wpos * conj(wmap[i]))));
7449 }
7450 // Compute the contribution to the electric field.
7451 whelp = wdpos * (1. - pow(abs(wmap[i]), 2)) /
7452 ((wpos - wmap[i]) * (1. - conj(wmap[i]) * wpos));
7453 ex += real(m_sigmat[isw][i]) * real(whelp);
7454 ey -= real(m_sigmat[isw][i]) * imag(whelp);
7455 }
7456 ex /= m_cotube;
7457 ey /= m_cotube;
7458}
7459
7460void ComponentAnalyticField::WfieldPlaneA00(
7461 const double xpos, const double ypos, double& ex, double& ey, double& volt,
7462 const int mx, const int my, const int iplane, const bool opt) const {
7463 //-----------------------------------------------------------------------
7464 // IPLA00 - Routine returning the A I,J [MX,MY] * E terms for A cells.
7465 // VARIABLES : R2 : Potential before taking -Log(Sqrt(...))
7466 // EX,EY : x,y-Component of the electric field.
7467 // EXHELP ETC : One term in the summing series.
7468 // (XPOS,YPOS): Position where the field is needed.
7469 // (Last changed on 9/11/98.)
7470 //-----------------------------------------------------------------------
7471
7472 // Initialise the electric field and potential.
7473 ex = ey = volt = 0.;
7474
7475 double xxmirr = 0., yymirr = 0.;
7476 // Loop over all wires.
7477 for (int i = m_nWires; i--;) {
7478 // Define a few reduced variables.
7479 const double xx = xpos - m_w[i].x - mx * m_sx;
7480 const double yy = ypos - m_w[i].y - my * m_sy;
7481 // Calculate the field in case there are no planes.
7482 double r2 = xx * xx + yy * yy;
7483 if (r2 <= 0.) continue;
7484 double exhelp = xx / r2;
7485 double eyhelp = yy / r2;
7486 // Take care of a planes at constant x.
7487 if (m_ynplax) {
7488 xxmirr = xpos + m_w[i].x - 2 * m_coplax;
7489 const double r2plan = xxmirr * xxmirr + yy * yy;
7490 if (r2plan <= 0.) continue;
7491 exhelp -= xxmirr / r2plan;
7492 eyhelp -= yy / r2plan;
7493 r2 /= r2plan;
7494 }
7495 // Take care of a plane at constant y.
7496 if (m_ynplay) {
7497 yymirr = ypos + m_w[i].y - 2 * m_coplay;
7498 const double r2plan = xx * xx + yymirr * yymirr;
7499 if (r2plan <= 0.) continue;
7500 exhelp -= xx / r2plan;
7501 eyhelp -= yymirr / r2plan;
7502 r2 /= r2plan;
7503 }
7504 // Take care of pairs of planes.
7505 if (m_ynplax && m_ynplay) {
7506 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
7507 if (r2plan <= 0.) continue;
7508 exhelp += xxmirr / r2plan;
7509 eyhelp += yymirr / r2plan;
7510 r2 *= r2plan;
7511 }
7512 // Calculate the electric field and potential.
7513 if (opt) volt -= 0.5 * m_qplane[iplane][i] * log(r2);
7514 ex += m_qplane[iplane][i] * exhelp;
7515 ey += m_qplane[iplane][i] * eyhelp;
7516 }
7517}
7518
7519void ComponentAnalyticField::WfieldPlaneB2X(const double xpos,
7520 const double ypos, double& ex,
7521 double& ey, double& volt,
7522 const int my, const int iplane,
7523 const bool opt) const {
7524 //-----------------------------------------------------------------------
7525 // IPLB2X - Routine calculating the MY contribution to the signal on
7526 // wire IPLANE due to a charge at (XPOS,YPOS) for F-B2Y cells.
7527 // VARIABLES : See routine EFCA00 for most of the variables.
7528 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
7529 // ECOMPL : EX + I*EY ; I**2=-1
7530 // (Last changed on 12/11/98.)
7531 //-----------------------------------------------------------------------
7532
7533 // Initialise ex, ey and volt.
7534 ex = ey = volt = 0.;
7535
7536 const double tx = HalfPi / m_sx;
7537 // Loop over all wires.
7538 for (unsigned int i = 0; i < m_nWires; ++i) {
7539 const double xx = tx * (xpos - m_w[i].x);
7540 const double yy = tx * (ypos - m_w[i].y - my * m_sy);
7541 const double xxneg = tx * (xpos + m_w[i].x - 2 * m_coplan[0]);
7542 const std::complex<double> zz(xx, yy);
7543 const std::complex<double> zzneg(xxneg, yy);
7544 // Calculate the field in case there are no equipotential planes.
7545 std::complex<double> ecompl(0., 0.);
7546 double r2 = 1.;
7547 if (fabs(yy) <= 20.) {
7548 ecompl = -m_b2sin[i] / (sin(zz) * sin(zzneg));
7549 if (opt) {
7550 const double sinhy = sinh(yy);
7551 const double sinxx = sin(xx);
7552 const double sinxxneg = sin(xxneg);
7553 r2 = (sinhy * sinhy + sinxx * sinxx) /
7554 (sinhy * sinhy + sinxxneg * sinxxneg);
7555 }
7556 }
7557 // Take care of a plane at constant y.
7558 if (m_ynplay) {
7559 const double yymirr = tx * (ypos + m_w[i].y - 2 * m_coplay);
7560 const std::complex<double> zzmirr(yy, yymirr);
7561 const std::complex<double> zznmirr(xxneg, yymirr);
7562 if (fabs(yymirr) <= 20.) {
7563 ecompl += m_b2sin[i] / (sin(zzmirr) * sin(zznmirr));
7564 if (opt) {
7565 const double sinhy = sinh(yymirr);
7566 const double sinxx = sin(xx);
7567 const double sinxxneg = sin(xxneg);
7568 const double r2plan = (sinhy * sinhy + sinxx * sinxx) /
7569 (sinhy * sinhy + sinxxneg * sinxxneg);
7570 r2 /= r2plan;
7571 }
7572 }
7573 }
7574 // Calculate the electric field.
7575 ex += m_qplane[iplane][i] * real(ecompl);
7576 ey -= m_qplane[iplane][i] * imag(ecompl);
7577 if (opt) volt -= 0.5 * m_qplane[iplane][i] * log(r2);
7578 }
7579 ex *= tx;
7580 ey *= tx;
7581}
7582
7583void ComponentAnalyticField::WfieldPlaneB2Y(const double xpos,
7584 const double ypos, double& ex,
7585 double& ey, double& volt,
7586 const int mx, const int iplane,
7587 const bool opt) const {
7588 //-----------------------------------------------------------------------
7589 // IPLB2Y - Routine calculating the MX contribution to the signal on
7590 // wire IPLANE due to a charge at (XPOS,YPOS) for F-B2X cells.
7591 // VARIABLES : See routine EFCA00 for most of the variables.
7592 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
7593 // ECOMPL : EX + I*EY ; I**2=-1
7594 // (Last changed on 12/11/98.)
7595 //-----------------------------------------------------------------------
7596
7597 constexpr std::complex<double> icons(0., 1.);
7598
7599 // Initialise ex, ey and volt.
7600 ex = ey = volt = 0.;
7601
7602 const double ty = HalfPi / m_sy;
7603 // Loop over all wires.
7604 for (unsigned int i = 0; i < m_nWires; ++i) {
7605 const double xx = ty * (xpos - m_w[i].x - mx * m_sx);
7606 const double yy = ty * (ypos - m_w[i].y);
7607 const double yyneg = ty * (ypos + m_w[i].y - 2 * m_coplan[2]);
7608 const std::complex<double> zz(xx, yy);
7609 const std::complex<double> zzneg(xx, yyneg);
7610 // Calculate the field in case there are no equipotential planes.
7611 std::complex<double> ecompl(0., 0.);
7612 double r2 = 1.;
7613 if (fabs(xx) <= 20.) {
7614 ecompl = icons * m_b2sin[i] / (sin(icons * zz) * sin(icons * zzneg));
7615 if (opt) {
7616 const double sinhx = sinh(xx);
7617 const double sinyy = sin(yy);
7618 const double sinyyneg = sin(yyneg);
7619 r2 = (sinhx * sinhx + sinyy * sinyy) /
7620 (sinhx * sinhx + sinyyneg * sinyyneg);
7621 }
7622 }
7623 // Take care of a plane at constant y.
7624 if (m_ynplax) {
7625 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
7626 const std::complex<double> zzmirr(xxmirr, yy);
7627 const std::complex<double> zznmirr(xxmirr, yyneg);
7628 if (fabs(xxmirr) <= 20.) {
7629 ecompl -= m_b2sin[i] / (sin(icons * zzmirr) * sin(icons * zznmirr));
7630 if (opt) {
7631 const double sinhx = sinh(xxmirr);
7632 const double sinyy = sin(yy);
7633 const double sinyyneg = sin(yyneg);
7634 const double r2plan = (sinhx * sinhx + sinyy * sinyy) /
7635 (sinhx * sinhx + sinyyneg * sinyyneg);
7636 r2 /= r2plan;
7637 }
7638 }
7639 }
7640 // Calculate the electric field and potential.
7641 ex += m_qplane[iplane][i] * real(ecompl);
7642 ey -= m_qplane[iplane][i] * imag(ecompl);
7643 if (opt) volt -= 0.5 * m_qplane[iplane][i] * log(r2);
7644 }
7645 ex *= ty;
7646 ey *= ty;
7647}
7648
7649void ComponentAnalyticField::WfieldPlaneC2X(const double xpos,
7650 const double ypos, double& ex,
7651 double& ey, double& volt,
7652 const int iplane,
7653 const bool opt) const {
7654 //-----------------------------------------------------------------------
7655 // IPLC2X - Routine returning the potential and electric field in a
7656 // configuration with 2 x planes and y periodicity.
7657 // VARIABLES : see the writeup
7658 // (Last changed on 12/10/06.)
7659 //-----------------------------------------------------------------------
7660
7661 constexpr std::complex<double> icons(0., 1.);
7662 // Initial values.
7663 std::complex<double> wsum1 = 0.;
7664 std::complex<double> wsum2 = 0.;
7665 double s = 0.;
7666 volt = 0.;
7667
7668 // Wire loop.
7669 for (unsigned int i = 0; i < m_nWires; ++i) {
7670 // Compute the direct contribution.
7671 auto zeta =
7672 m_zmult * std::complex<double>(xpos - m_w[i].x, ypos - m_w[i].y);
7673 if (imag(zeta) > +15.) {
7674 wsum1 -= m_qplane[iplane][i] * icons;
7675 if (opt) volt -= m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7676 } else if (imag(zeta) < -15.) {
7677 wsum1 += m_qplane[iplane][i] * icons;
7678 if (opt) volt -= m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7679 } else {
7680 const auto zterm = Th1(zeta, m_p1, m_p2);
7681 wsum1 += m_qplane[iplane][i] * (zterm.second / zterm.first);
7682 if (opt) volt -= m_qplane[iplane][i] * log(abs(zterm.first));
7683 }
7684 // Find the plane nearest to the wire.
7685 const double cx =
7686 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
7687 // Constant terms sum
7688 s += m_qplane[iplane][i] * (m_w[i].x - cx);
7689 // Mirror contribution.
7690 zeta = m_zmult *
7691 std::complex<double>(2. * cx - xpos - m_w[i].x, ypos - m_w[i].y);
7692 if (imag(zeta) > 15.) {
7693 wsum2 -= m_qplane[iplane][i] * icons;
7694 if (opt) volt += m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7695 } else if (imag(zeta) < -15.) {
7696 wsum2 += m_qplane[iplane][i] * icons;
7697 if (opt) volt += m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7698 } else {
7699 const auto zterm = Th1(zeta, m_p1, m_p2);
7700 wsum2 += m_qplane[iplane][i] * (zterm.second / zterm.first);
7701 if (opt) volt += m_qplane[iplane][i] * log(abs(zterm.first));
7702 }
7703 if (opt && m_mode == 0) {
7704 volt -= TwoPi * m_qplane[iplane][i] * (xpos - cx) * (m_w[i].x - cx) /
7705 (m_sx * m_sy);
7706 }
7707 }
7708 // Convert the two contributions to a real field.
7709 ex = real(m_zmult * (wsum1 + wsum2));
7710 ey = -imag(m_zmult * (wsum1 - wsum2));
7711 // Constant correction terms.
7712 if (m_mode == 0) ex += s * TwoPi / (m_sx * m_sy);
7713}
7714
7715void ComponentAnalyticField::WfieldPlaneC2Y(const double xpos,
7716 const double ypos, double& ex,
7717 double& ey, double& volt,
7718 const int iplane,
7719 const bool opt) const {
7720 //-----------------------------------------------------------------------
7721 // IPLC2Y - Routine returning the potential and electric field in a
7722 // configuration with 2 y planes and x periodicity.
7723 // VARIABLES : see the writeup
7724 // (Last changed on 12/10/06.)
7725 //-----------------------------------------------------------------------
7726
7727 constexpr std::complex<double> icons(0., 1.);
7728
7729 // Initial values.
7730 std::complex<double> wsum1 = 0.;
7731 std::complex<double> wsum2 = 0.;
7732 double s = 0.;
7733 volt = 0.;
7734
7735 // Wire loop.
7736 for (unsigned int i = 0; i < m_nWires; ++i) {
7737 // Compute the direct contribution.
7738 auto zeta =
7739 m_zmult * std::complex<double>(xpos - m_w[i].x, ypos - m_w[i].y);
7740 if (imag(zeta) > +15.) {
7741 wsum1 -= m_qplane[iplane][i] * icons;
7742 if (opt) volt -= m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7743 } else if (imag(zeta) < -15.) {
7744 wsum1 += m_qplane[iplane][i] * icons;
7745 if (opt) volt -= m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7746 } else {
7747 const auto zterm = Th1(zeta, m_p1, m_p2);
7748 wsum1 += m_qplane[iplane][i] * (zterm.second / zterm.first);
7749 if (opt) volt -= m_qplane[iplane][i] * log(abs(zterm.first));
7750 }
7751 // Find the plane nearest to the wire.
7752 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
7753 // Constant terms sum
7754 s += m_qplane[iplane][i] * (m_w[i].y - cy);
7755 // Mirror contribution.
7756 zeta = m_zmult *
7757 std::complex<double>(xpos - m_w[i].x, 2. * cy - ypos - m_w[i].y);
7758 if (imag(zeta) > 15.) {
7759 wsum2 -= m_qplane[iplane][i] * icons;
7760 if (opt) volt += m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7761 } else if (imag(zeta) < -15.) {
7762 wsum2 += m_qplane[iplane][i] * icons;
7763 if (opt) volt += m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7764 } else {
7765 const auto zterm = Th1(zeta, m_p1, m_p2);
7766 wsum2 += m_qplane[iplane][i] * (zterm.second / zterm.first);
7767 if (opt) volt += m_qplane[iplane][i] * log(abs(zterm.first));
7768 }
7769 // Correct the voltage, if needed (MODE).
7770 if (opt && m_mode == 1) {
7771 volt -= TwoPi * m_qplane[iplane][i] * (ypos - cy) * (m_w[i].y - cy) /
7772 (m_sx * m_sy);
7773 }
7774 }
7775 // Convert the two contributions to a real field.
7776 ex = real(m_zmult * (wsum1 - wsum2));
7777 ey = -imag(m_zmult * (wsum1 + wsum2));
7778 // Constant correction terms.
7779 if (m_mode == 1) ey += s * TwoPi / (m_sx * m_sy);
7780}
7781
7782void ComponentAnalyticField::WfieldPlaneC30(const double xpos,
7783 const double ypos, double& ex,
7784 double& ey, double& volt,
7785 const int iplane,
7786 const bool opt) const {
7787 //-----------------------------------------------------------------------
7788 // IPLC30 - Routine returning the weighting field field in a
7789 // configuration with 2 y and 2 x planes. This routine is
7790 // basically the same as EFCC30.
7791 // (Last changed on 9/11/98.)
7792 //-----------------------------------------------------------------------
7793
7794 constexpr std::complex<double> icons(0., 1.);
7795
7796 // Initial values.
7797 std::complex<double> wsum1 = 0.;
7798 std::complex<double> wsum2 = 0.;
7799 std::complex<double> wsum3 = 0.;
7800 std::complex<double> wsum4 = 0.;
7801 volt = 0.;
7802
7803 // Wire loop.
7804 for (unsigned int i = 0; i < m_nWires; ++i) {
7805 // Compute the direct contribution.
7806 auto zeta =
7807 m_zmult * std::complex<double>(xpos - m_w[i].x, ypos - m_w[i].y);
7808 if (imag(zeta) > +15.) {
7809 wsum1 -= m_qplane[iplane][i] * icons;
7810 if (opt) volt -= m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7811 } else if (imag(zeta) < -15.) {
7812 wsum1 += m_qplane[iplane][i] * icons;
7813 if (opt) volt -= m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7814 } else {
7815 const auto zterm = Th1(zeta, m_p1, m_p2);
7816 wsum1 += m_qplane[iplane][i] * zterm.second / zterm.first;
7817 if (opt) volt -= m_qplane[iplane][i] * log(abs(zterm.first));
7818 }
7819 // Find the plane nearest to the wire.
7820 const double cx =
7821 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
7822 // Mirror contribution from the x plane.
7823 zeta = m_zmult *
7824 std::complex<double>(2. * cx - xpos - m_w[i].x, ypos - m_w[i].y);
7825 if (imag(zeta) > 15.) {
7826 wsum2 -= m_qplane[iplane][i] * icons;
7827 if (opt) volt += m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7828 } else if (imag(zeta) < -15.) {
7829 wsum2 += m_qplane[iplane][i] * icons;
7830 if (opt) volt += m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7831 } else {
7832 const auto zterm = Th1(zeta, m_p1, m_p2);
7833 wsum2 += m_qplane[iplane][i] * zterm.second / zterm.first;
7834 if (opt) volt += m_qplane[iplane][i] * log(abs(zterm.first));
7835 }
7836 // Find the plane nearest to the wire.
7837 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
7838 // Mirror contribution from the y plane.
7839 zeta = m_zmult *
7840 std::complex<double>(xpos - m_w[i].x, 2. * cy - ypos - m_w[i].y);
7841 if (imag(zeta) > 15.) {
7842 wsum3 -= m_qplane[iplane][i] * icons;
7843 if (opt) volt += m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7844 } else if (imag(zeta) < -15.) {
7845 wsum3 += m_qplane[iplane][i] * icons;
7846 if (opt) volt += m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7847 } else {
7848 const auto zterm = Th1(zeta, m_p1, m_p2);
7849 wsum3 += m_qplane[iplane][i] * zterm.second / zterm.first;
7850 if (opt) volt += m_qplane[iplane][i] * log(abs(zterm.first));
7851 }
7852 // Mirror contribution from both the x and the y plane.
7853 zeta = m_zmult * std::complex<double>(2. * cx - xpos - m_w[i].x,
7854 2. * cy - ypos - m_w[i].y);
7855 if (imag(zeta) > 15.) {
7856 wsum4 -= m_qplane[iplane][i] * icons;
7857 if (opt) volt -= m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7858 } else if (imag(zeta) < -15.) {
7859 wsum4 += m_qplane[iplane][i] * icons;
7860 if (opt) volt -= m_qplane[iplane][i] * (fabs(imag(zeta)) - CLog2);
7861 } else {
7862 const auto zterm = Th1(zeta, m_p1, m_p2);
7863 wsum4 += m_qplane[iplane][i] * zterm.second / zterm.first;
7864 if (opt) volt -= m_qplane[iplane][i] * log(abs(zterm.first));
7865 }
7866 }
7867 ex = real(m_zmult * (wsum1 + wsum2 - wsum3 - wsum4));
7868 ey = -imag(m_zmult * (wsum1 - wsum2 + wsum3 - wsum4));
7869}
7870
7871void ComponentAnalyticField::WfieldPlaneD10(const double xpos,
7872 const double ypos, double& ex,
7873 double& ey, double& volt,
7874 const int iplane,
7875 const bool opt) const {
7876 //-----------------------------------------------------------------------
7877 // IPLD10 - Subroutine computing the signal on wire IPLANE due to a
7878 // charge at (XPOS,YPOS). This is effectively routine EFCD10.
7879 // VARIABLES : EX, EY : Electric field.
7880 // (XPOS,YPOS): The position where the field is calculated.
7881 // ZI, ZPOS : Shorthand complex notations.
7882 // (Last changed on 9/11/98.)
7883 //-----------------------------------------------------------------------
7884
7885 // Initialise the electric field and potential.
7886 ex = ey = volt = 0.;
7887
7888 // Set the complex position coordinates.
7889 std::complex<double> zpos = std::complex<double>(xpos, ypos);
7890 std::complex<double> zi;
7891 std::complex<double> wi;
7892 // Loop over all wires.
7893 for (int i = m_nWires; i--;) {
7894 // Set the complex version of the wire-coordinate for simplicity.
7895 zi = std::complex<double>(m_w[i].x, m_w[i].y);
7896 // Compute the contribution to the potential, if needed.
7897 if (opt) {
7898 volt -= m_qplane[iplane][i] *
7899 log(abs(m_cotube * (zpos - zi) / (m_cotube2 - zpos * conj(zi))));
7900 }
7901 // Compute the contribution to the electric field.
7902 wi = 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
7903 ex += m_qplane[iplane][i] * real(wi);
7904 ey += m_qplane[iplane][i] * imag(wi);
7905 }
7906}
7907
7908void ComponentAnalyticField::WfieldPlaneD30(const double xpos,
7909 const double ypos, double& ex,
7910 double& ey, double& volt,
7911 const int iplane,
7912 const bool opt) const {
7913 //-----------------------------------------------------------------------
7914 // IPLD30 - Subroutine computing the weighting field for a polygonal
7915 // cells without periodicities, type D3.
7916 // VARIABLES : EX, EY : Electric field
7917 // (XPOS,YPOS): The position where the field is calculated.
7918 // ZI, ZPOS : Shorthand complex notations.
7919 // (Last changed on 9/11/98.)
7920 //-----------------------------------------------------------------------
7921
7922 // Initialise the weighting field and potential.
7923 ex = ey = volt = 0.;
7924
7925 std::complex<double> whelp;
7926
7927 // Get the mapping of the position.
7928 std::complex<double> wpos, wdpos;
7929 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
7930 // Loop over all wires.
7931 for (unsigned int i = 0; i < m_nWires; ++i) {
7932 // Compute the contribution to the potential, if needed.
7933 if (opt) {
7934 volt -= m_qplane[iplane][i] *
7935 log(abs((wpos - wmap[i]) / (1. - wpos * conj(wmap[i]))));
7936 }
7937 // Compute the contribution to the electric field.
7938 whelp = wdpos * (1. - pow(abs(wmap[i]), 2)) /
7939 ((wpos - wmap[i]) * (1. - conj(wmap[i]) * wpos));
7940 ex += m_qplane[iplane][i] * real(whelp);
7941 ey -= m_qplane[iplane][i] * imag(whelp);
7942 }
7943 ex /= m_cotube;
7944 ey /= m_cotube;
7945}
7946
7947void ComponentAnalyticField::WfieldStripZ(
7948 const double xpos, const double ypos, double& ex, double& ey, double& volt,
7949 const int ip, const Strip& strip,
7950 // const int ip, const int is,
7951 const bool opt) const {
7952 //-----------------------------------------------------------------------
7953 // IONEST - Weighting field for strips.
7954 // (Last changed on 6/12/00.)
7955 //-----------------------------------------------------------------------
7956
7957 // Initialise the weighting field and potential.
7958 ex = ey = volt = 0.;
7959
7960 // Transform to normalised coordinates.
7961 double xw = 0., yw = 0.;
7962 switch (ip) {
7963 case 0:
7964 xw = -ypos + 0.5 * (strip.smin + strip.smax);
7965 yw = xpos - m_coplan[ip];
7966 break;
7967 case 1:
7968 xw = ypos - 0.5 * (strip.smin + strip.smax);
7969 yw = m_coplan[ip] - xpos;
7970 break;
7971 case 2:
7972 xw = xpos - 0.5 * (strip.smin + strip.smax);
7973 yw = ypos - m_coplan[ip];
7974 break;
7975 case 3:
7976 xw = -xpos + 0.5 * (strip.smin + strip.smax);
7977 yw = m_coplan[ip] - ypos;
7978 break;
7979 default:
7980 return;
7981 }
7982 // Store the gap and strip halfwidth.
7983 const double w = 0.5 * fabs(strip.smax - strip.smin);
7984 const double g = strip.gap;
7985
7986 // Make sure we are in the fiducial part of the weighting map.
7987 if (yw <= 0. || yw > g) return;
7988
7989 // Define shorthand notations.
7990 const double s = sin(Pi * yw / g);
7991 const double c = cos(Pi * yw / g);
7992 const double e1 = exp(Pi * (w - xw) / g);
7993 const double e2 = exp(-Pi * (w + xw) / g);
7994 const double ce12 = pow(c - e1, 2);
7995 const double ce22 = pow(c - e2, 2);
7996 // Check for singularities.
7997 if (c == e1 || c == e2) return;
7998 // Evaluate the potential, if requested.
7999 if (opt) {
8000 volt = atan((c - e2) / s) - atan((c - e1) / s);
8001 volt /= Pi;
8002 }
8003 // Evaluate the field.
8004 const double s2 = s * s;
8005 const double ewx = (s / g) * (e1 / (ce12 + s2) - e2 / (ce22 + s2));
8006 const double ewy = ((c / (c - e2) + s2 / ce22) / (1. + s2 / ce22) -
8007 (c / (c - e1) + s2 / ce12) / (1. + s2 / ce12)) / g;
8008
8009 // Rotate the field back to the original coordinates.
8010 switch (ip) {
8011 case 0:
8012 ex = ewy;
8013 ey = -ewx;
8014 break;
8015 case 1:
8016 ex = -ewy;
8017 ey = ewx;
8018 break;
8019 case 2:
8020 ex = ewx;
8021 ey = ewy;
8022 break;
8023 case 3:
8024 ex = -ewx;
8025 ey = -ewy;
8026 break;
8027 }
8028}
8029
8030void ComponentAnalyticField::WfieldStripXy(const double xpos, const double ypos,
8031 const double zpos, double& ex,
8032 double& ey, double& ez, double& volt,
8033 const int ip, const Strip& strip,
8034 const bool opt) const {
8035 //-----------------------------------------------------------------------
8036 // IONEST - Weighting field for strips.
8037 // (Last changed on 6/12/00.)
8038 //-----------------------------------------------------------------------
8039
8040 // Initialise the weighting field and potential.
8041 ex = ey = ez = volt = 0.;
8042
8043 // Transform to normalised coordinates.
8044 double xw = 0., yw = 0.;
8045 switch (ip) {
8046 case 0:
8047 xw = -zpos + 0.5 * (strip.smin + strip.smax);
8048 yw = xpos - m_coplan[ip];
8049 break;
8050 case 1:
8051 xw = zpos - 0.5 * (strip.smin + strip.smax);
8052 yw = m_coplan[ip] - xpos;
8053 break;
8054 case 2:
8055 xw = zpos - 0.5 * (strip.smin + strip.smax);
8056 yw = ypos - m_coplan[ip];
8057 break;
8058 case 3:
8059 xw = -zpos + 0.5 * (strip.smin + strip.smax);
8060 yw = m_coplan[ip] - ypos;
8061 break;
8062 default:
8063 return;
8064 }
8065
8066 // Store the gap and strip halfwidth.
8067 const double w = 0.5 * fabs(strip.smax - strip.smin);
8068 const double g = strip.gap;
8069
8070 // Make sure we are in the fiducial part of the weighting map.
8071 if (yw <= 0. || yw > g) return;
8072
8073 // Define shorthand notations.
8074 const double s = sin(Pi * yw / g);
8075 const double c = cos(Pi * yw / g);
8076 const double e1 = exp(Pi * (w - xw) / g);
8077 const double e2 = exp(-Pi * (w + xw) / g);
8078 const double ce12 = pow(c - e1, 2);
8079 const double ce22 = pow(c - e2, 2);
8080 // Check for singularities.
8081 if (c == e1 || c == e2) return;
8082 // Evaluate the potential, if requested.
8083 if (opt) {
8084 volt = atan((c - e2) / s) - atan((c - e1) / s);
8085 volt /= Pi;
8086 }
8087 // Evaluate the field.
8088 const double s2 = s * s;
8089 const double ewx = (s / g) * (e1 / (ce12 + s2) - e2 / (ce22 + s2));
8090 const double ewy = ((c / (c - e2) + s2 / ce22) / (1. + s2 / ce22) -
8091 (c / (c - e1) + s2 / ce12) / (1. + s2 / ce12)) / g;
8092
8093 // Rotate the field back to the original coordinates.
8094 switch (ip) {
8095 case 0:
8096 ex = ewy;
8097 ey = 0.;
8098 ez = -ewx;
8099 break;
8100 case 1:
8101 ex = -ewy;
8102 ey = 0.;
8103 ez = ewx;
8104 break;
8105 case 2:
8106 ex = 0.;
8107 ey = ewy;
8108 ez = ewx;
8109 break;
8110 case 3:
8111 ex = 0.;
8112 ey = -ewy;
8113 ez = -ewx;
8114 break;
8115 }
8116}
8117
8118void ComponentAnalyticField::WfieldPixel(const double xpos, const double ypos,
8119 const double zpos, double& ex,
8120 double& ey, double& ez, double& volt,
8121 const int ip, const Pixel& pixel,
8122 const bool opt) const {
8123 //-----------------------------------------------------------------------
8124 // Weighting field for pixels.
8125 //-----------------------------------------------------------------------
8126 // W. Riegler, G. Aglieri Rinella,
8127 // Point charge potential and weighting field of a pixel or pad
8128 // in a plane condenser,
8129 // Nucl. Instr. Meth. A 767, 2014, 267 - 270
8130 // http://dx.doi.org/10.1016/j.nima.2014.08.044
8131
8132 // Initialise the weighting field and potential.
8133 ex = ey = ez = volt = 0.;
8134
8135 const double d = pixel.gap;
8136 // Transform to standard coordinates.
8137 double x = 0., y = 0., z = 0.;
8138
8139 // Pixel centre and widths.
8140 const double ps = 0.5 * (pixel.smin + pixel.smax);
8141 const double pz = 0.5 * (pixel.zmin + pixel.zmax);
8142 const double wx = pixel.smax - pixel.smin;
8143 const double wy = pixel.zmax - pixel.zmin;
8144 switch (ip) {
8145 case 0:
8146 x = ypos - ps;
8147 y = zpos - pz;
8148 z = xpos - m_coplan[ip];
8149 break;
8150 case 1:
8151 x = ypos - ps;
8152 y = -zpos + pz;
8153 z = -xpos + m_coplan[ip];
8154 break;
8155 case 2:
8156 x = xpos - ps;
8157 y = -zpos + pz;
8158 z = ypos - m_coplan[ip];
8159 break;
8160 case 3:
8161 x = xpos - ps;
8162 y = zpos - pz;
8163 z = -ypos + m_coplan[ip];
8164 break;
8165 default:
8166 return;
8167 }
8168 // if (z < 0.) std::cerr << " z = " << z << std::endl;
8169 // Make sure we are in the fiducial part of the weighting map.
8170 // Commenting out this lines either breaks the simulation or the plot!
8171 // TODO!
8172 // if (z <= 0. || z > d) return;
8173
8174 // Define shorthand notations and common terms.
8175 const double x1 = x - 0.5 * wx;
8176 const double x2 = x + 0.5 * wx;
8177 const double y1 = y - 0.5 * wy;
8178 const double y2 = y + 0.5 * wy;
8179 const double x1s = x1 * x1;
8180 const double x2s = x2 * x2;
8181 const double y1s = y1 * y1;
8182 const double y2s = y2 * y2;
8183
8184 // Calculate number of terms needed to have sufficiently small error.
8185 const double maxError = 1.e-5;
8186 const double d3 = d * d * d;
8187 const unsigned int nz = std::ceil(sqrt(wx * wy / (8 * Pi * d3 * maxError)));
8188 const unsigned int nx = std::ceil(sqrt(wy * z / (4 * Pi * d3 * maxError)));
8189 const unsigned int ny = std::ceil(sqrt(wx * z / (4 * Pi * d3 * maxError)));
8190 const unsigned int nn = std::max(ny, std::max(nx, nz));
8191 for (unsigned int i = 1; i <= nn; ++i) {
8192 const double u1 = 2 * i * d - z;
8193 const double u2 = 2 * i * d + z;
8194 const double u1s = u1 * u1;
8195 const double u2s = u2 * u2;
8196 const double u1x1y1 = sqrt(x1s + y1s + u1s);
8197 const double u1x1y2 = sqrt(x1s + y2s + u1s);
8198 const double u1x2y1 = sqrt(x2s + y1s + u1s);
8199 const double u1x2y2 = sqrt(x2s + y2s + u1s);
8200 const double u2x1y1 = sqrt(x1s + y1s + u2s);
8201 const double u2x1y2 = sqrt(x1s + y2s + u2s);
8202 const double u2x2y1 = sqrt(x2s + y1s + u2s);
8203 const double u2x2y2 = sqrt(x2s + y2s + u2s);
8204
8205 if (i <= nx) {
8206 //-df/dx(x,y,2nd-z)
8207 ex -= u1 * y1 / ((u1s + x2s) * u1x2y1) -
8208 u1 * y1 / ((u1s + x1s) * u1x1y1) +
8209 u1 * y2 / ((u1s + x1s) * u1x1y2) - u1 * y2 / ((u1s + x2s) * u1x2y2);
8210
8211 //-df/dx(x,y,2nd+z)
8212 ex += u2 * y1 / ((u2s + x2s) * u2x2y1) -
8213 u2 * y1 / ((u2s + x1s) * u2x1y1) +
8214 u2 * y2 / ((u2s + x1s) * u2x1y2) - u2 * y2 / ((u2s + x2s) * u2x2y2);
8215 }
8216 if (i <= ny) {
8217 //-df/dy(x,y,2nd-z)
8218 ey -= u1 * x1 / ((u1s + y2s) * u1x1y2) -
8219 u1 * x1 / ((u1s + y1s) * u1x1y1) +
8220 u1 * x2 / ((u1s + y1s) * u1x2y1) - u1 * x2 / ((u1s + y2s) * u1x2y2);
8221
8222 //-df/dy(x,y,2nd+z)
8223 ey += u2 * x1 / ((u2s + y2s) * u2x1y2) -
8224 u2 * x1 / ((u2s + y1s) * u2x1y1) +
8225 u2 * x2 / ((u2s + y1s) * u2x2y1) - u2 * x2 / ((u2s + y2s) * u2x2y2);
8226 }
8227 if (i <= nz) {
8228 //-df/dz(x,y,2nd-z)
8229 ez += x1 * y1 * (x1s + y1s + 2 * u1s) /
8230 ((x1s + u1s) * (y1s + u1s) * u1x1y1) +
8231 x2 * y2 * (x2s + y2s + 2 * u1s) /
8232 ((x2s + u1s) * (y2s + u1s) * u1x2y2) -
8233 x1 * y2 * (x1s + y2s + 2 * u1s) /
8234 ((x1s + u1s) * (y2s + u1s) * u1x1y2) -
8235 x2 * y1 * (x2s + y1s + 2 * u1s) /
8236 ((x2s + u1s) * (y1s + u1s) * u1x2y1);
8237
8238 //-df/dz(x,y,2nd+z)
8239 ez += x1 * y1 * (x1s + y1s + 2 * u2s) /
8240 ((x1s + u2s) * (y1s + u2s) * u2x1y1) +
8241 x2 * y2 * (x2s + y2s + 2 * u2s) /
8242 ((x2s + u2s) * (y2s + u2s) * u2x2y2) -
8243 x1 * y2 * (x1s + y2s + 2 * u2s) /
8244 ((x1s + u2s) * (y2s + u2s) * u2x1y2) -
8245 x2 * y1 * (x2s + y1s + 2 * u2s) /
8246 ((x2s + u2s) * (y1s + u2s) * u2x2y1);
8247 }
8248 if (!opt) continue;
8249 volt -= atan(x1 * y1 / (u1 * u1x1y1)) + atan(x2 * y2 / (u1 * u1x2y2)) -
8250 atan(x1 * y2 / (u1 * u1x1y2)) - atan(x2 * y1 / (u1 * u1x2y1));
8251 volt += atan(x1 * y1 / (u2 * u2x1y1)) + atan(x2 * y2 / (u2 * u2x2y2)) -
8252 atan(x1 * y2 / (u2 * u2x1y2)) - atan(x2 * y1 / (u2 * u2x2y1));
8253 }
8254
8255 const double zs = z * z;
8256 const double x1y1 = sqrt(x1s + y1s + zs);
8257 const double x1y2 = sqrt(x1s + y2s + zs);
8258 const double x2y1 = sqrt(x2s + y1s + zs);
8259 const double x2y2 = sqrt(x2s + y2s + zs);
8260 //-df/dx(x,y,z)
8261 ex += z * y1 / ((zs + x2s) * x2y1) - z * y1 / ((zs + x1s) * x1y1) +
8262 z * y2 / ((zs + x1s) * x1y2) - z * y2 / ((zs + x2s) * x2y2);
8263
8264 //-df/y(x,y,z)
8265 ey += z * x1 / ((zs + y2s) * x1y2) - z * x1 / ((zs + y1s) * x1y1) +
8266 z * x2 / ((zs + y1s) * x2y1) - z * x2 / ((zs + y2s) * x2y2);
8267
8268 //-df/dz(x,y,z)
8269 ez += x1 * y1 * (x1s + y1s + 2 * zs) / ((x1s + zs) * (y1s + zs) * x1y1) +
8270 x2 * y2 * (x2s + y2s + 2 * zs) / ((x2s + zs) * (y2s + zs) * x2y2) -
8271 x1 * y2 * (x1s + y2s + 2 * zs) / ((x1s + zs) * (y2s + zs) * x1y2) -
8272 x2 * y1 * (x2s + y1s + 2 * zs) / ((x2s + zs) * (y1s + zs) * x2y1);
8273
8274 constexpr double invTwoPi = 1. / TwoPi;
8275 ex *= invTwoPi;
8276 ey *= invTwoPi;
8277 ez *= invTwoPi;
8278
8279 if (opt) {
8280 volt += atan(x1 * y1 / (z * x1y1)) + atan(x2 * y2 / (z * x2y2)) -
8281 atan(x1 * y2 / (z * x1y2)) - atan(x2 * y1 / (z * x2y1));
8282 volt *= invTwoPi;
8283 }
8284
8285 // Rotate the field back to the original coordinates.
8286 const double fx = ex;
8287 const double fy = ey;
8288 const double fz = ez;
8289 switch (ip) {
8290 case 0:
8291 ex = fz;
8292 ey = fx;
8293 ez = fy;
8294 break;
8295 case 1:
8296 ex = -fz;
8297 ey = fx;
8298 ez = -fy;
8299 break;
8300 case 2:
8301 ex = fx;
8302 ey = fz;
8303 ez = -fy;
8304 break;
8305 case 3:
8306 ex = fx;
8307 ey = -fz;
8308 ez = fy;
8309 break;
8310 }
8311}
8312
8313void ComponentAnalyticField::FieldAtWireA00(
8314 const double xpos, const double ypos, double& ex, double& ey,
8315 const std::vector<bool>& cnalso) const {
8316 //-----------------------------------------------------------------------
8317 // FFCA00 - Subroutine performing the actual field calculations in case
8318 // only one charge and not more than 1 mirror-charge in either
8319 // x or y is present.
8320 // The potential used is 1/2*pi*eps0 log(r).
8321 // (Last changed on 27/ 1/96.)
8322 //-----------------------------------------------------------------------
8323
8324 ex = ey = 0.;
8325 // Loop over all wires.
8326 for (unsigned int i = 0; i < m_nWires; ++i) {
8327 const auto& wire = m_w[i];
8328 // Calculate the field in case there are no planes.
8329 double exhelp = 0.;
8330 double eyhelp = 0.;
8331 const double xx = xpos - wire.x;
8332 const double yy = ypos - wire.y;
8333 if (cnalso[i]) {
8334 const double r2 = xx * xx + yy * yy;
8335 exhelp = xx / r2;
8336 eyhelp = yy / r2;
8337 }
8338 // Take care of a plane at constant x.
8339 double xxmirr = 0.;
8340 if (m_ynplax) {
8341 xxmirr = wire.x + xpos - 2 * m_coplax;
8342 const double r2plan = xxmirr * xxmirr + yy * yy;
8343 exhelp -= xxmirr / r2plan;
8344 eyhelp -= yy / r2plan;
8345 }
8346 // Take care of a plane at constant y.
8347 double yymirr = 0.;
8348 if (m_ynplay) {
8349 yymirr = wire.y + ypos - 2 * m_coplay;
8350 const double r2plan = xx * xx + yymirr * yymirr;
8351 exhelp -= xx / r2plan;
8352 eyhelp -= yymirr / r2plan;
8353 }
8354 // Take care of pairs of planes.
8355 if (m_ynplax && m_ynplay) {
8356 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
8357 exhelp += xxmirr / r2plan;
8358 eyhelp += yymirr / r2plan;
8359 }
8360 ex += wire.e * exhelp;
8361 ey += wire.e * eyhelp;
8362 }
8363}
8364
8365void ComponentAnalyticField::FieldAtWireB1X(
8366 const double xpos, const double ypos, double& ex, double& ey,
8367 const std::vector<bool>& cnalso) const {
8368 //-----------------------------------------------------------------------
8369 // FFCB1X - Routine calculating the potential for a row of positive
8370 // charges. The potential used is Re(Log(sin pi/s (z-z0))).
8371 //-----------------------------------------------------------------------
8372
8373 constexpr std::complex<double> icons(0., 1.);
8374 std::complex<double> ecompl;
8375 ex = ey = 0.;
8376 const double tx = Pi / m_sx;
8377 if (m_ynplay) {
8378 // With a y plane.
8379 for (unsigned int i = 0; i < m_nWires; ++i) {
8380 const auto& wire = m_w[i];
8381 const double xx = tx * (xpos - wire.x);
8382 const double yy = tx * (ypos - wire.y);
8383 if (!cnalso[i]) {
8384 ecompl = 0.;
8385 } else if (yy > 20.) {
8386 ecompl = -icons;
8387 } else if (yy < -20.) {
8388 ecompl = icons;
8389 } else {
8390 const std::complex<double> zz(xx, yy);
8391 const auto expzz = exp(2. * icons * zz);
8392 ecompl = icons * (expzz + 1.) / (expzz - 1.);
8393 }
8394 const double yymirr = tx * (ypos + wire.y - 2. * m_coplay);
8395 if (yymirr > 20.) {
8396 ecompl += icons;
8397 } else if (yymirr < -20.) {
8398 ecompl += -icons;
8399 } else {
8400 const std::complex<double> zzmirr(xx, yymirr);
8401 const auto expzzmirr = exp(2. * icons * zzmirr);
8402 ecompl += -icons * (expzzmirr + 1.) / (expzzmirr - 1.);
8403 }
8404 // Update the field.
8405 ex += wire.e * real(ecompl);
8406 ey -= wire.e * imag(ecompl);
8407 }
8408 } else {
8409 // Without a y plane.
8410 for (unsigned int i = 0; i < m_nWires; ++i) {
8411 if (!cnalso[i]) continue;
8412 const auto& wire = m_w[i];
8413 const double xx = tx * (xpos - wire.x);
8414 const double yy = tx * (ypos - wire.y);
8415 if (yy > 20.) {
8416 ecompl = -icons;
8417 } else if (yy < -20.) {
8418 ecompl = icons;
8419 } else {
8420 const std::complex<double> zz(xx, yy);
8421 const auto expzz = exp(2. * icons * zz);
8422 ecompl = icons * (expzz + 1.) / (expzz - 1.);
8423 }
8424 ex += wire.e * real(ecompl);
8425 ey -= wire.e * imag(ecompl);
8426 }
8427 }
8428 ex *= tx;
8429 ey *= tx;
8430}
8431
8432void ComponentAnalyticField::FieldAtWireB1Y(
8433 const double xpos, const double ypos, double& ex, double& ey,
8434 const std::vector<bool>& cnalso) const {
8435 //-----------------------------------------------------------------------
8436 // FFCB1Y - Routine calculating the potential for a row of positive
8437 // charges. The potential used is Re(Log(sinh pi/sy(z-z0)).
8438 //-----------------------------------------------------------------------
8439
8440 std::complex<double> ecompl;
8441 ex = ey = 0.;
8442 const double ty = Pi / m_sy;
8443 if (m_ynplax) {
8444 // With an x plane.
8445 for (unsigned int i = 0; i < m_nWires; ++i) {
8446 const auto& wire = m_w[i];
8447 const double xx = ty * (xpos - wire.x);
8448 const double yy = ty * (ypos - wire.y);
8449 if (!cnalso[i]) {
8450 ecompl = 0.;
8451 } else if (xx > 20.) {
8452 ecompl = 1.;
8453 } else if (xx < -20.) {
8454 ecompl = -1.;
8455 } else {
8456 const std::complex<double> zz(xx, yy);
8457 const auto expzz = exp(2. * zz);
8458 ecompl = (expzz + 1.) / (expzz - 1.);
8459 }
8460 const double xxmirr = ty * (xpos + wire.x - 2. * m_coplax);
8461 if (xxmirr > 20.) {
8462 ecompl -= 1.;
8463 } else if (xxmirr < -20.) {
8464 ecompl += 1.;
8465 } else {
8466 const std::complex<double> zzmirr(xxmirr, yy);
8467 const auto expzzmirr = exp(2. * zzmirr);
8468 ecompl -= (expzzmirr + 1.) / (expzzmirr - 1.);
8469 }
8470 ex += wire.e * real(ecompl);
8471 ey -= wire.e * imag(ecompl);
8472 }
8473 } else {
8474 // Without an x plane.
8475 for (unsigned int i = 0; i < m_nWires; ++i) {
8476 if (!cnalso[i]) continue;
8477 const auto& wire = m_w[i];
8478 const double xx = ty * (xpos - wire.x);
8479 const double yy = ty * (ypos - wire.y);
8480 if (xx > 20.) {
8481 ecompl = 1.;
8482 } else if (xx < -20.) {
8483 ecompl = -1.;
8484 } else {
8485 const std::complex<double> zz(xx, yy);
8486 const auto expzz = exp(2. * zz);
8487 ecompl = (expzz + 1.) / (expzz - 1.);
8488 }
8489 ex += wire.e * real(ecompl);
8490 ey -= wire.e * imag(ecompl);
8491 }
8492 }
8493 ex *= ty;
8494 ey *= ty;
8495}
8496
8497void ComponentAnalyticField::FieldAtWireB2X(
8498 const double xpos, const double ypos, double& ex, double& ey,
8499 const std::vector<bool>& cnalso) const {
8500 //-----------------------------------------------------------------------
8501 // FFCB2X - Routine calculating the potential for a row of alternating
8502 // + - charges. The potential used is re log(sin pi/sx (z-z0))
8503 //-----------------------------------------------------------------------
8504 constexpr std::complex<double> icons(0., 1.);
8505 ex = ey = 0.;
8506 const double tx = HalfPi / m_sx;
8507 // Loop over all wires.
8508 for (unsigned int i = 0; i < m_nWires; ++i) {
8509 const double xx = tx * (xpos - m_w[i].x);
8510 const double yy = tx * (ypos - m_w[i].y);
8511 const double xxneg = tx * (xpos - m_w[i].x - 2 * m_coplax);
8512 // Calculate the field in case there are no equipotential planes.
8513 std::complex<double> ecompl(0., 0.);
8514 if (cnalso[i] && fabs(yy) <= 20.) {
8515 const std::complex<double> zz(xx, yy);
8516 const std::complex<double> zzneg(xxneg, yy);
8517 ecompl = -m_b2sin[i] / (sin(zz) * sin(zzneg));
8518 } else if (fabs(yy) <= 20.) {
8519 const std::complex<double> zzneg(xxneg, yy);
8520 const auto expzzneg = exp(2. * icons * zzneg);
8521 ecompl = -icons * (expzzneg + 1.) / (expzzneg - 1.);
8522 }
8523 // Take care of planes at constant y.
8524 if (m_ynplay) {
8525 const double yymirr = tx * (ypos + m_w[i].y - 2 * m_coplay);
8526 if (fabs(yymirr) <= 20.) {
8527 const std::complex<double> zzmirr(xx, yymirr);
8528 const std::complex<double> zznmirr(xxneg, yymirr);
8529 ecompl += m_b2sin[i] / (sin(zzmirr) * sin(zznmirr));
8530 }
8531 }
8532 ex += m_w[i].e * real(ecompl);
8533 ey -= m_w[i].e * imag(ecompl);
8534 }
8535 ex *= tx;
8536 ey *= tx;
8537}
8538
8539void ComponentAnalyticField::FieldAtWireB2Y(
8540 const double xpos, const double ypos, double& ex, double& ey,
8541 const std::vector<bool>& cnalso) const {
8542 //-----------------------------------------------------------------------
8543 // FFCB2Y - Routine calculating the potential for a row of alternating
8544 // + - charges. The potential used is re log(sin pi/sx (z-z0))
8545 //-----------------------------------------------------------------------
8546 constexpr std::complex<double> icons(0., 1.);
8547 ex = ey = 0.;
8548 const double ty = HalfPi / m_sy;
8549 // Loop over all wires.
8550 for (unsigned int i = 0; i < m_nWires; ++i) {
8551 const double xx = ty * (xpos - m_w[i].x);
8552 const double yy = ty * (ypos - m_w[i].y);
8553 const double yyneg = ty * (ypos + m_w[i].y - 2 * m_coplay);
8554 // Calculate the field in case there are no equipotential planes.
8555 std::complex<double> ecompl(0., 0.);
8556 if (cnalso[i] && fabs(xx) <= 20.) {
8557 const std::complex<double> zz(xx, yy);
8558 const std::complex<double> zzneg(xx, yyneg);
8559 ecompl = icons * m_b2sin[i] / (sin(icons * zz) * sin(icons * zzneg));
8560 } else if (fabs(xx) <= 20.) {
8561 const std::complex<double> zzneg(xx, yyneg);
8562 const auto expzzneg = exp(2. * zzneg);
8563 ecompl = -(expzzneg + 1.) / (expzzneg - 1.);
8564 }
8565 // Take care of a plane at constant x.
8566 if (m_ynplax) {
8567 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
8568 if (fabs(xxmirr) <= 20.) {
8569 const std::complex<double> zzmirr(xxmirr, yy);
8570 const std::complex<double> zznmirr(xxmirr, yyneg);
8571 ecompl -=
8572 icons * m_b2sin[i] / (sin(icons * zzmirr) * sin(icons * zznmirr));
8573 }
8574 }
8575 ex += m_w[i].e * real(ecompl);
8576 ey -= m_w[i].e * imag(ecompl);
8577 }
8578 ex *= ty;
8579 ey *= ty;
8580}
8581
8582void ComponentAnalyticField::FieldAtWireC10(
8583 const double xpos, const double ypos, double& ex, double& ey,
8584 const std::vector<bool>& cnalso) const {
8585 //-----------------------------------------------------------------------
8586 // FFCC10 - Routine returning the potential and electric field. It
8587 // calls the routines PH2 and E2SUM written by G.A.Erskine.
8588 //-----------------------------------------------------------------------
8589 constexpr std::complex<double> icons(0., 1.);
8590 std::complex<double> wsum(0., 0.);
8591 // Loop over the wires.
8592 for (unsigned int j = 0; j < m_nWires; ++j) {
8593 if (!cnalso[j]) continue;
8594 const auto& wire = m_w[j];
8595 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
8596 if (imag(zeta) > 15.) {
8597 wsum -= wire.e * icons;
8598 } else if (imag(zeta) < -15.) {
8599 wsum += wire.e * icons;
8600 } else {
8601 const auto zterm = Th1(zeta, m_p1, m_p2);
8602 wsum += wire.e * (zterm.second / zterm.first);
8603 }
8604 }
8605 ex = -real(-m_zmult * wsum);
8606 ey = imag(-m_zmult * wsum);
8607 if (m_mode == 0) ex -= m_c1;
8608 if (m_mode == 1) ey -= m_c1;
8609}
8610
8611void ComponentAnalyticField::FieldAtWireC2X(
8612 const double xpos, const double ypos, double& ex, double& ey,
8613 const std::vector<bool>& cnalso) const {
8614 //-----------------------------------------------------------------------
8615 // FFCC2X - Routine returning the potential and electric field in a
8616 // configuration with 2 x planes and y periodicity.
8617 //-----------------------------------------------------------------------
8618 constexpr std::complex<double> icons(0., 1.);
8619 // Initial values.
8620 std::complex<double> wsum1 = 0.;
8621 std::complex<double> wsum2 = 0.;
8622 for (unsigned int i = 0; i < m_nWires; ++i) {
8623 const auto& wire = m_w[i];
8624 if (cnalso[i]) {
8625 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
8626 if (imag(zeta) > 15.) {
8627 wsum1 -= wire.e * icons;
8628 } else if (imag(zeta) < -15.) {
8629 wsum1 += wire.e * icons;
8630 } else {
8631 const auto zterm = Th1(zeta, m_p1, m_p2);
8632 wsum1 += wire.e * (zterm.second / zterm.first);
8633 }
8634 }
8635 // Find the plane nearest to the wire.
8636 const double cx = m_coplax - m_sx * int(round((m_coplax - wire.x) / m_sx));
8637 // Mirror contribution.
8638 auto zeta =
8639 m_zmult * std::complex<double>(2. * cx - xpos - wire.x, ypos - wire.y);
8640 if (imag(zeta) > 15.) {
8641 wsum2 -= wire.e * icons;
8642 } else if (imag(zeta) < -15.) {
8643 wsum2 += wire.e * icons;
8644 } else {
8645 const auto zterm = Th1(zeta, m_p1, m_p2);
8646 wsum2 += wire.e * (zterm.second / zterm.first);
8647 }
8648 }
8649 // Convert the two contributions to a real field.
8650 ex = real(m_zmult * (wsum1 + wsum2));
8651 ey = -imag(m_zmult * (wsum1 - wsum2));
8652 // Constant correction terms.
8653 if (m_mode == 0) ex -= m_c1;
8654}
8655
8656void ComponentAnalyticField::FieldAtWireC2Y(
8657 const double xpos, const double ypos, double& ex, double& ey,
8658 const std::vector<bool>& cnalso) const {
8659 //-----------------------------------------------------------------------
8660 // FFCC2Y - Routine returning the potential and electric field in a
8661 // configuration with 2 y planes and x periodicity.
8662 //-----------------------------------------------------------------------
8663 const std::complex<double> icons(0., 1.);
8664 // Initial values.
8665 std::complex<double> wsum1 = 0.;
8666 std::complex<double> wsum2 = 0.;
8667 for (unsigned int i = 0; i < m_nWires; ++i) {
8668 const auto& wire = m_w[i];
8669 if (cnalso[i]) {
8670 // Compute the direct contribution.
8671 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
8672 if (imag(zeta) > 15.) {
8673 wsum1 -= wire.e * icons;
8674 } else if (imag(zeta) < -15.) {
8675 wsum1 += wire.e * icons;
8676 } else {
8677 const auto zterm = Th1(zeta, m_p1, m_p2);
8678 wsum1 += wire.e * (zterm.second / zterm.first);
8679 }
8680 }
8681 // Find the plane nearest to the wire.
8682 const double cy = m_coplay - m_sy * int(round((m_coplay - wire.y) / m_sy));
8683 // Mirror contribution from the y plane.
8684 auto zeta =
8685 m_zmult * std::complex<double>(xpos - wire.x, 2 * cy - ypos - wire.y);
8686 if (imag(zeta) > 15.) {
8687 wsum2 -= wire.e * icons;
8688 } else if (imag(zeta) < -15.) {
8689 wsum2 += wire.e * icons;
8690 } else {
8691 const auto zterm = Th1(zeta, m_p1, m_p2);
8692 wsum2 += wire.e * (zterm.second / zterm.first);
8693 }
8694 }
8695 // Convert the two contributions to a real field.
8696 ex = real(m_zmult * (wsum1 - wsum2));
8697 ey = -imag(m_zmult * (wsum1 + wsum2));
8698 // Constant correction terms.
8699 if (m_mode == 1) ey -= m_c1;
8700}
8701
8702void ComponentAnalyticField::FieldAtWireC30(
8703 const double xpos, const double ypos, double& ex, double& ey,
8704 const std::vector<bool>& cnalso) const {
8705 //-----------------------------------------------------------------------
8706 // FFCC30 - Routine returning the potential and electric field in a
8707 // configuration with 2 y and 2 x planes.
8708 //-----------------------------------------------------------------------
8709 constexpr std::complex<double> icons(0., 1.);
8710 // Initial values.
8711 std::complex<double> wsum1 = 0.;
8712 std::complex<double> wsum2 = 0.;
8713 std::complex<double> wsum3 = 0.;
8714 std::complex<double> wsum4 = 0.;
8715 for (unsigned int i = 0; i < m_nWires; ++i) {
8716 const auto& wire = m_w[i];
8717 if (cnalso[i]) {
8718 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
8719 if (imag(zeta) > 15.) {
8720 wsum1 -= wire.e * icons;
8721 } else if (imag(zeta) < -15.) {
8722 wsum1 += wire.e * icons;
8723 } else {
8724 const auto zterm = Th1(zeta, m_p1, m_p2);
8725 wsum1 += wire.e * (zterm.second / zterm.first);
8726 }
8727 }
8728 // Find the plane nearest to the wire.
8729 const double cx = m_coplax - m_sx * int(round((m_coplax - wire.x) / m_sx));
8730 // Mirror contribution from the x plane.
8731 auto zeta =
8732 m_zmult * std::complex<double>(2. * cx - xpos - wire.x, ypos - wire.y);
8733 if (imag(zeta) > 15.) {
8734 wsum2 -= wire.e * icons;
8735 } else if (imag(zeta) < -15.) {
8736 wsum2 += wire.e * icons;
8737 } else {
8738 const auto zterm = Th1(zeta, m_p1, m_p2);
8739 wsum2 += wire.e * (zterm.second / zterm.first);
8740 }
8741 // Find the plane nearest to the wire.
8742 const double cy = m_coplay - m_sy * int(round((m_coplay - wire.y) / m_sy));
8743 // Mirror contribution from the x plane.
8744 zeta =
8745 m_zmult * std::complex<double>(xpos - wire.x, 2. * cy - ypos - wire.y);
8746 if (imag(zeta) > 15.) {
8747 wsum3 -= wire.e * icons;
8748 } else if (imag(zeta) < -15.) {
8749 wsum3 += wire.e * icons;
8750 } else {
8751 const auto zterm = Th1(zeta, m_p1, m_p2);
8752 wsum3 += wire.e * (zterm.second / zterm.first);
8753 }
8754 // Mirror contribution from both the x and the y plane.
8755 zeta = m_zmult * std::complex<double>(2. * cx - xpos - wire.x,
8756 2. * cy - ypos - wire.y);
8757 if (imag(zeta) > 15.) {
8758 wsum4 -= wire.e * icons;
8759 } else if (imag(zeta) < -15.) {
8760 wsum4 += wire.e * icons;
8761 } else {
8762 const auto zterm = Th1(zeta, m_p1, m_p2);
8763 wsum4 += wire.e * (zterm.second / zterm.first);
8764 }
8765 }
8766 // Convert the two contributions to a real field.
8767 ex = real(m_zmult * (wsum1 + wsum2 - wsum3 - wsum4));
8768 ey = -imag(m_zmult * (wsum1 - wsum2 + wsum3 - wsum4));
8769}
8770
8771void ComponentAnalyticField::FieldAtWireD10(
8772 const double xpos, const double ypos, double& ex, double& ey,
8773 const std::vector<bool>& cnalso) const {
8774 //-----------------------------------------------------------------------
8775 // FFCD10 - Subroutine performing the actual field calculations for a
8776 // cell which has a one circular plane and some wires.
8777 //-----------------------------------------------------------------------
8778 ex = ey = 0.;
8779 // Set the complex position coordinates.
8780 const std::complex<double> zpos(xpos, ypos);
8781 // Loop over all wires.
8782 for (unsigned int i = 0; i < m_nWires; ++i) {
8783 const auto& wire = m_w[i];
8784 // Set the complex version of the wire-coordinate for simplicity.
8785 const std::complex<double> zi(wire.x, wire.y);
8786 // First the case that the wire has to be taken fully.
8787 if (cnalso[i]) {
8788 const std::complex<double> wi =
8789 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
8790 ex += wire.e * real(wi);
8791 ey += wire.e * imag(wi);
8792 } else {
8793 const std::complex<double> wi = zi / (m_cotube2 - conj(zpos) * zi);
8794 ex += wire.e * real(wi);
8795 ey += wire.e * imag(wi);
8796 }
8797 }
8798}
8799
8800void ComponentAnalyticField::FieldAtWireD20(
8801 const double xpos, const double ypos, double& ex, double& ey,
8802 const std::vector<bool>& cnalso) const {
8803 //-----------------------------------------------------------------------
8804 // FFCD20 - Subroutine performing the actual field calculations for a
8805 // cell which has a tube and phi periodicity.
8806 //-----------------------------------------------------------------------
8807 ex = ey = 0.;
8808 // Set the complex position coordinates.
8809 const std::complex<double> zpos(xpos, ypos);
8810 for (unsigned int i = 0; i < m_nWires; ++i) {
8811 const auto& wire = m_w[i];
8812 // Set the complex version of the wire-coordinate for simplicity.
8813 const std::complex<double> zi(wire.x, wire.y);
8814 if (cnalso[i]) {
8815 // Case of the wire which is not in the centre.
8816 if (std::abs(zi) > wire.r) {
8817 const std::complex<double> wi =
8818 double(m_mtube) * pow(conj(zpos), m_mtube - 1) *
8819 (1. / conj(pow(zpos, m_mtube) - pow(zi, m_mtube)) +
8820 pow(zi, m_mtube) /
8821 (pow(m_cotube, 2 * m_mtube) - pow(conj(zpos) * zi, m_mtube)));
8822 ex += wire.e * real(wi);
8823 ey += wire.e * imag(wi);
8824 } else {
8825 const std::complex<double> wi =
8826 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
8827 ex += wire.e * real(wi);
8828 ey += wire.e * imag(wi);
8829 }
8830 } else {
8831 if (abs(zi) > wire.r) {
8832 const std::complex<double> wi =
8833 double(m_mtube) * pow(conj(zpos), m_mtube - 1) *
8834 (pow(zi, m_mtube) /
8835 (pow(m_cotube, 2 * m_mtube) - pow(conj(zpos) * zi, m_mtube)));
8836 ex += wire.e * real(wi);
8837 ey += wire.e * imag(wi);
8838 } else {
8839 const std::complex<double> wi = zi / (m_cotube2 - conj(zpos) * zi);
8840 ex += wire.e * real(wi);
8841 ey += wire.e * imag(wi);
8842 }
8843 }
8844 }
8845}
8846
8847void ComponentAnalyticField::FieldAtWireD30(
8848 const double xpos, const double ypos, double& ex, double& ey,
8849 const std::vector<bool>& cnalso) const {
8850 //-----------------------------------------------------------------------
8851 // FFCD30 - Subroutine performing the actual field calculations for a
8852 // cell which has a polygon as tube and some wires.
8853 //-----------------------------------------------------------------------
8854 ex = ey = 0.;
8855 // Get the mapping of the position.
8856 std::complex<double> wpos, wdpos;
8857 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
8858 // Loop over all wires.
8859 for (unsigned int i = 0; i < m_nWires; ++i) {
8860 if (cnalso[i]) {
8861 // Full contribution.
8862 const std::complex<double> whelp =
8863 wdpos * (1. - pow(abs(wmap[i]), 2)) /
8864 ((wpos - wmap[i]) * (1. - conj(wmap[i]) * wpos));
8865 ex += m_w[i].e * real(whelp);
8866 ey -= m_w[i].e * imag(whelp);
8867 } else {
8868 // Mirror charges only.
8869 const std::complex<double> whelp =
8870 wdpos * conj(wmap[i]) / (1. - conj(wmap[i]) * wpos);
8871 ex += m_w[i].e * real(whelp);
8872 ey -= m_w[i].e * imag(whelp);
8873 }
8874 }
8875 ex /= m_cotube;
8876 ey /= m_cotube;
8877}
8878
8879bool ComponentAnalyticField::SagDetailed(
8880 const Wire& wire, const std::vector<double>& xMap,
8881 const std::vector<double>& yMap,
8882 const std::vector<std::vector<double> >& fxMap,
8883 const std::vector<std::vector<double> >& fyMap, std::vector<double>& csag,
8884 std::vector<double>& xsag, std::vector<double>& ysag) const {
8885 //-----------------------------------------------------------------------
8886 // OPTSAG - Computes the wire sag due to electrostatic and gravitational
8887 // forces, using a Runge-Kutta-Nystrom multiple shoot method,
8888 // where the intermediate conditions are imposed through a
8889 // Broyden rank-1 zero search.
8890 //-----------------------------------------------------------------------
8891
8892 csag.clear();
8893 xsag.clear();
8894 ysag.clear();
8895 const unsigned int np = m_nSteps * (m_nShots + 1);
8896 // Compute the step width.
8897 const double h = wire.u / np;
8898 // Compute expected maximum sag, constant-force approximation.
8899 std::array<double, 2> xst = {0., 0.};
8900 std::array<double, 2> dxst = {0., 0.};
8901 double fxmean = 0.;
8902 double fymean = 0.;
8903 // Loop over the whole wire.
8904 for (unsigned int i = 0; i <= np; ++i) {
8905 const double z = i * h;
8906 std::array<double, 2> force = {0., 0.};
8907 if (!GetForceRatio(wire, z, xst, dxst, force, xMap, yMap, fxMap, fyMap)) {
8908 std::cerr << m_className << "::SagDetailed:\n"
8909 << " Wire at nominal position outside scanning area.\n";
8910 return false;
8911 }
8912 fxmean += force[0];
8913 fymean += force[1];
8914 }
8915 const double u2 = wire.u * wire.u;
8916 // Compute expected sag.
8917 const double s = u2 / (8. * (1. + np));
8918 double sagx0 = -fxmean * s;
8919 double sagy0 = -fymean * s;
8920 if (m_debug) {
8921 std::cout << m_className << "::SagDetailed: Parabolic sag.\n";
8922 std::printf(" dx = %12.5e, dy = %12.5e [cm]\n", sagx0, sagy0);
8923 }
8924 // Starting position: parabolic sag.
8925 std::vector<double> xx(4 * m_nShots + 2);
8926 // Derivative first point.
8927 xx[0] = 4 * sagx0 / wire.u;
8928 xx[1] = 4 * sagy0 / wire.u;
8929 // Intermediate points, both position and derivative.
8930 for (unsigned int i = 1; i <= m_nShots; ++i) {
8931 // Position along the wire.
8932 const double z = -0.5 * wire.u + i * m_nSteps * h;
8933 const unsigned int k = 4 * i - 2;
8934 // Deflection.
8935 const double f = 1. - 4 * z * z / u2;
8936 xx[k] = sagx0 * f;
8937 xx[k + 1] = sagy0 * f;
8938 // Derivative.
8939 const double fp = -8 * z / u2;
8940 xx[k + 2] = sagx0 * fp;
8941 xx[k + 3] = sagy0 * fp;
8942 }
8943 // Search for solution.
8944 if (!FindZeroes(wire, h, xx, xMap, yMap, fxMap, fyMap)) {
8945 std::cerr << m_className << "::SagDetailed:\n"
8946 << " Failed to solve the differential equation for the sag.\n";
8947 return false;
8948 }
8949 // And return the detailed solution, first the starting point.
8950 csag.assign(np + 1, 0.);
8951 xsag.assign(np + 1, 0.);
8952 ysag.assign(np + 1, 0.);
8953 csag[0] = -0.5 * wire.u;
8954 double coor = -0.5 * wire.u;
8955 for (unsigned int i = 0; i <= m_nShots; ++i) {
8956 // Set the starting value and starting derivative.
8957 if (i == 0) {
8958 xst[0] = 0;
8959 xst[1] = 0;
8960 dxst[0] = xx[0];
8961 dxst[1] = xx[1];
8962 } else {
8963 xst[0] = xx[4 * i - 2];
8964 xst[1] = xx[4 * i - 1];
8965 dxst[0] = xx[4 * i];
8966 dxst[1] = xx[4 * i + 1];
8967 }
8968 // Store the intermediate values.
8969 for (unsigned int j = 1; j <= m_nSteps; ++j) {
8970 StepRKN(wire, h, coor, xst, dxst, xMap, yMap, fxMap, fyMap);
8971 csag[i * m_nSteps + j] = coor;
8972 xsag[i * m_nSteps + j] = xst[0];
8973 ysag[i * m_nSteps + j] = xst[1];
8974 }
8975 }
8976 // Seems to have worked.
8977 return true;
8978}
8979
8980bool ComponentAnalyticField::GetForceRatio(
8981 const Wire& wire, const double /*coor*/, const std::array<double, 2>& bend,
8982 const std::array<double, 2>& /*dbend*/, std::array<double, 2>& f,
8983 const std::vector<double>& xMap, const std::vector<double>& yMap,
8984 const std::vector<std::vector<double> >& fxMap,
8985 const std::vector<std::vector<double> >& fyMap) const {
8986 //-----------------------------------------------------------------------
8987 // OPTSTP - Returns the electrostatic and gravitational force divided
8988 // by the stretching force acting on a wire at position COOR,
8989 // with deflection BEND and bending derivative DBEND.
8990 //-----------------------------------------------------------------------
8991
8992 // TODO: COOR and DBEND don't seem to be used?
8993
8994 // Initialise the forces.
8995 f.fill(0.);
8996
8997 const double xw = wire.x + bend[0];
8998 const double yw = wire.y + bend[1];
8999 if (m_useElectrostaticForce) {
9000 // Electrostatic force.
9001 if (xMap.empty() || yMap.empty() || fxMap.empty() || fyMap.empty()) {
9002 return false;
9003 }
9004 // In case extrapolation is not permitted, check range.
9005 if (!m_extrapolateForces) {
9006 if ((xMap.front() - xw) * (xw - xMap.back()) < 0 ||
9007 (yMap.front() - yw) * (yw - yMap.back()) < 0) {
9008 return false;
9009 }
9010 }
9011 // Interpolation order.
9012 constexpr int order = 2;
9013 // Electrostatic force: interpolate the table, first along the y-lines.
9014 const unsigned int nX = xMap.size();
9015 const unsigned int nY = yMap.size();
9016 std::vector<double> xaux(nX, 0.);
9017 std::vector<double> yaux(nX, 0.);
9018 for (unsigned int i = 0; i < nX; ++i) {
9019 xaux[i] = Numerics::Divdif(fxMap[i], yMap, nY, yw, order);
9020 yaux[i] = Numerics::Divdif(fyMap[i], yMap, nY, yw, order);
9021 }
9022 // Then along the x-lines.
9023 f[0] += Numerics::Divdif(xaux, xMap, nX, xw, order);
9024 f[1] += Numerics::Divdif(yaux, xMap, nX, xw, order);
9025 }
9026 // Add the gravity term.
9027 if (m_useGravitationalForce) {
9028 // Mass per unit length [kg / cm].
9029 const double m = 1.e-3 * wire.density * Pi * wire.r * wire.r;
9030 f[0] -= m_down[0] * m * GravitationalAcceleration;
9031 f[1] -= m_down[1] * m * GravitationalAcceleration;
9032 }
9033 // Divide by the stretching force.
9034 const double s = 1000. / (GravitationalAcceleration * wire.tension);
9035 f[0] *= s;
9036 f[1] *= s;
9037 return true;
9038}
9039
9040// Subroutine subprograms RRKNYS and DRKNYS advance the solution of the system
9041// of n >= 1 second-order differential equations
9042// by a single step of length h in the independent variable x.
9043bool ComponentAnalyticField::StepRKN(
9044 const Wire& wire, const double h, double& x, std::array<double, 2>& y,
9045 std::array<double, 2>& yp, const std::vector<double>& xMap,
9046 const std::vector<double>& yMap,
9047 const std::vector<std::vector<double> >& fxMap,
9048 const std::vector<std::vector<double> >& fyMap) const {
9049 constexpr double r2 = 1. / 2.;
9050 constexpr double r6 = 1. / 6.;
9051 constexpr double r8 = 1. / 8.;
9052 if (h == 0) return true;
9053 const double h2 = r2 * h;
9054 const double h6 = r6 * h;
9055 const double hh2 = h * h2;
9056 const double hh6 = h * h6;
9057 const double hh8 = r8 * h * h;
9058
9059 constexpr unsigned int n = 2;
9060 std::array<std::array<double, n>, 6> w;
9061 if (!GetForceRatio(wire, x, y, yp, w[0], xMap, yMap, fxMap, fyMap)) {
9062 return false;
9063 }
9064 for (unsigned int j = 0; j < n; ++j) {
9065 w[3][j] = y[j] + h2 * yp[j];
9066 w[4][j] = w[3][j] + hh8 * w[0][j];
9067 w[5][j] = yp[j] + h2 * w[0][j];
9068 }
9069 const double xh2 = x + h2;
9070 if (!GetForceRatio(wire, xh2, w[4], w[5], w[1], xMap, yMap, fxMap, fyMap)) {
9071 return false;
9072 }
9073 for (unsigned int j = 0; j < n; ++j) {
9074 w[5][j] = yp[j] + h2 * w[1][j];
9075 w[0][j] = w[0][j] + w[1][j];
9076 w[1][j] = w[0][j] + w[1][j];
9077 }
9078 if (!GetForceRatio(wire, xh2, w[4], w[5], w[2], xMap, yMap, fxMap, fyMap)) {
9079 return false;
9080 }
9081 for (unsigned int j = 0; j < n; ++j) {
9082 w[3][j] = w[3][j] + h2 * yp[j];
9083 w[4][j] = w[3][j] + hh2 * w[2][j];
9084 w[5][j] = yp[j] + h * w[2][j];
9085 w[0][j] = w[0][j] + w[2][j];
9086 w[1][j] = w[1][j] + 2 * w[2][j];
9087 }
9088 const double xh = x + h;
9089 if (!GetForceRatio(wire, xh, w[4], w[5], w[2], xMap, yMap, fxMap, fyMap)) {
9090 return false;
9091 }
9092 for (unsigned int j = 0; j < n; ++j) {
9093 y[j] = w[3][j] + hh6 * w[0][j];
9094 yp[j] += h6 * (w[1][j] + w[2][j]);
9095 }
9096 x = xh;
9097 return true;
9098}
9099
9100bool ComponentAnalyticField::FindZeroes(
9101 const Wire& wire, const double h, std::vector<double>& x,
9102 const std::vector<double>& xMap, const std::vector<double>& yMap,
9103 const std::vector<std::vector<double> >& fxMap,
9104 const std::vector<std::vector<double> >& fyMap) const {
9105 //-----------------------------------------------------------------------
9106 // OPTZRO - Tries to find zeroes of a set of functions F. Uses the
9107 // Broyden rank-1 update variant of an n-dimensional Newton-
9108 // Raphson zero search in most steps, except every 5th step
9109 // and whenever the step length update becomes less than 0.5,
9110 // when a new derivative is computed.
9111 //-----------------------------------------------------------------------
9112
9113 if (x.empty()) {
9114 std::cerr << m_className << "::FindZeroes: Empty vector.\n";
9115 return false;
9116 }
9117 const unsigned int n = x.size();
9118 // Initial deviation.
9119 std::vector<double> fold(n, 0.);
9120 if (!Trace(wire, h, x, fold, xMap, yMap, fxMap, fyMap)) {
9121 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n "
9122 << "Initial position outside scanning area.\n";
9123 return false;
9124 }
9125 double fnorml =
9126 std::inner_product(fold.begin(), fold.end(), fold.begin(), 0.);
9127 // Debugging output for initial situation.
9128 constexpr unsigned int nbsmax = 10;
9129 constexpr unsigned int nitmax = 20;
9130 constexpr double eps = 1.e-4;
9131 constexpr double epsx = 1.e-4;
9132 constexpr double epsf = 1.e-4;
9133 if (m_debug) {
9134 std::cout << m_className << "::FindZeroes: Start of zero search.\n"
9135 << " Number of parameters: " << n << "\n"
9136 << " Maximum bisections: " << nbsmax << "\n"
9137 << " Maximum iterations: " << nitmax << "\n"
9138 << " Epsilon differentiation: " << eps << "\n"
9139 << " Required location change: " << epsx << "\n"
9140 << " Required function norm: " << epsf << "\n"
9141 << " Initial function norm: " << sqrt(fnorml) << "\n"
9142 << " Parameter Value Function\n";
9143 for (unsigned int i = 0; i < n; ++i) {
9144 std::printf(" %9d %12.5e %12.5e\n", i, x[i], fold[i]);
9145 }
9146 }
9147 // Derivative matrix.
9148 std::vector<std::vector<double> > b(n, std::vector<double>(n, 0.));
9149 std::vector<std::vector<double> > bb(n, std::vector<double>(n, 0.));
9150 // Flag whether the matrix needs to be recomputed.
9151 bool updateMatrix = true;
9152 // Count function calls.
9153 unsigned int nCalls = 0;
9154 bool converged = false;
9155 for (unsigned int iter = 0; iter < nitmax; ++iter) {
9156 // If needed, (re-)compute the derivative matrix.
9157 if (updateMatrix) {
9158 std::vector<double> f1(n, 0.);
9159 std::vector<double> f2(n, 0.);
9160 for (unsigned int i = 0; i < n; ++i) {
9161 const double epsdif = eps * (1. + std::abs(x[i]));
9162 x[i] += 0.5 * epsdif;
9163 if (!Trace(wire, h, x, f1, xMap, yMap, fxMap, fyMap)) {
9164 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n "
9165 << "Differential matrix requires a point "
9166 << "outside scanning area.\n";
9167 return false;
9168 }
9169 x[i] -= epsdif;
9170 if (!Trace(wire, h, x, f2, xMap, yMap, fxMap, fyMap)) {
9171 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n "
9172 << "Differential matrix requires a point "
9173 << "outside scanning area.\n";
9174 return false;
9175 }
9176 x[i] += 0.5 * epsdif;
9177 for (unsigned int j = 0; j < n; ++j) {
9178 b[j][i] = (f1[j] - f2[j]) / epsdif;
9179 }
9180 }
9181 nCalls += 2 * n;
9182 updateMatrix = false;
9183 }
9184 if (m_debug) {
9185 std::cout << " Start of iteration " << iter << "\n";
9186 for (unsigned int i = 0; i < m_nShots; ++i) {
9187 const unsigned int k = 4 * i + 2;
9188 std::printf(" x = %12.5e, y = %12.5e\n", x[k], x[k + 1]);
9189 }
9190 }
9191 // Find the correction vector to 0th order.
9192 std::vector<double> dx = fold;
9193 bb = b;
9194 if (Numerics::CERNLIB::deqn(n, bb, dx) != 0) {
9195 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n"
9196 << " Solving the update equation failed.\n";
9197 break;
9198 }
9199 if (m_debug) {
9200 for (unsigned int i = 0; i < m_nShots; ++i) {
9201 const unsigned int k = 4 * i + 2;
9202 std::printf(" dx = %12.5e, dy = %12.5e\n", dx[k], dx[k + 1]);
9203 }
9204 }
9205 // Scale the correction vector to improve FNORM, AUX3: f.
9206 double scale = 1.;
9207 double fnorm = 2 * fnorml;
9208 std::vector<double> xnew(n, 0.);
9209 std::vector<double> fnew(n, 0.);
9210 for (unsigned int kbs = 0; kbs < nbsmax; ++kbs) {
9211 for (unsigned int i = 0; i < n; ++i) {
9212 xnew[i] = x[i] - scale * dx[i];
9213 }
9214 if (!Trace(wire, h, xnew, fnew, xMap, yMap, fxMap, fyMap)) {
9215 std::cerr
9216 << m_className << "::FindZeroes: Zero search stopped.\n "
9217 << "Step update leads to a point outside the scanning area.\n";
9218 return false;
9219 }
9220 ++nCalls;
9221 fnorm = std::inner_product(fnew.begin(), fnew.end(), fnew.begin(), 0.);
9222 if (fnorm <= fnorml) {
9223 if (m_debug) std::cout << " Scaling factor: " << scale << "\n";
9224 break;
9225 }
9226 scale *= 0.5;
9227 }
9228 if (fnorm > fnorml) {
9229 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n "
9230 << "Bisection search for scaling factor did not converge.\n";
9231 break;
9232 }
9233 // Update the estimate.
9234 std::vector<double> df(n, 0.);
9235 for (unsigned int i = 0; i < n; ++i) {
9236 dx[i] = xnew[i] - x[i];
9237 x[i] = xnew[i];
9238 df[i] = fnew[i] - fold[i];
9239 fold[i] = fnew[i];
9240 }
9241 double xnorm = std::inner_product(x.begin(), x.end(), x.begin(), 0.);
9242 double dxnorm = std::inner_product(dx.begin(), dx.end(), dx.begin(), 0.);
9243 double dfnorm = std::inner_product(df.begin(), df.end(), df.begin(), 0.);
9244 // Debugging output to show current status.
9245 if (m_debug) {
9246 std::cout << " After this iteration...\n";
9247 std::printf(" Norm and change of position: %12.5e %12.5e\n",
9248 sqrt(xnorm), sqrt(dxnorm));
9249 std::printf(" Norm and change of function: %12.5e %12.5e\n",
9250 sqrt(fnorm), sqrt(dfnorm));
9251 }
9252 // See whether convergence has been achieved.
9253 if (sqrt(dxnorm) < epsx * sqrt(xnorm)) {
9254 if (m_debug) {
9255 std::cout << " Positional convergence criterion is satisfied.\n";
9256 }
9257 converged = true;
9258 break;
9259 } else if (sqrt(fnorm) < epsf) {
9260 if (m_debug) {
9261 std::cout << " Function value convergence criterion is satisfied.\n";
9262 }
9263 converged = true;
9264 break;
9265 }
9266 // Update the difference.
9267 fnorml = fnorm;
9268 if (scale > 0.4 && iter != 5 * (iter / 5)) {
9269 // If the scaling factor is small, then update (rank-1 Broyden).
9270 if (m_debug) std::cout << " Performing a Broyden rank-1 update.\n";
9271 // Compute the "df - B dx" term.
9272 std::vector<double> corr(n, 0.);
9273 for (unsigned int i = 0; i < n; ++i) {
9274 corr[i] = df[i];
9275 for (unsigned int j = 0; j < n; ++j) {
9276 corr[i] -= b[i][j] * dx[j];
9277 }
9278 }
9279 // Update the matrix.
9280 for (unsigned int i = 0; i < n; ++i) {
9281 for (unsigned int j = 0; j < n; ++j) {
9282 b[i][j] += corr[i] * dx[j] / dxnorm;
9283 }
9284 }
9285 } else {
9286 // Otherwise, recompute the differential.
9287 if (m_debug) std::cout << " Recomputing the covariance matrix.\n";
9288 updateMatrix = true;
9289 }
9290 }
9291 if (!converged) {
9292 std::cerr << m_className << "::FindZeroes: Search did not converge.\n";
9293 }
9294 if (m_debug) {
9295 std::vector<double> f(n, 0.);
9296 Trace(wire, h, x, f, xMap, yMap, fxMap, fyMap);
9297 ++nCalls;
9298 std::cout << " Final values:\n"
9299 << " Parameter Value Function\n";
9300 for (unsigned int i = 0; i < n; ++i) {
9301 std::printf(" %9d %12.5e %12.5e\n", i, x[i], f[i]);
9302 }
9303 std::cout << " Total number of function calls: " << nCalls << "\n";
9304 }
9305 return converged;
9306}
9307
9308bool ComponentAnalyticField::Trace(
9309 const Wire& wire, const double h, const std::vector<double>& xx,
9310 std::vector<double>& delta, const std::vector<double>& xMap,
9311 const std::vector<double>& yMap,
9312 const std::vector<std::vector<double> >& fxMap,
9313 const std::vector<std::vector<double> >& fyMap) const {
9314 //-----------------------------------------------------------------------
9315 // OPTSHT - Auxiliary routine for the wire sag routines which computes
9316 // for a given set of positions and derivatives the next set
9317 // which is used by OPTZRO to match the sections. Uses a
9318 // 2nd order Runge-Kutta-Nystrom integration routine (D203).
9319 //-----------------------------------------------------------------------
9320
9321 delta.assign(xx.size(), 0.);
9322 // For the starting set in XX, compute the next round.
9323 double z = -0.5 * wire.u;
9324 std::array<double, 2> xst = {0., 0.};
9325 std::array<double, 2> dxst = {xx[0], xx[1]};
9326 for (unsigned int i = 0; i <= m_nShots; ++i) {
9327 const unsigned int k = 4 * i;
9328 // Set the starting value and starting derivative.
9329 if (i > 0) {
9330 xst = {xx[k - 2], xx[k - 1]};
9331 dxst = {xx[k], xx[k + 1]};
9332 }
9333 // Compute the end value and end derivative.
9334 for (unsigned int j = 0; j < m_nSteps; ++j) {
9335 if (!StepRKN(wire, h, z, xst, dxst, xMap, yMap, fxMap, fyMap)) {
9336 return false;
9337 }
9338 }
9339 // Store the differences as function value.
9340 if (i < m_nShots) {
9341 delta[k] = xst[0] - xx[k + 2];
9342 delta[k + 1] = xst[1] - xx[k + 3];
9343 delta[k + 2] = dxst[0] - xx[k + 4];
9344 delta[k + 3] = dxst[1] - xx[k + 5];
9345 } else {
9346 delta[k] = xst[0];
9347 delta[k + 1] = xst[1];
9348 }
9349 }
9350 return true;
9351}
9352
9353bool ComponentAnalyticField::SetupDipoleTerms() {
9354
9355 //-----------------------------------------------------------------------
9356 // SETDIP - Subroutine computing coefficients for the dipole terms in
9357 // cells without periodicities.
9358 //-----------------------------------------------------------------------
9359
9360 // Parameters.
9361 constexpr double epsp = 1.e-3;
9362 constexpr double epsa = 1.e-3;
9363
9364 const unsigned int nWires = m_w.size();
9365 // Initial dipole moments.
9366 std::vector<double> phi2(nWires, 0.);
9367 m_cosph2.assign(nWires, 1.);
9368 m_sinph2.assign(nWires, 0.);
9369 m_amp2.assign(nWires, 0.);
9370
9371 // Iterate until the dipole terms have converged.
9372 std::vector<double> phit2(nWires, 0.);
9373 std::vector<double> ampt2(nWires, 0.);
9374 constexpr unsigned int nMaxIter = 10;
9375 for (unsigned int iter = 0; iter < nMaxIter; ++iter) {
9376 if (m_debug) {
9377 std::cout << "Iteration " << iter << "/" << nMaxIter << "\n"
9378 << " Wire correction angle [deg] amplitude\n";
9379 }
9380 // Loop over the wires.
9381 for (unsigned int iw = 0; iw < nWires; ++iw) {
9382 const double xw = m_w[iw].x;
9383 const double yw = m_w[iw].y;
9384 const double rw = m_w[iw].r;
9385 // Set the radius of the wire to 0.
9386 m_w[iw].r = 0.;
9387 // Loop around the wire.
9388 constexpr unsigned int nAngles = 20;
9389 std::vector<double> angle(nAngles, 0.);
9390 std::vector<double> volt(nAngles, 0.);
9391 constexpr double rmult = 1.;
9392 const double r = rw * rmult;
9393 int status = 0;
9394 for (unsigned int i = 0; i < nAngles; ++i) {
9395 angle[i] = TwoPi * (i + 1.) / nAngles;
9396 const double x = xw + r * cos(angle[i]);
9397 const double y = yw + r * sin(angle[i]);
9398 double ex = 0., ey = 0., ez = 0.;
9399 status = Field(x, y, 0., ex, ey, ez, volt[i], true);
9400 if (status != 0) {
9401 std::cerr << "Unexpected status code; computation stopped.\n";
9402 break;
9403 }
9404 volt[i] -= m_w[iw].v;
9405 }
9406 // Restore wire radius.
9407 m_w[iw].r = rw;
9408 if (status != 0) continue;
9409 // Determine the dipole term.
9410 double ampdip = 0., phidip = 0.;
9411 FitDipoleMoment(angle, volt, ampdip, phidip, false);
9412 // Store the parameters, removing the radial dependence.
9413 phit2[iw] = phidip;
9414 ampt2[iw] = ampdip * r;
9415 if (m_debug) {
9416 std::printf(" %3d %10.3f %12.5e\n",
9417 iw, RadToDegree * phit2[iw], ampt2[iw]);
9418 }
9419 }
9420 // Transfer to the arrays where the dipole moments have impact
9421 bool reiter = false;
9422 if (m_debug) std::cout << " Wire new angle [deg] amplitude\n";
9423 for (unsigned int iw = 0; iw < nWires; ++iw) {
9424 // See whether we need further refinements.
9425 bool converged = true;
9426 if (std::abs(phi2[iw]) > epsp * (1. + std::abs(phi2[iw])) ||
9427 std::abs(m_amp2[iw]) > epsa * (1. + std::abs(m_amp2[iw]))) {
9428 reiter = true;
9429 converged = false;
9430 }
9431 // Add the new term to the existing one.
9432 const double s0 = m_sinph2[iw] * m_amp2[iw] + sin(phit2[iw]) * ampt2[iw];
9433 const double c0 = m_cosph2[iw] * m_amp2[iw] + cos(phit2[iw]) * ampt2[iw];
9434 phi2[iw] = atan2(s0, c0);
9435 m_cosph2[iw] = cos(phi2[iw]);
9436 m_sinph2[iw] = sin(phi2[iw]);
9437 const double s1 = m_sinph2[iw] * m_amp2[iw] + sin(phit2[iw]) * ampt2[iw];
9438 const double c1 = m_cosph2[iw] * m_amp2[iw] + cos(phit2[iw]) * ampt2[iw];
9439 m_amp2[iw] = sqrt(s1 * s1 + c1 * c1);
9440 if (m_debug) {
9441 std::printf(" %3d %10.3f %12.5e %s\n",
9442 iw, RadToDegree * phi2[iw], m_amp2[iw],
9443 converged ? "CONVERGED" : "");
9444 }
9445 }
9446 // Next iteration?
9447 if (!reiter) return true;
9448 }
9449 // Maximum number of iterations exceeded.
9450 std::cerr << m_className << "::SetupDipoleTerms:\n"
9451 << " Maximum number of dipole iterations exceeded "
9452 << "without convergence; abandoned.\n";
9453 return false;
9454}
9455
9456void ComponentAnalyticField::DipoleFieldA00(
9457 const double xpos, const double ypos,
9458 double& ex, double& ey, double& volt, const bool opt) const {
9459
9460 //-----------------------------------------------------------------------
9461 // EMCA00 - Subroutine computing dipole terms in a field generated by
9462 // wires without periodicities.
9463 //-----------------------------------------------------------------------
9464
9465 // Initialise the potential and the electric field.
9466 ex = 0.;
9467 ey = 0.;
9468 volt = 0.;
9469 // Loop over all wires.
9470 double v = 0.;
9471 for (unsigned int i = 0; i < m_nWires; ++i) {
9472 const auto& wire = m_w[i];
9473 const double dx = xpos - wire.x;
9474 const double dy = ypos - wire.y;
9475 const double dxm = xpos + wire.x - 2. * m_coplax;
9476 const double dym = ypos + wire.y - 2. * m_coplay;
9477 // Calculate the field in case there are no planes.
9478 const double a = dx * dx - dy * dy;
9479 const double b = 2 * dx * dy;
9480 const double d2 = dx * dx + dy * dy;
9481 const double d4 = d2 * d2;
9482 double fx = (a * m_cosph2[i] + b * m_sinph2[i]) / d4;
9483 double fy = (b * m_cosph2[i] - a * m_sinph2[i]) / d4;
9484 if (opt) v = (dx * m_cosph2[i] + dy * m_sinph2[i]) / d2;
9485 // Take care of a plane at constant x.
9486 if (m_ynplax) {
9487 const double am = dxm * dxm - dy * dy;
9488 const double bm = 2 * dxm * dy;
9489 const double d2m = dxm * dxm + dy * dy;
9490 const double d4m = d2m * d2m;
9491 fx -= (am * m_cosph2[i] + bm * m_sinph2[i]) / d4m;
9492 fy -= (bm * m_cosph2[i] - am * m_sinph2[i]) / d4m;
9493 if (opt) v -= (dxm * m_cosph2[i] + dy * m_sinph2[i]) / d2m;
9494 }
9495 // Take care of a plane at constant y.
9496 if (m_ynplay) {
9497 const double am = dx * dx - dym * dym;
9498 const double bm = 2 * dx * dym;
9499 const double d2m = dx * dx + dym * dym;
9500 const double d4m = d2m * d2m;
9501 fx -= (am * m_cosph2[i] + bm * m_sinph2[i]) / d4m;
9502 fy -= (bm * m_cosph2[i] - am * m_sinph2[i]) / d4m;
9503 if (opt) v -= (dx * m_cosph2[i] + dym * m_sinph2[i]) / d2m;
9504 }
9505 // Take care of pairs of planes.
9506 if (m_ynplax && m_ynplay) {
9507 const double am = dxm * dxm - dym * dym;
9508 const double bm = 2 * dxm * dym;
9509 const double d2m = dxm * dxm + dym * dym;
9510 const double d4m = d2m * d2m;
9511 fx += (am * m_cosph2[i] + bm * m_sinph2[i]) / d4m;
9512 fy += (bm * m_cosph2[i] - am * m_sinph2[i]) / d4m;
9513 if (opt) v += (dxm * m_cosph2[i] + dym * m_sinph2[i]) / d2m;
9514 }
9515 // Normalise.
9516 volt -= m_amp2[i] * v;
9517 ex -= m_amp2[i] * fx;
9518 ey -= m_amp2[i] * fy;
9519 }
9520}
9521
9522void ComponentAnalyticField::DipoleFieldB1X(
9523 const double xpos, const double ypos,
9524 double& ex, double& ey, double& volt, const bool opt) const {
9525
9526 //-----------------------------------------------------------------------
9527 // EMCB1X - Subroutine computing dipole terms in a field generated by
9528 // a row of wires along the x-axis.
9529 //-----------------------------------------------------------------------
9530
9531 // Initialise the potential and the electric field.
9532 ex = 0.;
9533 ey = 0.;
9534 volt = 0.;
9535 // Shorthand.
9536 const double tx = Pi / m_sx;
9537 const double tx2 = tx * tx;
9538 // Loop over all wires.
9539 double v = 0.;
9540 for (unsigned int i = 0; i < m_nWires; ++i) {
9541 const auto& wire = m_w[i];
9542 // Calculate the field in case there are no planes.
9543 const double dx = tx * (xpos - wire.x);
9544 const double dy = tx * (ypos - wire.y);
9545 const double a = 1 - cos(2 * dx) * cosh(2 * dy);
9546 const double b = sin(2 * dx) * sinh(2 * dy);
9547 const double sx = sin(dx);
9548 const double shy = sinh(dy);
9549 const double d2 = sx * sx + shy * shy;
9550 const double d4 = d2 * d2;
9551 double fx = ( m_cosph2[i] * a + m_sinph2[i] * b) / d4;
9552 double fy = (-m_sinph2[i] * a + m_cosph2[i] * b) / d4;
9553 if (opt) {
9554 v = (m_cosph2[i] * sin(2 * dx) + m_sinph2[i] * sinh(2 * dy)) / d2;
9555 }
9556 // Take care of a plane at constant y.
9557 if (m_ynplay) {
9558 const double dym = tx * (ypos + wire.y - 2. * m_coplay);
9559 const double am = 1 - cos(2 * dx) * cosh(2 * dym);
9560 const double bm = sin(2 * dx) * sinh(2 * dym);
9561 const double shym = sinh(dym);
9562 const double d2m = sx * sx + shym * shym;
9563 const double d4m = d2m * d2m;
9564 fx -= (m_cosph2[i] * am - m_sinph2[i] * bm) / d4m;
9565 fy -= (m_sinph2[i] * am + m_cosph2[i] * bm) / d4m;
9566 if (opt) {
9567 v -= (m_cosph2[i] * sin(2 * dx) - m_sinph2[i] * sinh(2 * dym)) / d2m;
9568 }
9569 }
9570 // Calculate the electric field and the potential.
9571 ex -= m_amp2[i] * 0.5 * tx2 * fx;
9572 ey -= m_amp2[i] * 0.5 * tx2 * fy;
9573 if (opt) volt -= 0.5 * tx * m_amp2[i] * v;
9574 }
9575}
9576
9577void ComponentAnalyticField::DipoleFieldB1Y(
9578 const double xpos, const double ypos,
9579 double& ex, double& ey, double& volt, const bool opt) const {
9580
9581 //-----------------------------------------------------------------------
9582 // EMCB1Y - Subroutine computing dipole terms in a field generated by
9583 // a row of wires along the y-axis.
9584 //-----------------------------------------------------------------------
9585
9586 // Initialise the potential and the electric field.
9587 ex = 0.;
9588 ey = 0.;
9589 volt = 0.;
9590 // Shorthand.
9591 const double ty = Pi / m_sy;
9592 const double ty2 = ty * ty;
9593 // Loop over all wires.
9594 double v = 0.;
9595 for (unsigned int i = 0; i < m_nWires; ++i) {
9596 const auto& wire = m_w[i];
9597 // Calculate the field in case there are no planes.
9598 const double dx = ty * (xpos - wire.x);
9599 const double dy = ty * (ypos - wire.y);
9600 const double a = 1 - cosh(2 * dx) * cos(2 * dy);
9601 const double b = sinh(2 * dx) * sin(2 * dy);
9602 const double shx = sinh(dx);
9603 const double sy = sin(dy);
9604 const double d2 = shx * shx + sy * sy;
9605 const double d4 = d2 * d2;
9606 double fx = (-m_cosph2[i] * a + m_sinph2[i] * b) / d4;
9607 double fy = ( m_sinph2[i] * a + m_cosph2[i] * b) / d4;
9608 if (opt) {
9609 v = (m_cosph2[i] * sinh(2 * dx) + m_sinph2[i] * sin(2 * dy)) / d2;
9610 }
9611 // Take care of a plane at constant x.
9612 if (m_ynplax) {
9613 const double dxm = ty * (xpos + wire.x - 2. * m_coplax);
9614 const double am = 1 - cosh(2 * dxm) * cos(2 * dy);
9615 const double bm = sinh(2 * dxm) * sin(2 * dy);
9616 const double shxm = sinh(dxm);
9617 const double d2m = shxm * shxm + sy * sy;
9618 const double d4m = d2m * d2m;
9619 fx -= (m_cosph2[i] * am + m_sinph2[i] * bm) / d4m;
9620 fy -= (m_sinph2[i] * am - m_cosph2[i] * bm) / d4m;
9621 if (opt) {
9622 v -= (-m_cosph2[i] * sinh(2 * dxm) + m_sinph2[i] * sin(2 * dy)) / d2m;
9623 }
9624 }
9625 // Calculate the electric field and the potential.
9626 ex -= m_amp2[i] * 0.5 * ty2 * fx;
9627 ey -= m_amp2[i] * 0.5 * ty2 * fy;
9628 if (opt) volt -= 0.5 * ty * m_amp2[i] * v;
9629 }
9630}
9631
9632void ComponentAnalyticField::DipoleFieldB2X(
9633 const double xpos, const double ypos,
9634 double& ex, double& ey, double& volt, const bool opt) const {
9635
9636 //-----------------------------------------------------------------------
9637 // EMCB2X - Routine calculating the dipole terms for a charge between
9638 // parallel conducting planes.
9639 //-----------------------------------------------------------------------
9640
9641 // Initialise the potential and the electric field.
9642 ex = 0.;
9643 ey = 0.;
9644 volt = 0.;
9645 // Shorthand.
9646 const double tx = HalfPi / m_sx;
9647 const double tx2 = tx * tx;
9648 // Loop over all wires.
9649 double v = 0.;
9650 for (unsigned int i = 0; i < m_nWires; ++i) {
9651 const auto& wire = m_w[i];
9652 const double dx = tx * (xpos - wire.x);
9653 const double dy = tx * (ypos - wire.y);
9654 // Calculate the field in case there are no equipotential planes.
9655 const double a = 1 - cos(2 * dx) * cosh(2 * dy);
9656 const double b = sin(2 * dx) * sinh(2 * dy);
9657 const double sx = sin(dx);
9658 const double shy = sinh(dy);
9659 const double d2 = sx * sx + shy * shy;
9660 const double d4 = d2 * d2;
9661 double fx = ( m_cosph2[i] * a + m_sinph2[i] * b) / d4;
9662 double fy = (-m_sinph2[i] * a + m_cosph2[i] * b) / d4;
9663 const double dxn = tx * (xpos + wire.x - 2. * m_coplax);
9664 const double an = 1 - cos(2 * dxn) * cosh(2 * dy);
9665 const double bn = sin(2 * dxn) * sinh(2 * dy);
9666 const double sxn = sin(dxn);
9667 const double d2n = sxn * sxn + shy * shy;
9668 const double d4n = d2n * d2n;
9669 fx += (m_cosph2[i] * an - m_sinph2[i] * bn) / d4n;
9670 fy += (m_sinph2[i] * an + m_cosph2[i] * bn) / d4n;
9671 if (opt) {
9672 v = -sin(dx + dxn) * (-2 * m_cosph2[i] * (
9673 (1 + shy * shy) * sx * sxn + cos(dx) * cos(dxn) * shy * shy) +
9674 m_sinph2[i] * m_b2sin[i] * sinh(2 * dy)) / (d2 * d2n);
9675 }
9676 // Take care of planes at constant y.
9677 if (m_ynplay) {
9678 const double dym = tx * (ypos + wire.y - 2. * m_coplay);
9679 const double am = 1 - cos(2 * dx) * cosh(2 * dym);
9680 const double bm = sin(2 * dx) * sinh(2 * dym);
9681 const double shym = sinh(dym);
9682 const double d2m = sx * sx + shym * shym;
9683 const double d4m = d2m * d2m;
9684 fx -= (m_cosph2[i] * am - m_sinph2[i] * bm) / d4m;
9685 fy -= (m_sinph2[i] * am + m_cosph2[i] * bm) / d4m;
9686 const double amn = 1 - cos(2 * dxn) * cosh(2 * dym);
9687 const double bmn = sin(2 * dxn) * sinh(2 * dym);
9688 const double d2mn = sxn * sxn + shym * shym;
9689 const double d4mn = d2mn * d2mn;
9690 fx -= ( m_cosph2[i] * amn + m_sinph2[i] * bmn) / d4mn;
9691 fy -= (-m_sinph2[i] * amn + m_cosph2[i] * bmn) / d4mn;
9692 if (opt) {
9693 v += sin(dx + dxn) * (-2 * m_cosph2[i] * (
9694 (1 + shym * shym) * sx * sxn + cos(dx) * cos(dxn) * shym * shym) -
9695 m_sinph2[i] * m_b2sin[i] * sinh(2 * dym)) / (d2m * d2mn);
9696 }
9697 }
9698 // Calculate the electric field and the potential.
9699 ex -= m_amp2[i] * 0.5 * tx2 * fx;
9700 ey -= m_amp2[i] * 0.5 * tx2 * fy;
9701 if (opt) volt -= 0.5 * tx * m_amp2[i] * v;
9702 }
9703}
9704
9705void ComponentAnalyticField::DipoleFieldB2Y(
9706 const double xpos, const double ypos,
9707 double& ex, double& ey, double& volt, const bool opt) const {
9708
9709 //-----------------------------------------------------------------------
9710 // EMCB2Y - Routine calculating the dipole terms for a charge between
9711 // parallel conducting planes.
9712 //-----------------------------------------------------------------------
9713
9714 // Initialise the potential and the electric field.
9715 ex = 0.;
9716 ey = 0.;
9717 volt = 0.;
9718 // Shorthand.
9719 const double ty = HalfPi / m_sy;
9720 const double ty2 = ty * ty;
9721 // Loop over all wires.
9722 double v = 0.;
9723 for (unsigned int i = 0; i < m_nWires; ++i) {
9724 const auto& wire = m_w[i];
9725 const double dx = ty * (xpos - wire.x);
9726 const double dy = ty * (ypos - wire.y);
9727 // Calculate the field in case there are no equipotential planes.
9728 const double a = 1 - cosh(2 * dx) * cos(2 * dy);
9729 const double b = sinh(2 * dx) * sin(2 * dy);
9730 const double shx = sinh(dx);
9731 const double sy = sin(dy);
9732 const double d2 = shx * shx + sy * sy;
9733 const double d4 = d2 * d2;
9734 double fx = (-m_cosph2[i] * a + m_sinph2[i] * b) / d4;
9735 double fy = ( m_sinph2[i] * a + m_cosph2[i] * b) / d4;
9736 const double dyn = ty * (ypos + wire.y - 2. * m_coplay);
9737 const double an = 1 - cosh(2 * dx) * sin(2 * dyn);
9738 const double bn = sinh(2 * dx) * sin(2 * dyn);
9739 const double syn = sin(dyn);
9740 const double d2n = shx * shx + syn * syn;
9741 const double d4n = d2n * d2n;
9742 fx += (m_cosph2[i] * an + m_sinph2[i] * bn) / d4n;
9743 fy += (m_sinph2[i] * an - m_cosph2[i] * bn) / d4n;
9744 if (opt) {
9745 // TODO: check!
9746 v = sin(dy + dyn) * (
9747 m_sinph2[i] * (-cos(dy + dyn) + cos(dy - dyn) * cosh(2 * dx)) -
9748 m_cosph2[i] * sinh(2 * dx) * (cos(dyn) * sy + cos(dy) * syn)) /
9749 (d2 * d2n);
9750 }
9751 // Take care of planes at constant x.
9752 if (m_ynplax) {
9753 const double dxm = ty * (xpos + wire.x - 2. * m_coplax);
9754 const double am = 1 - cosh(2 * dxm) * cos(2 * dy);
9755 const double bm = sinh(2 * dxm) * sin(2 * dy);
9756 const double shxm = sinh(dxm);
9757 const double d2m = shxm * shxm + sy * sy;
9758 const double d4m = d2m * d2m;
9759 fx -= (m_cosph2[i] * am + m_sinph2[i] * bm) / d4m;
9760 fy -= (m_sinph2[i] * am - m_cosph2[i] * bm) / d4m;
9761 const double amn = 1 - cosh(2 * dxm) * cos(2 * dyn);
9762 const double bmn = sinh(2 * dxm) * sin(2 * dyn);
9763 const double d2mn = shxm * shxm + syn * syn;
9764 const double d4mn = d2mn * d2mn;
9765 fx -= (-m_cosph2[i] * amn + m_sinph2[i] * bmn) / d4mn;
9766 fy -= ( m_sinph2[i] * amn + m_cosph2[i] * bmn) / d4mn;
9767 if (opt) {
9768 // TODO: check!
9769 v -= sin(dy + dyn) * (
9770 m_sinph2[i] * (-cos(dy + dyn) + cos(dy - dyn) * cosh(2 * dxm)) +
9771 m_cosph2[i] * sinh(2 * dxm) * (cos(dyn) * sy - cos(dy) * syn)) /
9772 (d2m * d2mn);
9773
9774 }
9775 }
9776 // Calculate the electric field and the potential.
9777 ex -= m_amp2[i] * 0.5 * ty2 * fx;
9778 ey -= m_amp2[i] * 0.5 * ty2 * fy;
9779 if (opt) volt -= 0.5 * ty * m_amp2[i] * v;
9780 }
9781}
9782
9784 const unsigned int nPoles, const bool print, const bool plot,
9785 const double rmult, const double eps,
9786 const unsigned int nMaxIter) {
9787
9788 //-----------------------------------------------------------------------
9789 // EFMWIR - Computes the dipole moment of a given wire.
9790 //-----------------------------------------------------------------------
9791 if (!m_cellset && !Prepare()) return false;
9792 // Check input parameters.
9793 if (iw >= m_nWires) {
9794 std::cerr << m_className << "::MultipoleMoments:\n"
9795 << " Wire index out of range.\n";
9796 return false;
9797 }
9798 if (eps <= 0.) {
9799 std::cerr << m_className << "::MultipoleMoments:\n"
9800 << " Epsilon must be positive.\n";
9801 return false;
9802 }
9803 if (nPoles == 0) {
9804 std::cerr << m_className << "::MultipoleMoments:\n"
9805 << " Multipole order out of range.\n";
9806 return false;
9807 }
9808 if (rmult <= 0.) {
9809 std::cerr << m_className << "::MultipoleMoments:\n"
9810 << " Radius multiplication factor out of range.\n";
9811 return false;
9812 }
9813
9814 const double xw = m_w[iw].x;
9815 const double yw = m_w[iw].y;
9816 // Set the radius of the wire to 0.
9817 const double rw = m_w[iw].r;
9818 m_w[iw].r = 0.;
9819
9820 // Loop around the wire.
9821 constexpr unsigned int nPoints = 20000;
9822 std::vector<double> angle(nPoints, 0.);
9823 std::vector<double> volt(nPoints, 0.);
9824 std::vector<double> weight(nPoints, 1.);
9825 for (unsigned int i = 0; i < nPoints; ++i) {
9826 // Set angle around wire.
9827 angle[i] = TwoPi * (i + 1.) / nPoints;
9828 // Compute E field, make sure the point is in a free region.
9829 const double x = xw + rmult * rw * cos(angle[i]);
9830 const double y = yw + rmult * rw * sin(angle[i]);
9831 double ex = 0., ey = 0., ez = 0., v = 0.;
9832 if (Field(x, y, 0., ex, ey, ez, v, true) != 0) {
9833 std::cerr << m_className << "::MultipoleMoments:\n"
9834 << " Unexpected location code. Computation stopped.\n";
9835 m_w[iw].r = rw;
9836 return false;
9837 }
9838 // Assign the result to the fitting array.
9839 volt[i] = v;
9840 }
9841 // Restore the wire diameter.
9842 m_w[iw].r = rw;
9843
9844 // Determine the maximum, minimum and average.
9845 double vmin = *std::min_element(volt.cbegin(), volt.cend());
9846 double vmax = *std::max_element(volt.cbegin(), volt.cend());
9847 double vave = std::accumulate(volt.cbegin(), volt.cend(), 0.) / nPoints;
9848 // Subtract the wire potential to centre the data more or less.
9849 for (unsigned int i = 0; i < nPoints; ++i) volt[i] -= vave;
9850 vmax -= vave;
9851 vmin -= vave;
9852
9853 // Perform the fit.
9854 const double vm = 0.5 * fabs(vmin) + fabs(vmax);
9855 double chi2 = 1.e-6 * nPoints * vm * vm;
9856 const double dist = 1.e-3 * (1. + vm);
9857 const unsigned int nPar = 2 * nPoles + 1;
9858 std::vector<double> pars(nPar, 0.);
9859 std::vector<double> epar(nPar, 0.);
9860 pars[0] = 0.5 * (vmax + vmin);
9861 for (unsigned int i = 1; i <= nPoles; ++i) {
9862 pars[2 * i - 1] = 0.5 * (vmax - vmin);
9863 pars[2 * i] = 0.;
9864 }
9865
9866 auto f = [nPoles](const double x, const std::vector<double>& par) {
9867 // EFMFUN
9868 // Sum the series, initial value is the monopole term.
9869 double sum = par[0];
9870 for (unsigned int k = 1; k <= nPoles; ++k) {
9871 // Obtain the Legendre polynomial of this order and add to the series.
9872 const float cphi = cos(x - par[2 * k]);
9873 sum += par[2 * k - 1] * sqrt(k + 0.5) * Numerics::Legendre(k, cphi);
9874 }
9875 return sum;
9876 };
9877
9878 if (!Numerics::LeastSquaresFit(f, pars, epar, angle, volt, weight,
9879 nMaxIter, dist, chi2, eps, m_debug, print)) {
9880 std::cerr << m_className << "::MultipoleMoments:\n"
9881 << " Fitting the multipoles failed; computation stopped.\n";
9882 }
9883 // Plot the result of the fit.
9884 if (plot) {
9885 TCanvas* cfit = new TCanvas();
9886 cfit->SetGridx();
9887 cfit->SetGridy();
9888 cfit->DrawFrame(0., vmin, TwoPi, vmax,
9889 ";Angle around the wire [rad]; Potential - average [V]");
9890 TGraph graph;
9891 graph.SetLineWidth(2);
9892 graph.SetLineColor(kBlack);
9893 graph.DrawGraph(angle.size(), angle.data(), volt.data(), "lsame");
9894 // Sum of contributions.
9895 constexpr unsigned int nP = 1000;
9896 std::array<double, nP> xp;
9897 std::array<double, nP> yp;
9898 for (unsigned int i = 0; i < nP; ++i) {
9899 xp[i] = TwoPi * (i + 1.) / nP;
9900 yp[i] = f(xp[i], pars);
9901 }
9902 graph.SetLineColor(kViolet + 3);
9903 graph.DrawGraph(nP, xp.data(), yp.data(), "lsame");
9904 // Individual contributions.
9905 std::vector<double> parres = pars;
9906 for (unsigned int i = 1; i <= nPoles; ++i) parres[2 * i - 1] = 0.;
9907 for (unsigned int j = 1; j <= nPoles; ++j) {
9908 parres[2 * j - 1] = pars[2 * j - 1];
9909 for (unsigned int i = 0; i < nP; ++i) {
9910 yp[i] = f(xp[i], parres);
9911 }
9912 parres[2 * j - 1] = 0.;
9913 graph.SetLineColor(kAzure + j);
9914 graph.DrawGraph(nP, xp.data(), yp.data(), "lsame");
9915 }
9916 }
9917
9918 // Print the results.
9919 std::cout << m_className << "::MultipoleMoments:\n"
9920 << " Multipole moments for wire " << iw << ":\n"
9921 << " Moment Value Angle [degree]\n";
9922 std::printf(" %6u %15.8f Arbitrary\n", 0, vave);
9923 for (unsigned int i = 1; i <= nPoles; ++i) {
9924 // Remove radial term from the multipole moment.
9925 const double val = pow(rmult * rw, i) * pars[2 * i - 1];
9926 const double phi = RadToDegree * fmod(pars[2 * i], Pi);
9927 std::printf(" %6u %15.8f %15.8f\n", i, val, phi);
9928 }
9929 return true;
9930}
9931
9932} // namespace Garfield
void PrintCell()
Print all available information on the cell.
void SetGravity(const double dx, const double dy, const double dz)
Set the gravity orientation.
bool GetVoltageRange(double &pmin, double &pmax) override
Calculate the voltage range [V].
void SetPolarCoordinates()
Use polar coordinates.
void SetPeriodicityX(const double s)
Set the periodic length [cm] in the x-direction.
void EnableDipoleTerms(const bool on=true)
Request dipole terms be included for each of the wires (default: off).
bool GetPeriodicityY(double &s)
Get the periodic length in the y-direction.
void SetCartesianCoordinates()
Use Cartesian coordinates (default).
void AddPixelOnPlanePhi(const double phi, const double rmin, const double rmax, const double zmin, const double zmax, const std::string &label, const double gap=-1.)
Add a pixel on an existing plane at constant phi.
void AddTube(const double radius, const double voltage, const int nEdges, const std::string &label)
Add a tube.
void AddPlaneX(const double x, const double voltage, const std::string &label)
Add a plane at constant x.
void AddPixelOnPlaneY(const double y, const double xmin, const double xmax, const double zmin, const double zmax, const std::string &label, const double gap=-1.)
Add a pixel on an existing plane at constant y.
void AddCharge(const double x, const double y, const double z, const double q)
Add a point charge.
void SetScanningGrid(const unsigned int nX, const unsigned int nY)
void SetNumberOfSteps(const unsigned int n)
Set the number of integration steps within each shot (must be >= 1).
void AddPixelOnPlaneR(const double r, const double phimin, const double phimax, const double zmin, const double zmax, const std::string &label, const double gap=-1.)
Add a pixel on an existing plane at constant radius.
bool GetWire(const unsigned int i, double &x, double &y, double &diameter, double &voltage, std::string &label, double &length, double &charge, int &ntrap) const
Retrieve the parameters of a wire.
bool GetPlaneR(const unsigned int i, double &r, double &voltage, std::string &label) const
Retrieve the parameters of a plane at constant radius.
bool WireDisplacement(const unsigned int iw, const bool detailed, std::vector< double > &csag, std::vector< double > &xsag, std::vector< double > &ysag, double &stretch, const bool print=true)
void GetGravity(double &dx, double &dy, double &dz) const
Get the gravity orientation.
void AddStripOnPlaneX(const char direction, const double x, const double smin, const double smax, const std::string &label, const double gap=-1.)
Add a strip in the y or z direction on an existing plane at constant x.
void ClearCharges()
Remove all point charges.
unsigned int GetNumberOfPlanesY() const
Get the number of equipotential planes at constant y.
void AddPlanePhi(const double phi, const double voltage, const std::string &label)
Add a plane at constant phi.
void AddPlaneR(const double r, const double voltage, const std::string &label)
Add a plane at constant radius.
bool ElectricFieldAtWire(const unsigned int iw, double &ex, double &ey)
bool MultipoleMoments(const unsigned int iw, const unsigned int order=4, const bool print=false, const bool plot=false, const double rmult=1., const double eps=1.e-4, const unsigned int nMaxIter=20)
void AddWire(const double x, const double y, const double diameter, const double voltage, const std::string &label, const double length=100., const double tension=50., const double rho=19.3, const int ntrap=5)
Add a wire at (x, y) .
unsigned int GetNumberOfPlanesX() const
Get the number of equipotential planes at constant x.
bool GetPeriodicityX(double &s)
Get the periodic length in the x-direction.
bool GetTube(double &r, double &voltage, int &nEdges, std::string &label) const
Retrieve the tube parameters.
bool GetPlaneX(const unsigned int i, double &x, double &voltage, std::string &label) const
Retrieve the parameters of a plane at constant x.
void AddPlaneY(const double y, const double voltage, const std::string &label)
Add a plane at constant y.
void AddPixelOnPlaneX(const double x, const double ymin, const double ymax, const double zmin, const double zmax, const std::string &label, const double gap=-1.)
Add a pixel on an existing plane at constant x.
bool IsInTrapRadius(const double q0, const double x0, const double y0, const double z0, double &xw, double &yx, double &rw) override
void SetPeriodicityPhi(const double phi)
Set the periodicity [degree] in phi.
bool GetPeriodicityPhi(double &s)
Get the periodicity [degree] in phi.
bool GetPlanePhi(const unsigned int i, double &phi, double &voltage, std::string &label) const
Retrieve the parameters of a plane at constant phi.
void AddStripOnPlaneY(const char direction, const double y, const double smin, const double smax, const std::string &label, const double gap=-1.)
Add a strip in the x or z direction on an existing plane at constant y.
bool GetBoundingBox(double &x0, double &y0, double &z0, double &x1, double &y1, double &z1) override
Get the bounding box coordinates.
bool ForcesOnWire(const unsigned int iw, std::vector< double > &xMap, std::vector< double > &yMap, std::vector< std::vector< double > > &fxMap, std::vector< std::vector< double > > &fyMap)
void SetScanningAreaFirstOrder(const double scale=2.)
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) override
void AddReadout(const std::string &label)
Setup the weighting field for a given group of wires or planes.
void SetPeriodicityY(const double s)
Set the periodic length [cm] in the y-direction.
Medium * GetMedium(const double x, const double y, const double z) override
Get the medium at a given location (x, y, z).
unsigned int GetNumberOfPlanesPhi() const
Get the number of equipotential planes at constant phi.
void AddStripOnPlaneR(const char direction, const double r, const double smin, const double smax, const std::string &label, const double gap=-1.)
Add a strip in the phi or z direction on an existing plane at constant radius.
void PrintCharges() const
Print a list of the point charges.
void SetScanningArea(const double xmin, const double xmax, const double ymin, const double ymax)
bool GetPlaneY(const unsigned int i, double &y, double &voltage, std::string &label) const
Retrieve the parameters of a plane at constant y.
unsigned int GetNumberOfPlanesR() const
Get the number of equipotential planes at constant radius.
void AddStripOnPlanePhi(const char direction, const double phi, const double smin, const double smax, const std::string &label, const double gap=-1.)
Add a strip in the r or z direction on an existing plane at constant phi.
Abstract base class for components.
std::array< bool, 3 > m_periodic
Simple periodicity in x, y, z.
std::array< bool, 3 > m_rotationSymmetric
Rotation symmetry around x-axis, y-axis, z-axis.
std::array< bool, 3 > m_axiallyPeriodic
Axial periodicity in x, y, z.
GeometryBase * m_geometry
Pointer to the geometry.
std::array< bool, 3 > m_mirrorPeriodic
Mirror periodicity in x, y, z.
std::string m_className
Class name.
bool m_debug
Switch on/off debugging messages.
virtual Medium * GetMedium(const double x, const double y, const double z) const =0
Retrieve the medium at a given point.
virtual bool GetBoundingBox(double &xmin, double &ymin, double &zmin, double &xmax, double &ymax, double &zmax)=0
Get the bounding box (envelope of the geometry).
Abstract base class for media.
Definition: Medium.hh:13
int deqn(const int n, std::vector< std::vector< double > > &a, std::vector< double > &b)
Definition: Numerics.cc:593
int cinv(const int n, std::vector< std::vector< std::complex< double > > > &a)
Replace square matrix A by its inverse.
Definition: Numerics.cc:1134
int deqinv(const int n, std::vector< std::vector< double > > &a, std::vector< double > &b)
Replaces b by the solution x of Ax = b, and replace A by its inverse.
Definition: Numerics.cc:909
double BesselK0L(const double xx)
Definition: Numerics.hh:161
double Legendre(const unsigned int n, const double x)
Legendre polynomials.
Definition: Numerics.hh:119
double BesselK1S(const double xx)
Definition: Numerics.hh:169
bool LeastSquaresFit(std::function< double(double, const std::vector< double > &)> f, std::vector< double > &par, std::vector< double > &epar, const std::vector< double > &x, const std::vector< double > &y, const std::vector< double > &ey, const unsigned int nMaxIter, const double diff, double &chi2, const double eps, const bool debug, const bool verbose)
Least-squares minimisation.
Definition: Numerics.cc:1838
double Divdif(const std::vector< double > &f, const std::vector< double > &a, int nn, double x, int mm)
Definition: Numerics.cc:1206
double BesselK0S(const double xx)
Definition: Numerics.hh:152
double BesselK1L(const double xx)
Definition: Numerics.hh:179
DoubleAc cos(const DoubleAc &f)
Definition: DoubleAc.cpp:432
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
DoubleAc sin(const DoubleAc &f)
Definition: DoubleAc.cpp:384
DoubleAc sqrt(const DoubleAc &f)
Definition: DoubleAc.cpp:314