Garfield++ 4.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#include "Garfield/ViewBase.hh"
15
16namespace {
17
18constexpr double Internal2Newton = 1.e-15 * Garfield::TwoPiEpsilon0 * 100.;
19
20std::pair<std::complex<double>, std::complex<double> > Th1(
21 const std::complex<double>& zeta, const double p1, const double p2) {
22 const std::complex<double> zsin = sin(zeta);
23 const std::complex<double> zcof = 4. * zsin * zsin - 2.;
24 std::complex<double> zu = -p1 - zcof * p2;
25 std::complex<double> zunew = 1. - zcof * zu - p2;
26 const std::complex<double> zterm1 = (zunew + zu) * zsin;
27 zu = -3. * p1 - zcof * 5. * p2;
28 zunew = 1. - zcof * zu - 5. * p2;
29 const std::complex<double> zterm2 = (zunew - zu) * cos(zeta);
30 return std::make_pair(std::move(zterm1), std::move(zterm2));
31}
32
33// Transformation from Cartesian and polar coordinates.
34void Cartesian2Polar(const double x, const double y, double& r,
35 double& theta) {
36 if (x == 0. && y == 0.) {
37 r = theta = 0.;
38 return;
39 }
40 r = sqrt(x * x + y * y);
41 theta = atan2(y, x) * Garfield::RadToDegree;
42}
43
44// Transformation from polar to Cartesian coordinates.
45void Polar2Cartesian(const double r, const double theta, double& x,
46 double& y) {
47 const double thetap = theta * Garfield::DegreeToRad;
48 x = r * cos(thetap);
49 y = r * sin(thetap);
50}
51
52// Transformation (rho, phi) to (r, theta) via the map
53// (r, theta) = (exp(rho), 180 * phi / Pi).
54void Internal2Polar(const double rho, const double phi, double& r,
55 double& theta) {
56 // CFMRTP
57 r = exp(rho);
58 theta = Garfield::RadToDegree * phi;
59}
60
61// Transformation (r, theta) to (rho, phi) via the map
62// (rho, phi) = (log(r), Pi * theta / 180).
63void Polar2Internal(const double r, const double theta, double& rho,
64 double& phi) {
65 // CFMPTR
66 rho = r > 0. ? log(r) : -25.;
67 phi = Garfield::DegreeToRad * theta;
68}
69
70// Transformation (x, y) to (rho, phi) via the conformal map
71// (x, y) = exp(rho, phi)
72void Cartesian2Internal(const double x, const double y, double& rho,
73 double& phi) {
74 // CFMCTR
75 if (x == 0 && y == 0) {
76 rho = -25.;
77 phi = 0.;
78 return;
79 }
80 // const std::complex<double> z = log(std::complex<double>(x, y));
81 // rho = real(z);
82 // phi = imag(z);
83 rho = 0.5 * log(x * x + y * y);
84 phi = atan2(y, x);
85}
86
87// Transformation (rho, phi) to (x, y) via the conformal map
88// (x, y) = exp(rho, phi)
89void Internal2Cartesian(const double rho, const double phi, double& x,
90 double& y) {
91 // CFMRTC
92 // const std::complex<double> z = exp(std::complex<double>(rho, phi));
93 // x = real(z);
94 // y = imag(z);
95 const double r = exp(rho);
96 x = r * cos(phi);
97 y = r * sin(phi);
98}
99
100bool FitDipoleMoment(const std::vector<double>& angle,
101 const std::vector<double>& volt,
102 double& ampdip, double& phidip, const bool dbg) {
103 //-----------------------------------------------------------------------
104 // DIPFIT - Determines the dipole moment of a wire.
105 //-----------------------------------------------------------------------
106
107 // Initial values.
108 phidip = 0.;
109 ampdip = 0.;
110 const unsigned int n = angle.size();
111 // Initial search for a maximum.
112 double phiMax = 0.;
113 double sumMax = 0.;
114 constexpr unsigned int nTry = 100;
115 std::array<double, nTry> phiTry;
116 std::array<double, nTry> sumTry;
117 for (unsigned int i = 0; i < nTry; ++i) {
118 // Make the internal product with a shifted cosine.
119 phiTry[i] = i * Garfield::TwoPi / nTry;
120 sumTry[i] = 0.;
121 for (unsigned int j = 0; j < n; ++j) {
122 sumTry[i] += volt[j] * cos(phiTry[i] - angle[j]);
123 }
124 sumTry[i] *= 2. / n;
125 // See whether this one beats earlier.
126 if (sumTry[i] > sumMax) {
127 phiMax = phiTry[i];
128 sumMax = sumTry[i];
129 }
130 }
131 if (dbg) {
132 std::printf(" Maximum of scan at phi = %12.5f, product = %12.5f\n",
133 phiMax, sumMax);
134 // CALL GRGRPH(phiTry, sumTry, nTry, 'Angle [radians]',
135 // 'Cos projection', 'Search of maximum')
136 }
137 phidip = phiMax;
138 ampdip = sumMax;
139 // Scan in the neighbourbood
140 constexpr double eps = 0.1;
141 double x1 = phiMax - eps;
142 double x2 = phiMax;
143 double x3 = phiMax + eps;
144 double f1 = 0.;
145 double f2 = sumMax;
146 double f3 = 0.;
147 for (unsigned int j = 0; j < n; ++j) {
148 f1 += volt[j] * cos(x1 - angle[j]);
149 f3 += volt[j] * cos(x3 - angle[j]);
150 }
151 f1 *= 2. / n;
152 f3 *= 2. / n;
153 // Refine the estimate by parabolic extremum search.
154 const double epsf = 1.e-3 * sumMax;
155 constexpr double epsx = 1.e-3 * Garfield::TwoPi;
156 constexpr unsigned int nMaxIter = 10;
157 for (unsigned int i = 0; i < nMaxIter; ++i) {
158 if (dbg) std::cout << " Start of iteration " << i << ".\n";
159 // Estimate parabolic extremum.
160 const double det = (f1 - f2) * x3 + (f3 - f1) * x2 + (f2 - f3) * x1;
161 if (std::abs(det) <= 0.) {
162 std::cerr << " Warning: Determinant = 0; parabolic search stopped.\n";
163 phidip = x2;
164 ampdip = f2;
165 return false;
166 }
167 const double xp = ((f1 - f2) * x3 * x3 + (f3 - f1) * x2 * x2 +
168 (f2 - f3) * x1 * x1) / (2 * det);
169 double fp = 0.;
170 for (unsigned int j = 0; j < n; ++j) {
171 fp += volt[j] * cos(xp - angle[j]);
172 }
173 fp *= 2. / n;
174 // Debugging output.
175 if (dbg) {
176 std::printf(" Point 1: x = %15.8f f = %15.8f\n", x1, f1);
177 std::printf(" Point 2: x = %15.8f f = %15.8f\n", x2, f2);
178 std::printf(" Point 3: x = %15.8f f = %15.8f\n", x3, f3);
179 std::printf(" Parabola: x = %15.8f f = %15.8f\n", xp, fp);
180 }
181 // Check that the new estimate doesn't coincide with an old point.
182 const double tol = epsx * (epsx + std::abs(xp));
183 if (fabs(xp - x1) < tol || fabs(xp - x2) < tol || fabs(xp - x3) < tol) {
184 if (dbg) {
185 std::cout << " Location convergence criterion satisfied.\n";
186 }
187 phidip = xp;
188 ampdip = fp;
189 return true;
190 }
191 // Check convergence.
192 if (std::abs(fp - f1) < epsf * (std::abs(fp) + std::abs(f1) + epsf)) {
193 if (dbg) {
194 std::cout << " Function value convergence criterion satisfied.\n";
195 }
196 phidip = xp;
197 ampdip = fp;
198 return true;
199 }
200 // Store the value in the table.
201 if (fp > f1) {
202 f3 = f2;
203 x3 = x2;
204 f2 = f1;
205 x2 = x1;
206 f1 = fp;
207 x1 = xp;
208 } else if (fp > f2) {
209 f3 = f2;
210 x3 = x2;
211 f2 = fp;
212 x2 = xp;
213 } else if (fp > f3) {
214 f3 = fp;
215 x3 = xp;
216 } else {
217 std::cerr << " Warning: Parabolic extremum is worse "
218 << "than current optimum; search stopped.\n";
219 std::printf(" Point 1: x = %15.8f f = %15.8f\n", x1, f1);
220 std::printf(" Point 2: x = %15.8f f = %15.8f\n", x2, f2);
221 std::printf(" Point 3: x = %15.8f f = %15.8f\n", x3, f3);
222 std::printf(" Parabola: x = %15.8f f = %15.8f\n", xp, fp);
223 phidip = x2;
224 ampdip = f2;
225 return false;
226 }
227 }
228 // No convergence.
229 std::cerr << " Warning: No convergence after maximum number of steps.\n"
230 << " Current extremum f = " << f2 << "\n"
231 << " Found for x = " << x2 << "\n";
232 phidip = x2;
233 ampdip = f2;
234 return false;
235}
236
237double MirrorCoordinate(const double x, const double xp, const double xw,
238 const double sx) {
239
240 // Find the plane nearest to the wire.
241 double cx = xp - sx * int(round((xp - xw) / sx));
242 return 2 * cx - x - xw;
243}
244
245} // namespace
246
247namespace Garfield {
248
250 CellInit();
251}
252
253Medium* ComponentAnalyticField::GetMedium(const double xin, const double yin,
254 const double zin) {
255 if (m_geometry) return m_geometry->GetMedium(xin, yin, zin);
256
257 // Make sure the cell is prepared.
258 if (!m_cellset && !Prepare()) return nullptr;
259
260 double xpos = xin, ypos = yin;
261 if (m_polar) Cartesian2Internal(xin, yin, xpos, ypos);
262 // In case of periodicity, move the point into the basic cell.
263 if (m_perx) {
264 xpos -= m_sx * int(round(xin / m_sx));
265 }
266 double arot = 0.;
267 if (m_pery && m_tube) {
268 Cartesian2Polar(xin, yin, xpos, ypos);
269 arot = RadToDegree * m_sy * int(round(DegreeToRad * ypos / m_sy));
270 ypos -= arot;
271 Polar2Cartesian(xpos, ypos, xpos, ypos);
272 } else if (m_pery) {
273 ypos -= m_sy * int(round(ypos / m_sy));
274 }
275
276 // Move the point to the correct side of the plane.
277 if (m_perx && m_ynplan[0] && xpos <= m_coplan[0]) xpos += m_sx;
278 if (m_perx && m_ynplan[1] && xpos >= m_coplan[1]) xpos -= m_sx;
279 if (m_pery && m_ynplan[2] && ypos <= m_coplan[2]) ypos += m_sy;
280 if (m_pery && m_ynplan[3] && ypos >= m_coplan[3]) ypos -= m_sy;
281
282 // In case (xpos, ypos) is located behind a plane there is no field.
283 if (m_tube) {
284 if (!InTube(xpos, ypos, m_cotube, m_ntube)) return nullptr;
285 } else {
286 if ((m_ynplan[0] && xpos < m_coplan[0]) ||
287 (m_ynplan[1] && xpos > m_coplan[1]) ||
288 (m_ynplan[2] && ypos < m_coplan[2]) ||
289 (m_ynplan[3] && ypos > m_coplan[3])) {
290 return nullptr;
291 }
292 }
293
294 // If (xpos, ypos) is within a wire, there is no field either.
295 for (const auto& wire : m_w) {
296 double dx = xpos - wire.x;
297 double dy = ypos - wire.y;
298 // Correct for periodicities.
299 if (m_perx) dx -= m_sx * int(round(dx / m_sx));
300 if (m_pery) dy -= m_sy * int(round(dy / m_sy));
301 // Check the actual position.
302 if (dx * dx + dy * dy < wire.r * wire.r) return nullptr;
303 }
304 return m_medium;
305}
306
307bool ComponentAnalyticField::GetVoltageRange(double& pmin, double& pmax) {
308 // Make sure the cell is prepared.
309 if (!m_cellset && !Prepare()) {
310 std::cerr << m_className << "::GetVoltageRange: Cell not set up.\n";
311 return false;
312 }
313
314 pmin = m_vmin;
315 pmax = m_vmax;
316 return true;
317}
318
320 double& x0, double& y0, double& z0,
321 double& x1, double& y1, double& z1) {
322 // If a geometry is present, try to get the bounding box from there.
323 if (m_geometry) {
324 if (m_geometry->GetBoundingBox(x0, y0, z0, x1, y1, z1)) return true;
325 }
326 // Otherwise, return the cell dimensions.
327 return GetElementaryCell(x0, y0, z0, x1, y1, z1);
328}
329
331 double& x0, double& y0, double& z0,
332 double& x1, double& y1, double& z1) {
333 if (!m_cellset && !Prepare()) return false;
334 if (m_polar) {
335 double rmax, thetamax;
336 Internal2Polar(m_xmax, m_ymax, rmax, thetamax);
337 x0 = -rmax;
338 y0 = -rmax;
339 x1 = +rmax;
340 y1 = +rmax;
341 } else {
342 x0 = m_xmin;
343 y0 = m_ymin;
344 x1 = m_xmax;
345 y1 = m_ymax;
346 }
347 z0 = m_zmin;
348 z1 = m_zmax;
349 return true;
350}
351
353 //-----------------------------------------------------------------------
354 // CELPRT - Subroutine printing all available information on the cell.
355 //-----------------------------------------------------------------------
356
357 // Make sure the cell is prepared.
358 if (!m_cellset && !Prepare()) {
359 std::cerr << m_className << "::PrintCell: Cell not set up.\n";
360 return;
361 }
362 std::cout << m_className
363 << "::PrintCell: Cell identification: " << GetCellType() << "\n";
364 // Print positions of wires, applied voltages and resulting charges.
365 if (!m_w.empty()) {
366 std::cout << " Table of the wires\n";
367 if (m_polar) {
368 std::cout << " Nr Diameter r phi Voltage";
369 } else {
370 std::cout << " Nr Diameter x y Voltage";
371 }
372 std::cout << " Charge Tension Length Density Label\n";
373 if (m_polar) {
374 std::cout << " [micron] [cm] [deg] [Volt]";
375 } else {
376 std::cout << " [micron] [cm] [cm] [Volt]";
377 }
378 std::cout << " [pC/cm] [g] [cm] [g/cm3]\n";
379 for (unsigned int i = 0; i < m_nWires; ++i) {
380 const auto& w = m_w[i];
381 double xw = w.x;
382 double yw = w.y;
383 double dw = 2 * w.r;
384 if (m_polar) {
385 Internal2Polar(w.x, w.y, xw, yw);
386 dw *= xw;
387 }
388 std::printf(
389 "%4d %9.2f %9.4f %9.4f %9.3f %12.4f %9.2f %9.2f %9.2f \"%s\"\n", i,
390 1.e4 * dw, xw, yw, w.v, w.e * TwoPiEpsilon0 * 1.e-3, w.tension,
391 w.u, w.density, w.type.c_str());
392 }
393 }
394 // Print information on the tube if present.
395 if (m_tube) {
396 std::string shape;
397 if (m_ntube == 0) {
398 shape = "Circular";
399 } else if (m_ntube == 3) {
400 shape = "Triangular";
401 } else if (m_ntube == 4) {
402 shape = "Square";
403 } else if (m_ntube == 5) {
404 shape = "Pentagonal";
405 } else if (m_ntube == 6) {
406 shape = "Hexagonal";
407 } else if (m_ntube == 7) {
408 shape = "Heptagonal";
409 } else if (m_ntube == 8) {
410 shape = "Octagonal";
411 } else {
412 shape = "Polygonal with " + std::to_string(m_ntube) + " corners";
413 }
414 std::cout << " Enclosing tube\n"
415 << " Potential: " << m_vttube << " V\n"
416 << " Radius: " << m_cotube << " cm\n"
417 << " Shape: " << shape << "\n"
418 << " Label: " << m_planes[4].type << "\n";
419 }
420 // Print data on the equipotential planes.
421 if (m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3]) {
422 std::cout << " Equipotential planes\n";
423 // First those at const x or r.
424 const std::string xr = m_polar ? "r" : "x";
425 if (m_ynplan[0] && m_ynplan[1]) {
426 std::cout << " There are two planes at constant " << xr << ":\n";
427 } else if (m_ynplan[0] || m_ynplan[1]) {
428 std::cout << " There is one plane at constant " << xr << ":\n";
429 }
430 for (unsigned int i = 0; i < 2; ++i) {
431 if (!m_ynplan[i]) continue;
432 if (m_polar) {
433 std::cout << " r = " << exp(m_coplan[i]) << " cm, ";
434 } else {
435 std::cout << " x = " << m_coplan[i] << " cm, ";
436 }
437 if (fabs(m_vtplan[i]) > 1.e-4) {
438 std::cout << "potential = " << m_vtplan[i] << " V, ";
439 } else {
440 std::cout << "earthed, ";
441 }
442 const auto& plane = m_planes[i];
443 if (plane.type.empty() && plane.type != "?") {
444 std::cout << "label = " << plane.type << ", ";
445 }
446 const unsigned int nStrips = plane.strips1.size() + plane.strips2.size();
447 const unsigned int nPixels = plane.pixels.size();
448 if (nStrips == 0 && nPixels == 0) {
449 std::cout << "no strips or pixels.\n";
450 } else if (nPixels == 0) {
451 std::cout << nStrips << " strips.\n";
452 } else if (nStrips == 0) {
453 std::cout << nPixels << " pixels.\n";
454 } else {
455 std::cout << nStrips << " strips, " << nPixels << " pixels.\n";
456 }
457 for (const auto& strip : plane.strips2) {
458 std::cout << " ";
459 if (m_polar) {
460 double gap = i == 0 ? expm1(strip.gap) : -expm1(-strip.gap);
461 gap *= exp(m_coplan[i]);
462 std::cout << RadToDegree * strip.smin << " < phi < "
463 << RadToDegree * strip.smax
464 << " degrees, gap = " << gap << " cm";
465 } else {
466 std::cout << strip.smin << " < y < " << strip.smax
467 << " 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& strip : plane.strips1) {
475 std::cout << " " << strip.smin << " < z < " << strip.smax;
476 if (m_polar) {
477 double gap = i == 0 ? expm1(strip.gap) : -expm1(-strip.gap);
478 gap *= exp(m_coplan[i]);
479 std::cout << " cm, gap = " << gap << " cm";
480 } else {
481 std::cout << " cm, gap = " << strip.gap << " cm";
482 }
483 if (!strip.type.empty() && strip.type != "?") {
484 std::cout << " (\"" << strip.type << "\")";
485 }
486 std::cout << "\n";
487 }
488 for (const auto& pix : plane.pixels) {
489 std::cout << " ";
490 if (m_polar) {
491 std::cout << RadToDegree * pix.smin << " < phi < "
492 << RadToDegree * pix.smax << " degrees, ";
493 } else {
494 std::cout << pix.smin << " < y < " << pix.smax << " cm, ";
495 }
496 std::cout << pix.zmin << " < z < " << pix.zmax << " cm, gap = ";
497 if (m_polar) {
498 double gap = i == 0 ? expm1(pix.gap) : -expm1(-pix.gap);
499 gap *= exp(m_coplan[i]);
500 std::cout << gap << " cm";
501 } else {
502 std::cout << pix.gap << " cm";
503 }
504 if (!pix.type.empty() && pix.type != "?") {
505 std::cout << " (\"" << pix.type << "\")";
506 }
507 std::cout << "\n";
508 }
509 }
510 // Next the planes at constant y or phi
511 const std::string yphi = m_polar ? "phi" : "y";
512 if (m_ynplan[2] && m_ynplan[3]) {
513 std::cout << " There are two planes at constant " << yphi << ":\n";
514 } else if (m_ynplan[2] || m_ynplan[3]) {
515 std::cout << " There is one plane at constant " << yphi << ":\n";
516 }
517 for (unsigned int i = 2; i < 4; ++i) {
518 if (!m_ynplan[i]) continue;
519 if (m_polar) {
520 std::cout << " phi = " << RadToDegree * m_coplan[i] << " degrees, ";
521 } else {
522 std::cout << " y = " << m_coplan[i] << " cm, ";
523 }
524 if (fabs(m_vtplan[i]) > 1.e-4) {
525 std::cout << "potential = " << m_vtplan[i] << " V, ";
526 } else {
527 std::cout << "earthed, ";
528 }
529 const auto& plane = m_planes[i];
530 if (plane.type.empty() && plane.type != "?") {
531 std::cout << "label = " << plane.type << ", ";
532 }
533 const unsigned int nStrips = plane.strips1.size() + plane.strips2.size();
534 const unsigned int nPixels = plane.pixels.size();
535 if (nStrips == 0 && nPixels == 0) {
536 std::cout << "no strips or pixels.\n";
537 } else if (nPixels == 0) {
538 std::cout << nStrips << " strips.\n";
539 } else if (nStrips == 0) {
540 std::cout << nPixels << " pixels.\n";
541 } else {
542 std::cout << nStrips << " strips, " << nPixels << " pixels.\n";
543 }
544 for (const auto& strip : plane.strips2) {
545 std::cout << " ";
546 if (m_polar) {
547 std::cout << exp(strip.smin) << " < r < " << exp(strip.smax)
548 << " cm, gap = " << RadToDegree * strip.gap << " degrees";
549 } else {
550 std::cout << strip.smin << " < x < " << strip.smax
551 << " cm, gap = " << strip.gap << " cm";
552 }
553 if (!strip.type.empty() && strip.type != "?") {
554 std::cout << " (\"" << strip.type << "\")";
555 }
556 std::cout << "\n";
557 }
558 for (const auto& strip : plane.strips1) {
559 std::cout << " " << strip.smin << " < z < " << strip.smax;
560 if (m_polar) {
561 std::cout << " cm, gap = " << RadToDegree * strip.gap << " degrees";
562 } else {
563 std::cout << " cm, gap = " << strip.gap << " cm";
564 }
565 if (!strip.type.empty() && strip.type != "?") {
566 std::cout << " (\"" << strip.type << "\")";
567 }
568 std::cout << "\n";
569 }
570 for (const auto& pix : plane.pixels) {
571 std::cout << " ";
572 if (m_polar) {
573 std::cout << exp(pix.smin) << " < r < " << exp(pix.smax) << " cm, ";
574 } else {
575 std::cout << pix.smin << " < x < " << pix.smax << " cm, ";
576 }
577 std::cout << pix.zmin << " < z < " << pix.zmax << " cm, gap = ";
578 if (m_polar) {
579 std::cout << RadToDegree * pix.gap << " degrees";
580 } else {
581 std::cout << pix.gap << " cm";
582 }
583 if (!pix.type.empty() && pix.type != "?") {
584 std::cout << " (\"" << pix.type << "\")";
585 }
586 std::cout << "\n";
587 }
588 }
589 }
590 // Print the type of periodicity.
591 std::cout << " Periodicity\n";
592 if (m_perx) {
593 std::cout << " The cell is repeated every ";
594 if (m_polar) {
595 std::cout << exp(m_sx) << " cm in r.\n";
596 } else {
597 std::cout << m_sx << " cm in x.\n";
598 }
599 } else {
600 if (m_polar) {
601 std::cout << " The cell is not periodic in r.\n";
602 } else {
603 std::cout << " The cell has no translation periodicity in x.\n";
604 }
605 }
606 if (m_pery) {
607 std::cout << " The cell is repeated every ";
608 if (m_polar) {
609 std::cout << RadToDegree * m_sy << " degrees in phi.\n";
610 } else {
611 std::cout << m_sy << " cm in y.\n";
612 }
613 } else {
614 if (m_polar) {
615 std::cout << " The cell is not periodic in phi.\n";
616 } else {
617 std::cout << " The cell has no translation periodicity in y.\n";
618 }
619 }
620 std::cout << " Other data\n";
621 std::cout << " Gravity vector: (" << m_down[0] << ", " << m_down[1]
622 << ", " << m_down[2] << ") [g].\n";
623 std::cout << " Cell dimensions:\n ";
624 if (!m_polar) {
625 std::cout << m_xmin << " < x < " << m_xmax << " cm, " << m_ymin << " < y < "
626 << m_ymax << " cm.\n";
627 } else {
628 double xminp, yminp;
629 Internal2Polar(m_xmin, m_ymin, xminp, yminp);
630 double xmaxp, ymaxp;
631 Internal2Polar(m_xmax, m_ymax, xmaxp, ymaxp);
632 std::cout << xminp << " < r < " << xmaxp << " cm, " << yminp << " < phi < "
633 << ymaxp << " degrees.\n";
634 }
635 std::cout << " Potential range:\n " << m_vmin << " < V < " << m_vmax
636 << " V.\n";
637 // Print voltage shift in case no equipotential planes are present.
638 if (!(m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3] || m_tube)) {
639 std::cout << " All voltages have been shifted by " << m_v0
640 << " V to avoid net wire charge.\n";
641 } else {
642 // Print the net charge on wires.
643 double sum = 0.;
644 for (const auto& w : m_w) sum += w.e;
645 std::cout << " The net charge on the wires is "
646 << 1.e-3 * TwoPiEpsilon0 * sum << " pC/cm.\n";
647 }
648}
649
650bool ComponentAnalyticField::IsWireCrossed(const double xx0, const double yy0,
651 const double z0, const double xx1,
652 const double yy1, const double z1,
653 double& xc, double& yc, double& zc,
654 const bool centre, double& rc) {
655 xc = xx0;
656 yc = yy0;
657 zc = z0;
658
659 if (m_w.empty()) return false;
660
661 double x0 = xx0;
662 double y0 = yy0;
663 double x1 = xx1;
664 double y1 = yy1;
665 if (m_polar) {
666 Cartesian2Internal(xx0, yy0, x0, y0);
667 Cartesian2Internal(xx1, yy1, x1, y1);
668 }
669 const double dx = x1 - x0;
670 const double dy = y1 - y0;
671 const double d2 = dx * dx + dy * dy;
672 // Check that the step length is non-zero.
673 if (d2 < Small) return false;
674 const double invd2 = 1. / d2;
675
676 // Check if a whole period has been crossed.
677 if ((m_perx && fabs(dx) >= m_sx) || (m_pery && fabs(dy) >= m_sy)) {
678 std::cerr << m_className << "::IsWireCrossed:\n"
679 << " Particle crossed more than one period.\n";
680 return false;
681 }
682
683 // Both coordinates are assumed to be located inside
684 // the drift area and inside a drift medium.
685 // This should have been checked before this call.
686
687 const double xm = 0.5 * (x0 + x1);
688 const double ym = 0.5 * (y0 + y1);
689 double dMin2 = 0.;
690 for (const auto& wire : m_w) {
691 double xw = wire.x;
692 if (m_perx) {
693 xw += m_sx * int(round((xm - xw) / m_sx));
694 }
695 double yw = wire.y;
696 if (m_pery) {
697 yw += m_sy * int(round((ym - yw) / m_sy));
698 }
699 // Calculate the smallest distance between track and wire.
700 const double xIn0 = dx * (xw - x0) + dy * (yw - y0);
701 // Check if the minimum is located before (x0, y0).
702 if (xIn0 < 0.) continue;
703 const double xIn1 = -(dx * (xw - x1) + dy * (yw - y1));
704 // Check if the minimum is located behind (x1, y1).
705 if (xIn1 < 0.) continue;
706 // Minimum is located between (x0, y0) and (x1, y1).
707 const double xw0 = xw - x0;
708 const double xw1 = xw - x1;
709 const double yw0 = yw - y0;
710 const double yw1 = yw - y1;
711 const double dw02 = xw0 * xw0 + yw0 * yw0;
712 const double dw12 = xw1 * xw1 + yw1 * yw1;
713 if (xIn1 * xIn1 * dw02 > xIn0 * xIn0 * dw12) {
714 dMin2 = dw02 - xIn0 * xIn0 * invd2;
715 } else {
716 dMin2 = dw12 - xIn1 * xIn1 * invd2;
717 }
718 // Add in the times nTrap to account for the trap radius.
719 const double r2 = wire.r * wire.r;
720 if (dMin2 < r2) {
721 // Wire has been crossed.
722 if (centre) {
723 if (m_polar) {
724 Internal2Cartesian(xw, yw, xc, yc);
725 } else {
726 xc = xw;
727 yc = yw;
728 }
729 } else {
730 // Find the point of intersection.
731 const double p = -xIn0 * invd2;
732 const double q = (dw02 - r2) * invd2;
733 const double s = sqrt(p * p - q);
734 const double t = std::min(-p + s, -p - s);
735 if (m_polar) {
736 Internal2Cartesian(x0 + t * dx, y0 + t * dy, xc, yc);
737 } else {
738 xc = x0 + t * dx;
739 yc = y0 + t * dy;
740 }
741 zc = z0 + t * (z1 - z0);
742 }
743 rc = wire.r;
744 if (m_polar) rc *= exp(wire.x);
745 return true;
746 }
747 }
748 return false;
749}
750
751bool ComponentAnalyticField::IsInTrapRadius(const double qin, const double xin,
752 const double yin, const double zin,
753 double& xw, double& yw,
754 double& rw) {
755
756 double x0 = xin;
757 double y0 = yin;
758 if (m_polar) {
759 Cartesian2Internal(xin, yin, x0, y0);
760 }
761 // In case of periodicity, move the point into the basic cell.
762 int nX = 0, nY = 0, nPhi = 0;
763 if (m_perx) {
764 nX = int(round(x0 / m_sx));
765 x0 -= m_sx * nX;
766 }
767 if (m_pery && m_tube) {
768 Cartesian2Polar(xin, yin, x0, y0);
769 nPhi = int(round(DegreeToRad * y0 / m_sy));
770 y0 -= RadToDegree * m_sy * nPhi;
771 Polar2Cartesian(x0, y0, x0, y0);
772 } else if (m_pery) {
773 nY = int(round(y0 / m_sy));
774 y0 -= m_sy * nY;
775 }
776 // Move the point to the correct side of the plane.
777 std::array<bool, 4> shift = {false, false, false, false};
778 if (m_perx && m_ynplan[0] && x0 <= m_coplan[0]) {
779 x0 += m_sx;
780 shift[0] = true;
781 }
782 if (m_perx && m_ynplan[1] && x0 >= m_coplan[1]) {
783 x0 -= m_sx;
784 shift[1] = true;
785 }
786 if (m_pery && m_ynplan[2] && y0 <= m_coplan[2]) {
787 y0 += m_sy;
788 shift[2] = true;
789 }
790 if (m_pery && m_ynplan[3] && y0 >= m_coplan[3]) {
791 y0 -= m_sy;
792 shift[3] = true;
793 }
794
795 for (const auto& wire : m_w) {
796 // Skip wires with the wrong charge.
797 if (qin * wire.e > 0.) continue;
798 const double dxw0 = wire.x - x0;
799 const double dyw0 = wire.y - y0;
800 const double r2 = dxw0 * dxw0 + dyw0 * dyw0;
801 const double rTrap = wire.r * wire.nTrap;
802 if (r2 < rTrap * rTrap) {
803 xw = wire.x;
804 yw = wire.y;
805 rw = wire.r;
806 if (shift[0]) xw -= m_sx;
807 if (shift[1]) xw += m_sx;
808 if (shift[2]) yw -= m_sy;
809 if (shift[3]) yw += m_sy;
810 if (m_pery && m_tube) {
811 double rhow, phiw;
812 Cartesian2Polar(xw, yw, rhow, phiw);
813 phiw += RadToDegree * m_sy * nPhi;
814 Polar2Cartesian(rhow, phiw, xw, yw);
815 } else if (m_pery) {
816 y0 += m_sy * nY;
817 }
818 if (m_perx) xw += m_sx * nX;
819 if (m_polar) {
820 Internal2Cartesian(xw, yw, xw, yw);
821 rw *= exp(wire.x);
822 }
823 if (m_debug) {
824 std::cout << m_className << "::IsInTrapRadius: (" << xin << ", "
825 << yin << ", " << zin << ")" << " within trap radius.\n";
826 }
827 return true;
828 }
829 }
830
831 return false;
832}
833
834void ComponentAnalyticField::AddWire(const double x, const double y,
835 const double diameter,
836 const double voltage,
837 const std::string& label,
838 const double length, const double tension,
839 double rho, const int ntrap) {
840 // Check if the provided parameters make sense.
841 if (diameter <= 0.) {
842 std::cerr << m_className << "::AddWire: Unphysical wire diameter.\n";
843 return;
844 }
845
846 if (tension <= 0.) {
847 std::cerr << m_className << "::AddWire: Unphysical wire tension.\n";
848 return;
849 }
850
851 if (rho <= 0.) {
852 std::cerr << m_className << "::AddWire: Unphysical wire density.\n";
853 return;
854 }
855
856 if (length <= 0.) {
857 std::cerr << m_className << "::AddWire: Unphysical wire length.\n";
858 return;
859 }
860
861 if (ntrap <= 0) {
862 std::cerr << m_className << "::AddWire: Nbr. of trap radii must be > 0.\n";
863 return;
864 }
865
866 if (m_polar && x <= diameter) {
867 std::cerr << m_className << "::AddWire: Wire is too close to the origin.\n";
868 return;
869 }
870
871 // Create a new wire.
872 Wire wire;
873 if (m_polar) {
874 double r = 0., phi = 0.;
875 Polar2Internal(x, y, r, phi);
876 wire.x = r;
877 wire.y = phi;
878 wire.r = 0.5 * diameter / x;
879 } else {
880 wire.x = x;
881 wire.y = y;
882 wire.r = 0.5 * diameter;
883 }
884 wire.v = voltage;
885 wire.u = length;
886 wire.type = label;
887 wire.e = 0.;
888 wire.ind = -1;
889 wire.nTrap = ntrap;
890 wire.tension = tension;
891 wire.density = rho;
892 // Add the wire to the list
893 m_w.push_back(std::move(wire));
894 ++m_nWires;
895
896 // Force recalculation of the capacitance and signal matrices.
897 m_cellset = false;
898 m_sigset = false;
899}
900
901void ComponentAnalyticField::AddTube(const double radius, const double voltage,
902 const int nEdges,
903 const std::string& label) {
904 // Check if the provided parameters make sense.
905 if (radius <= 0.0) {
906 std::cerr << m_className << "::AddTube: Unphysical tube dimension.\n";
907 return;
908 }
909
910 if (nEdges < 3 && nEdges != 0) {
911 std::cerr << m_className << "::AddTube: Unphysical number of tube edges ("
912 << nEdges << ")\n";
913 return;
914 }
915
916 // If there is already a tube defined, print a warning message.
917 if (m_tube) {
918 std::cout << m_className << "::AddTube:\n"
919 << " Warning: Existing tube settings will be overwritten.\n";
920 }
921
922 // Set the coordinate system.
923 m_tube = true;
924 m_polar = false;
925
926 // Set the tube parameters.
927 m_cotube = radius;
928 m_cotube2 = radius * radius;
929 m_vttube = voltage;
930
931 m_ntube = nEdges;
932
933 m_planes[4].type = label;
934 m_planes[4].ind = -1;
935
936 // Force recalculation of the capacitance and signal matrices.
937 m_cellset = false;
938 m_sigset = false;
939}
940
941void ComponentAnalyticField::AddPlaneX(const double x, const double v,
942 const std::string& label) {
943 if (m_polar) {
944 std::cerr << m_className << "::AddPlaneX:\n"
945 << " Not compatible with polar coordinates; ignored.\n";
946 return;
947 }
948 if (m_ynplan[0] && m_ynplan[1]) {
949 std::cerr << m_className << "::AddPlaneX:\n"
950 << " Cannot have more than two planes at constant x.\n";
951 return;
952 }
953
954 if (m_ynplan[0]) {
955 m_ynplan[1] = true;
956 m_coplan[1] = x;
957 m_vtplan[1] = v;
958 m_planes[1].type = label;
959 m_planes[1].ind = -1;
960 } else {
961 m_ynplan[0] = true;
962 m_coplan[0] = x;
963 m_vtplan[0] = v;
964 m_planes[0].type = label;
965 m_planes[0].ind = -1;
966 }
967
968 // Force recalculation of the capacitance and signal matrices.
969 m_cellset = false;
970 m_sigset = false;
971}
972
973void ComponentAnalyticField::AddPlaneY(const double y, const double v,
974 const std::string& label) {
975 if (m_polar) {
976 std::cerr << m_className << "::AddPlaneY:\n"
977 << " Not compatible with polar coordinates; ignored.\n";
978 return;
979 }
980 if (m_ynplan[2] && m_ynplan[3]) {
981 std::cerr << m_className << "::AddPlaneY:\n"
982 << " Cannot have more than two planes at constant y.\n";
983 return;
984 }
985
986 if (m_ynplan[2]) {
987 m_ynplan[3] = true;
988 m_coplan[3] = y;
989 m_vtplan[3] = v;
990 m_planes[3].type = label;
991 m_planes[3].ind = -1;
992 } else {
993 m_ynplan[2] = true;
994 m_coplan[2] = y;
995 m_vtplan[2] = v;
996 m_planes[2].type = label;
997 m_planes[2].ind = -1;
998 }
999
1000 // Force recalculation of the capacitance and signal matrices.
1001 m_cellset = false;
1002 m_sigset = false;
1003}
1004
1005void ComponentAnalyticField::AddPlaneR(const double r, const double v,
1006 const std::string& label) {
1007 if (!m_polar) {
1008 std::cerr << m_className << "::AddPlaneR:\n"
1009 << " Not compatible with Cartesian coordinates; ignored.\n";
1010 return;
1011 }
1012 if (r <= 0.) {
1013 std::cerr << m_className << "::AddPlaneR:\n"
1014 << " Radius must be larger than zero; plane ignored.\n";
1015 return;
1016 }
1017
1018 if (m_ynplan[0] && m_ynplan[1]) {
1019 std::cerr << m_className << "::AddPlaneR:\n"
1020 << " Cannot have more than two circular planes.\n";
1021 return;
1022 }
1023
1024 if (m_ynplan[0]) {
1025 m_ynplan[1] = true;
1026 m_coplan[1] = log(r);
1027 m_vtplan[1] = v;
1028 m_planes[1].type = label;
1029 m_planes[1].ind = -1;
1030 } else {
1031 m_ynplan[0] = true;
1032 m_coplan[0] = log(r);
1033 m_vtplan[0] = v;
1034 m_planes[0].type = label;
1035 m_planes[0].ind = -1;
1036 }
1037
1038 // Force recalculation of the capacitance and signal matrices.
1039 m_cellset = false;
1040 m_sigset = false;
1041}
1042
1043void ComponentAnalyticField::AddPlanePhi(const double phi, const double v,
1044 const std::string& label) {
1045 if (!m_polar) {
1046 std::cerr << m_className << "::AddPlanePhi:\n"
1047 << " Not compatible with Cartesian coordinates; ignored.\n";
1048 return;
1049 }
1050 if (m_ynplan[2] && m_ynplan[3]) {
1051 std::cerr << m_className << "::AddPlanePhi:\n"
1052 << " Cannot have more than two planes at constant phi.\n";
1053 return;
1054 }
1055
1056 if (m_ynplan[2]) {
1057 m_ynplan[3] = true;
1058 m_coplan[3] = phi * DegreeToRad;
1059 m_vtplan[3] = v;
1060 m_planes[3].type = label;
1061 m_planes[3].ind = -1;
1062 } else {
1063 m_ynplan[2] = true;
1064 m_coplan[2] = phi * DegreeToRad;
1065 m_vtplan[2] = v;
1066 m_planes[2].type = label;
1067 m_planes[2].ind = -1;
1068 // Switch off default periodicity.
1069 if (m_pery && std::abs(m_sy - TwoPi) < 1.e-4) {
1070 m_pery = false;
1071 }
1072 }
1073
1074 // Force recalculation of the capacitance and signal matrices.
1075 m_cellset = false;
1076 m_sigset = false;
1077}
1078
1079void ComponentAnalyticField::AddStripOnPlaneX(const char dir, const double x,
1080 const double smin,
1081 const double smax,
1082 const std::string& label,
1083 const double gap) {
1084 if (m_polar || (!m_ynplan[0] && !m_ynplan[1])) {
1085 std::cerr << m_className << "::AddStripOnPlaneX:\n"
1086 << " There are no planes at constant x.\n";
1087 return;
1088 }
1089
1090 if (dir != 'y' && dir != 'Y' && dir != 'z' && dir != 'Z') {
1091 std::cerr << m_className << "::AddStripOnPlaneX:\n"
1092 << " Invalid direction (" << dir << ").\n"
1093 << " Only strips in y or z direction are possible.\n";
1094 return;
1095 }
1096
1097 if (fabs(smax - smin) < Small) {
1098 std::cerr << m_className << "::AddStripOnPlaneX:\n"
1099 << " Strip width must be greater than zero.\n";
1100 return;
1101 }
1102
1103 Strip newStrip;
1104 newStrip.type = label;
1105 newStrip.ind = -1;
1106 newStrip.smin = std::min(smin, smax);
1107 newStrip.smax = std::max(smin, smax);
1108 newStrip.gap = gap > Small ? gap : -1.;
1109
1110 int iplane = 0;
1111 if (m_ynplan[1]) {
1112 const double d0 = fabs(m_coplan[0] - x);
1113 const double d1 = fabs(m_coplan[1] - x);
1114 if (d1 < d0) iplane = 1;
1115 }
1116
1117 if (dir == 'y' || dir == 'Y') {
1118 m_planes[iplane].strips1.push_back(std::move(newStrip));
1119 } else {
1120 m_planes[iplane].strips2.push_back(std::move(newStrip));
1121 }
1122}
1123
1124void ComponentAnalyticField::AddStripOnPlaneY(const char dir, const double y,
1125 const double smin,
1126 const double smax,
1127 const std::string& label,
1128 const double gap) {
1129 if (m_polar || (!m_ynplan[2] && !m_ynplan[3])) {
1130 std::cerr << m_className << "::AddStripOnPlaneY:\n"
1131 << " There are no planes at constant y.\n";
1132 return;
1133 }
1134
1135 if (dir != 'x' && dir != 'X' && dir != 'z' && dir != 'Z') {
1136 std::cerr << m_className << "::AddStripOnPlaneY:\n"
1137 << " Invalid direction (" << dir << ").\n"
1138 << " Only strips in x or z direction are possible.\n";
1139 return;
1140 }
1141
1142 if (fabs(smax - smin) < Small) {
1143 std::cerr << m_className << "::AddStripOnPlaneY:\n"
1144 << " Strip width must be greater than zero.\n";
1145 return;
1146 }
1147
1148 Strip newStrip;
1149 newStrip.type = label;
1150 newStrip.ind = -1;
1151 newStrip.smin = std::min(smin, smax);
1152 newStrip.smax = std::max(smin, smax);
1153 newStrip.gap = gap > Small ? gap : -1.;
1154
1155 int iplane = 2;
1156 if (m_ynplan[3]) {
1157 const double d2 = fabs(m_coplan[2] - y);
1158 const double d3 = fabs(m_coplan[3] - y);
1159 if (d3 < d2) iplane = 3;
1160 }
1161
1162 if (dir == 'x' || dir == 'X') {
1163 m_planes[iplane].strips1.push_back(std::move(newStrip));
1164 } else {
1165 m_planes[iplane].strips2.push_back(std::move(newStrip));
1166 }
1167}
1168
1169void ComponentAnalyticField::AddStripOnPlaneR(const char dir, const double r,
1170 const double smin,
1171 const double smax,
1172 const std::string& label,
1173 const double gap) {
1174 if (!m_polar || (!m_ynplan[0] && !m_ynplan[1])) {
1175 std::cerr << m_className << "::AddStripOnPlaneR:\n"
1176 << " There are no planes at constant r.\n";
1177 return;
1178 }
1179
1180 if (dir != 'p' && dir != 'P' && dir != 'z' && dir != 'Z') {
1181 std::cerr << m_className << "::AddStripOnPlaneR:\n"
1182 << " Invalid direction (" << dir << ").\n"
1183 << " Only strips in p(hi) or z direction are possible.\n";
1184 return;
1185 }
1186
1187 if (fabs(smax - smin) < Small) {
1188 std::cerr << m_className << "::AddStripOnPlaneR:\n"
1189 << " Strip width must be greater than zero.\n";
1190 return;
1191 }
1192
1193 Strip newStrip;
1194 newStrip.type = label;
1195 newStrip.ind = -1;
1196 if (dir == 'z' || dir == 'Z') {
1197 const double phimin = smin * DegreeToRad;
1198 const double phimax = smax * DegreeToRad;
1199 newStrip.smin = std::min(phimin, phimax);
1200 newStrip.smax = std::max(phimin, phimax);
1201 } else {
1202 newStrip.smin = std::min(smin, smax);
1203 newStrip.smax = std::max(smin, smax);
1204 }
1205 newStrip.gap = gap > Small ? gap : -1.;
1206
1207 int iplane = 0;
1208 if (m_ynplan[1]) {
1209 const double rho = r > 0. ? log(r) : -25.;
1210 const double d0 = fabs(m_coplan[0] - rho);
1211 const double d1 = fabs(m_coplan[1] - rho);
1212 if (d1 < d0) iplane = 1;
1213 }
1214
1215 if (dir == 'p' || dir == 'P') {
1216 m_planes[iplane].strips1.push_back(std::move(newStrip));
1217 } else {
1218 m_planes[iplane].strips2.push_back(std::move(newStrip));
1219 }
1220}
1221
1223 const double phi,
1224 const double smin,
1225 const double smax,
1226 const std::string& label,
1227 const double gap) {
1228 if (!m_polar || (!m_ynplan[2] && !m_ynplan[3])) {
1229 std::cerr << m_className << "::AddStripOnPlanePhi:\n"
1230 << " There are no planes at constant phi.\n";
1231 return;
1232 }
1233
1234 if (dir != 'r' && dir != 'R' && dir != 'z' && dir != 'Z') {
1235 std::cerr << m_className << "::AddStripOnPlanePhi:\n"
1236 << " Invalid direction (" << dir << ").\n"
1237 << " Only strips in r or z direction are possible.\n";
1238 return;
1239 }
1240
1241 if (fabs(smax - smin) < Small) {
1242 std::cerr << m_className << "::AddStripOnPlanePhi:\n"
1243 << " Strip width must be greater than zero.\n";
1244 return;
1245 }
1246
1247 Strip newStrip;
1248 newStrip.type = label;
1249 newStrip.ind = -1;
1250 if (dir== 'z' || dir == 'Z') {
1251 if (smin < Small || smax < Small) {
1252 std::cerr << m_className << "::AddStripOnPlanePhi:\n"
1253 << " Radius must be greater than zero.\n";
1254 return;
1255 }
1256 const double rhomin = log(smin);
1257 const double rhomax = log(smax);
1258 newStrip.smin = std::min(rhomin, rhomax);
1259 newStrip.smax = std::max(rhomin, rhomax);
1260 } else {
1261 newStrip.smin = std::min(smin, smax);
1262 newStrip.smax = std::max(smin, smax);
1263 }
1264 newStrip.gap = gap > Small ? DegreeToRad * gap : -1.;
1265
1266 int iplane = 2;
1267 if (m_ynplan[3]) {
1268 const double d2 = fabs(m_coplan[2] - phi * DegreeToRad);
1269 const double d3 = fabs(m_coplan[3] - phi * DegreeToRad);
1270 if (d3 < d2) iplane = 3;
1271 }
1272
1273 if (dir == 'r' || dir == 'R') {
1274 m_planes[iplane].strips1.push_back(std::move(newStrip));
1275 } else {
1276 m_planes[iplane].strips2.push_back(std::move(newStrip));
1277 }
1278}
1279
1280
1282 const double x, const double ymin, const double ymax, const double zmin,
1283 const double zmax, const std::string& label, const double gap,
1284 const double rot) {
1285 if (m_polar || (!m_ynplan[0] && !m_ynplan[1])) {
1286 std::cerr << m_className << "::AddPixelOnPlaneX:\n"
1287 << " There are no planes at constant x.\n";
1288 return;
1289 }
1290
1291 if (fabs(ymax - ymin) < Small || fabs(zmax - zmin) < Small) {
1292 std::cerr << m_className << "::AddPixelOnPlaneX:\n"
1293 << " Pixel width must be greater than zero.\n";
1294 return;
1295 }
1296
1297 Pixel pixel;
1298 pixel.type = label;
1299 pixel.ind = -1;
1300 pixel.smin = std::min(ymin, ymax);
1301 pixel.smax = std::max(ymin, ymax);
1302 pixel.zmin = std::min(zmin, zmax);
1303 pixel.zmax = std::max(zmin, zmax);
1304 pixel.gap = gap > Small ? gap : -1.;
1305 if (fabs(rot) > 1.e-9) {
1306 pixel.cphi = cos(rot);
1307 pixel.sphi = sin(rot);
1308 }
1309
1310 int iplane = 0;
1311 if (m_ynplan[1]) {
1312 const double d0 = fabs(m_coplan[0] - x);
1313 const double d1 = fabs(m_coplan[1] - x);
1314 if (d1 < d0) iplane = 1;
1315 }
1316
1317 m_planes[iplane].pixels.push_back(std::move(pixel));
1318}
1319
1321 const double y, const double xmin, const double xmax, const double zmin,
1322 const double zmax, const std::string& label, const double gap,
1323 const double rot) {
1324 if (m_polar || (!m_ynplan[2] && !m_ynplan[3])) {
1325 std::cerr << m_className << "::AddPixelOnPlaneY:\n"
1326 << " There are no planes at constant y.\n";
1327 return;
1328 }
1329
1330 if (fabs(xmax - xmin) < Small || fabs(zmax - zmin) < Small) {
1331 std::cerr << m_className << "::AddPixelOnPlaneY:\n"
1332 << " Pixel width must be greater than zero.\n";
1333 return;
1334 }
1335
1336 Pixel pixel;
1337 pixel.type = label;
1338 pixel.ind = -1;
1339 pixel.smin = std::min(xmin, xmax);
1340 pixel.smax = std::max(xmin, xmax);
1341 pixel.zmin = std::min(zmin, zmax);
1342 pixel.zmax = std::max(zmin, zmax);
1343 pixel.gap = gap > Small ? gap : -1.;
1344 if (fabs(rot) > 1.e-9) {
1345 pixel.cphi = cos(rot);
1346 pixel.sphi = sin(rot);
1347 }
1348
1349 int iplane = 2;
1350 if (m_ynplan[3]) {
1351 const double d0 = fabs(m_coplan[2] - y);
1352 const double d1 = fabs(m_coplan[3] - y);
1353 if (d1 < d0) iplane = 3;
1354 }
1355
1356 m_planes[iplane].pixels.push_back(std::move(pixel));
1357}
1358
1360 const double r, const double phimin, const double phimax,
1361 const double zmin, const double zmax, const std::string& label,
1362 const double gap) {
1363 if (!m_polar || (!m_ynplan[0] && !m_ynplan[1])) {
1364 std::cerr << m_className << "::AddPixelOnPlaneR:\n"
1365 << " There are no planes at constant r.\n";
1366 return;
1367 }
1368
1369 if (fabs(phimax - phimin) < Small || fabs(zmax - zmin) < Small) {
1370 std::cerr << m_className << "::AddPixelOnPlaneR:\n"
1371 << " Pixel width must be greater than zero.\n";
1372 return;
1373 }
1374
1375 Pixel pixel;
1376 pixel.type = label;
1377 pixel.ind = -1;
1378 const double smin = phimin * DegreeToRad;
1379 const double smax = phimax * DegreeToRad;
1380 pixel.smin = std::min(smin, smax);
1381 pixel.smax = std::max(smin, smax);
1382 pixel.zmin = std::min(zmin, zmax);
1383 pixel.zmax = std::max(zmin, zmax);
1384 pixel.gap = gap > Small ? gap : -1.;
1385
1386 int iplane = 0;
1387 if (m_ynplan[1]) {
1388 const double rho = r > 0. ? log(r) : -25.;
1389 const double d0 = fabs(m_coplan[0] - rho);
1390 const double d1 = fabs(m_coplan[1] - rho);
1391 if (d1 < d0) iplane = 1;
1392 }
1393
1394 m_planes[iplane].pixels.push_back(std::move(pixel));
1395}
1396
1398 const double phi, const double rmin, const double rmax,
1399 const double zmin, const double zmax, const std::string& label,
1400 const double gap) {
1401 if (!m_polar || (!m_ynplan[2] && !m_ynplan[3])) {
1402 std::cerr << m_className << "::AddPixelOnPlanePhi:\n"
1403 << " There are no planes at constant phi.\n";
1404 return;
1405 }
1406
1407 if (fabs(rmax - rmin) < Small || fabs(zmax - zmin) < Small) {
1408 std::cerr << m_className << "::AddPixelOnPlanePhi:\n"
1409 << " Pixel width must be greater than zero.\n";
1410 return;
1411 }
1412 if (rmin < Small || rmax < Small) {
1413 std::cerr << m_className << "::AddPixelOnPlanePhi:\n"
1414 << " Radius must be greater than zero.\n";
1415 return;
1416 }
1417 Pixel pixel;
1418 pixel.type = label;
1419 pixel.ind = -1;
1420 const double smin = log(rmin);
1421 const double smax = log(rmax);
1422 pixel.smin = std::min(smin, smax);
1423 pixel.smax = std::max(smin, smax);
1424 pixel.zmin = std::min(zmin, zmax);
1425 pixel.zmax = std::max(zmin, zmax);
1426 pixel.gap = gap > Small ? DegreeToRad * gap : -1.;
1427
1428 int iplane = 2;
1429 if (m_ynplan[3]) {
1430 const double d0 = fabs(m_coplan[2] - phi * DegreeToRad);
1431 const double d1 = fabs(m_coplan[3] - phi * DegreeToRad);
1432 if (d1 < d0) iplane = 3;
1433 }
1434
1435 m_planes[iplane].pixels.push_back(std::move(pixel));
1436}
1437
1439
1440 m_cellset = false;
1441 m_sigset = false;
1442 m_dipole = on;
1443}
1444
1446 if (m_polar) {
1447 std::cerr << m_className << "::SetPeriodicityX:\n"
1448 << " Cannot use x-periodicity with polar coordinates.\n";
1449 return;
1450 }
1451 if (s < Small) {
1452 std::cerr << m_className << "::SetPeriodicityX:\n"
1453 << " Periodic length must be greater than zero.\n";
1454 return;
1455 }
1456
1457 m_periodic[0] = true;
1458 m_sx = s;
1459 UpdatePeriodicity();
1460}
1461
1463 if (m_polar) {
1464 std::cerr << m_className << "::SetPeriodicityY:\n"
1465 << " Cannot use y-periodicity with polar coordinates.\n";
1466 return;
1467 }
1468 if (s < Small) {
1469 std::cerr << m_className << "::SetPeriodicityY:\n"
1470 << " Periodic length must be greater than zero.\n";
1471 return;
1472 }
1473
1474 m_periodic[1] = true;
1475 m_sy = s;
1476 UpdatePeriodicity();
1477}
1478
1480 if (!m_polar && !m_tube) {
1481 std::cerr << m_className << "::SetPeriodicityPhi:\n"
1482 << " Cannot use phi-periodicity with Cartesian coordinates.\n";
1483 return;
1484 }
1485 if (std::abs(360. - s * int(360. / s)) > 1.e-4) {
1486 std::cerr << m_className << "::SetPeriodicityPhi:\n"
1487 << " Phi periods must divide 360; ignored.\n";
1488 return;
1489 }
1490
1491 m_periodic[1] = true;
1492 m_sy = DegreeToRad * s;
1493 m_mtube = int(360. / s);
1494 UpdatePeriodicity();
1495}
1496
1498 if (!m_periodic[0] || m_polar) {
1499 s = 0.;
1500 return false;
1501 }
1502
1503 s = m_sx;
1504 return true;
1505}
1506
1508 if (!m_periodic[1] || m_polar) {
1509 s = 0.;
1510 return false;
1511 }
1512
1513 s = m_sy;
1514 return true;
1515}
1516
1518 if (!m_periodic[1] || (!m_polar && !m_tube)) {
1519 s = 0.;
1520 return false;
1521 }
1522
1523 s = RadToDegree * m_sy;
1524 return true;
1525}
1526
1527void ComponentAnalyticField::UpdatePeriodicity() {
1528 // Check if the settings have actually changed.
1529 if (m_perx && !m_periodic[0]) {
1530 m_perx = false;
1531 m_cellset = false;
1532 m_sigset = false;
1533 } else if (!m_perx && m_periodic[0]) {
1534 if (m_sx < Small) {
1535 std::cerr << m_className << "::UpdatePeriodicity:\n";
1536 std::cerr << " Periodicity in x direction was enabled"
1537 << " but periodic length is not set.\n";
1538 } else {
1539 m_perx = true;
1540 m_cellset = false;
1541 m_sigset = false;
1542 }
1543 }
1544
1545 if (m_pery && !m_periodic[1]) {
1546 m_pery = false;
1547 m_cellset = false;
1548 m_sigset = false;
1549 } else if (!m_pery && m_periodic[1]) {
1550 if (m_sy < Small) {
1551 std::cerr << m_className << "::UpdatePeriodicity:\n";
1552 std::cerr << " Periodicity in y direction was enabled"
1553 << " but periodic length is not set.\n";
1554 } else {
1555 m_pery = true;
1556 m_cellset = false;
1557 m_sigset = false;
1558 }
1559 }
1560
1561 // Check if symmetries other than x/y periodicity have been requested
1562 if (m_periodic[2]) {
1563 std::cerr << m_className << "::UpdatePeriodicity:\n"
1564 << " Periodicity in z is not possible.\n";
1565 }
1566
1568 std::cerr << m_className << "::UpdatePeriodicity:\n"
1569 << " Mirror periodicity is not possible.\n";
1570 }
1571
1573 std::cerr << m_className << "::UpdatePeriodicity:\n"
1574 << " Axial periodicity is not possible.\n";
1575 }
1576
1579 std::cerr << m_className << "::UpdatePeriodicity:\n"
1580 << " Rotation symmetry is not possible.\n";
1581 }
1582}
1583
1585 if (m_polar) {
1586 std::cout << m_className << "::SetCartesianCoordinates:\n "
1587 << "Switching to Cartesian coordinates; resetting the cell.\n";
1588 CellInit();
1589 }
1590 m_polar = false;
1591}
1592
1594 if (!m_polar) {
1595 std::cout << m_className << "::SetPolarCoordinates:\n "
1596 << "Switching to polar coordinates; resetting the cell.\n";
1597 CellInit();
1598 }
1599 m_polar = true;
1600 // Set default phi period.
1601 m_pery = true;
1602 m_sy = TwoPi;
1603}
1604
1605void ComponentAnalyticField::AddCharge(const double x, const double y,
1606 const double z, const double q) {
1607 // Convert from fC to internal units (division by 4 pi epsilon0).
1608 Charge3d charge;
1609 charge.x = x;
1610 charge.y = y;
1611 charge.z = z;
1612 charge.e = q / FourPiEpsilon0;
1613 m_ch3d.push_back(std::move(charge));
1614}
1615
1617 m_ch3d.clear();
1618 m_nTermBessel = 10;
1619 m_nTermPoly = 100;
1620}
1621
1623 std::cout << m_className << "::PrintCharges:\n";
1624 if (m_ch3d.empty()) {
1625 std::cout << " No charges present.\n";
1626 return;
1627 }
1628 std::cout << " x [cm] y [cm] z [cm] charge [fC]\n";
1629 for (const auto& charge : m_ch3d) {
1630 std::cout << " " << std::setw(9) << charge.x << " " << std::setw(9)
1631 << charge.y << " " << std::setw(9) << charge.z << " "
1632 << std::setw(11) << charge.e * FourPiEpsilon0 << "\n";
1633 }
1634}
1635
1637 if (m_polar) {
1638 return 0;
1639 } else if (m_ynplan[0] && m_ynplan[1]) {
1640 return 2;
1641 } else if (m_ynplan[0] || m_ynplan[1]) {
1642 return 1;
1643 }
1644 return 0;
1645}
1646
1648 if (m_polar) {
1649 return 0;
1650 } else if (m_ynplan[2] && m_ynplan[3]) {
1651 return 2;
1652 } else if (m_ynplan[2] || m_ynplan[3]) {
1653 return 1;
1654 }
1655 return 0;
1656}
1657
1659 if (!m_polar) {
1660 return 0;
1661 } else if (m_ynplan[0] && m_ynplan[1]) {
1662 return 2;
1663 } else if (m_ynplan[0] || m_ynplan[1]) {
1664 return 1;
1665 }
1666 return 0;
1667}
1668
1670 if (!m_polar) {
1671 return 0;
1672 } else if (m_ynplan[2] && m_ynplan[3]) {
1673 return 2;
1674 } else if (m_ynplan[2] || m_ynplan[3]) {
1675 return 1;
1676 }
1677 return 0;
1678}
1679
1680bool ComponentAnalyticField::GetWire(const unsigned int i, double& x, double& y,
1681 double& diameter, double& voltage,
1682 std::string& label, double& length,
1683 double& charge, int& ntrap) const {
1684 if (i >= m_nWires) {
1685 std::cerr << m_className << "::GetWire: Index out of range.\n";
1686 return false;
1687 }
1688
1689 if (m_polar) {
1690 double r = 0., theta = 0.;
1691 Internal2Polar(m_w[i].x, m_w[i].y, r, theta);
1692 x = r;
1693 y = theta;
1694 diameter = 2 * m_w[i].r * r;
1695 } else {
1696 x = m_w[i].x;
1697 y = m_w[i].y;
1698 diameter = 2 * m_w[i].r;
1699 }
1700 voltage = m_w[i].v;
1701 label = m_w[i].type;
1702 length = m_w[i].u;
1703 charge = m_w[i].e;
1704 ntrap = m_w[i].nTrap;
1705 return true;
1706}
1707
1708bool ComponentAnalyticField::GetPlaneX(const unsigned int i, double& x,
1709 double& voltage,
1710 std::string& label) const {
1711 if (m_polar || i >= 2 || (i == 1 && !m_ynplan[1])) {
1712 std::cerr << m_className << "::GetPlaneX: Index out of range.\n";
1713 return false;
1714 }
1715
1716 x = m_coplan[i];
1717 voltage = m_vtplan[i];
1718 label = m_planes[i].type;
1719 return true;
1720}
1721
1722bool ComponentAnalyticField::GetPlaneY(const unsigned int i, double& y,
1723 double& voltage,
1724 std::string& label) const {
1725 if (m_polar || i >= 2 || (i == 1 && !m_ynplan[3])) {
1726 std::cerr << m_className << "::GetPlaneY: Index out of range.\n";
1727 return false;
1728 }
1729
1730 y = m_coplan[i + 2];
1731 voltage = m_vtplan[i + 2];
1732 label = m_planes[i + 2].type;
1733 return true;
1734}
1735
1736bool ComponentAnalyticField::GetPlaneR(const unsigned int i, double& r,
1737 double& voltage,
1738 std::string& label) const {
1739 if (!m_polar || i >= 2 || (i == 1 && !m_ynplan[1])) {
1740 std::cerr << m_className << "::GetPlaneR: Index out of range.\n";
1741 return false;
1742 }
1743
1744 r = exp(m_coplan[i]);
1745 voltage = m_vtplan[i];
1746 label = m_planes[i].type;
1747 return true;
1748}
1749
1750bool ComponentAnalyticField::GetPlanePhi(const unsigned int i, double& phi,
1751 double& voltage,
1752 std::string& label) const {
1753 if (!m_polar || i >= 2 || (i == 1 && !m_ynplan[3])) {
1754 std::cerr << m_className << "::GetPlanePhi: Index out of range.\n";
1755 return false;
1756 }
1757
1758 phi = RadToDegree * m_coplan[i + 2];
1759 voltage = m_vtplan[i + 2];
1760 label = m_planes[i + 2].type;
1761 return true;
1762}
1763
1764bool ComponentAnalyticField::GetTube(double& r, double& voltage, int& nEdges,
1765 std::string& label) const {
1766 if (!m_tube) return false;
1767 r = m_cotube;
1768 voltage = m_vttube;
1769 nEdges = m_ntube;
1770 label = m_planes[4].type;
1771 return true;
1772}
1773
1775 double& ex, double& ey) {
1776 //-----------------------------------------------------------------------
1777 // FFIELD - Subroutine calculating the electric field at a given wire
1778 // position, as if the wire itself were not there but with
1779 // the presence of its mirror images.
1780 // VARIABLES : IW : wire number
1781 // EX, EY : x- and y-component of the electric field.
1782 // (Last changed on 27/ 1/96.)
1783 //-----------------------------------------------------------------------
1784 ex = ey = 0.;
1785 // Check the wire number.
1786 if (iw >= m_nWires) {
1787 std::cerr << m_className << "::ElectricFieldAtWire: Index out of range.\n";
1788 return false;
1789 }
1790 // Set the flags appropriately.
1791 std::vector<bool> cnalso(m_nWires, true);
1792 cnalso[iw] = false;
1793
1794 const double xpos = m_w[iw].x;
1795 const double ypos = m_w[iw].y;
1796 // Call the appropriate function.
1797 switch (m_cellType) {
1798 case A00:
1799 FieldAtWireA00(xpos, ypos, ex, ey, cnalso);
1800 break;
1801 case B1X:
1802 FieldAtWireB1X(xpos, ypos, ex, ey, cnalso);
1803 break;
1804 case B1Y:
1805 FieldAtWireB1Y(xpos, ypos, ex, ey, cnalso);
1806 break;
1807 case B2X:
1808 FieldAtWireB2X(xpos, ypos, ex, ey, cnalso);
1809 break;
1810 case B2Y:
1811 FieldAtWireB2Y(xpos, ypos, ex, ey, cnalso);
1812 break;
1813 case C10:
1814 FieldAtWireC10(xpos, ypos, ex, ey, cnalso);
1815 break;
1816 case C2X:
1817 FieldAtWireC2X(xpos, ypos, ex, ey, cnalso);
1818 break;
1819 case C2Y:
1820 FieldAtWireC2Y(xpos, ypos, ex, ey, cnalso);
1821 break;
1822 case C30:
1823 FieldAtWireC30(xpos, ypos, ex, ey, cnalso);
1824 break;
1825 case D10:
1826 FieldAtWireD10(xpos, ypos, ex, ey, cnalso);
1827 break;
1828 case D20:
1829 FieldAtWireD20(xpos, ypos, ex, ey, cnalso);
1830 break;
1831 case D30:
1832 FieldAtWireD30(xpos, ypos, ex, ey, cnalso);
1833 break;
1834 default:
1835 std::cerr << m_className << "::ElectricFieldAtWire:\n"
1836 << " Unknown cell type (id " << m_cellType << ")\n";
1837 return false;
1838 }
1839 // Correct for the equipotential planes.
1840 ex -= m_corvta;
1841 ey -= m_corvtb;
1842 if (m_polar) {
1843 const double r = exp(xpos);
1844 const double er = ex / r;
1845 const double ep = ey / r;
1846 const double ct = cos(ypos);
1847 const double st = sin(ypos);
1848 ex = +ct * er - st * ep;
1849 ey = +st * er + ct * ep;
1850 }
1851 return true;
1852}
1853
1855 const unsigned int nY) {
1856 if (nX < 2) {
1857 std::cerr << m_className << "::SetScanningGrid:\n"
1858 << " Number of x-lines must be > 1.\n";
1859 } else {
1860 m_nScanX = nX;
1861 }
1862 if (nY < 2) {
1863 std::cerr << m_className << "::SetScanningGrid:\n"
1864 << " Number of y-lines must be > 1.\n";
1865 } else {
1866 m_nScanY = nY;
1867 }
1868}
1869
1871 const double xmax,
1872 const double ymin,
1873 const double ymax) {
1874 if (std::abs(xmax - xmin) < Small || std::abs(ymax - ymin) < Small) {
1875 std::cerr << m_className << "::SetScanningArea:\n"
1876 << " Zero range not permitted.\n";
1877 return;
1878 }
1879 m_scanRange = ScanningRange::User;
1880 m_xScanMin = std::min(xmin, xmax);
1881 m_xScanMax = std::max(xmin, xmax);
1882 m_yScanMin = std::min(ymin, ymax);
1883 m_yScanMax = std::max(ymin, ymax);
1884}
1885
1887 m_scanRange = ScanningRange::FirstOrder;
1888 if (scale > 0.) {
1889 m_scaleRange = scale;
1890 } else {
1891 std::cerr << m_className << "::SetScanningAreaFirstOrder:\n"
1892 << " Scaling factor must be > 0.\n";
1893 }
1894}
1895
1896void ComponentAnalyticField::SetGravity(const double dx, const double dy,
1897 const double dz) {
1898
1899 const double d = sqrt(dx * dx + dy * dy + dz * dz);
1900 if (d > 0.) {
1901 m_down[0] = dx / d;
1902 m_down[1] = dy / d;
1903 m_down[2] = dz / d;
1904 } else {
1905 std::cerr << m_className << "::SetGravity:\n"
1906 << " The gravity vector has zero norm ; ignored.\n";
1907 }
1908}
1909
1910void ComponentAnalyticField::GetGravity(double& dx, double& dy,
1911 double& dz) const {
1912
1913 dx = m_down[0];
1914 dy = m_down[1];
1915 dz = m_down[2];
1916}
1917
1919 const unsigned int iw, std::vector<double>& xMap, std::vector<double>& yMap,
1920 std::vector<std::vector<double> >& fxMap,
1921 std::vector<std::vector<double> >& fyMap) {
1922 if (!m_cellset && !Prepare()) {
1923 std::cerr << m_className << "::ForcesOnWire: Cell not set up.\n";
1924 return false;
1925 }
1926
1927 if (iw >= m_nWires) {
1928 std::cerr << m_className << "::ForcesOnWire: Wire index out of range.\n";
1929 return false;
1930 }
1931 if (m_polar) {
1932 std::cerr << m_className << "::ForcesOnWire: Cannot handle polar cells.\n";
1933 return false;
1934 }
1935 const auto& wire = m_w[iw];
1936 // Compute a 'safe box' around the wire.
1937 double bxmin = m_perx ? wire.x - 0.5 * m_sx : 2 * m_xmin - m_xmax;
1938 double bxmax = m_perx ? wire.x + 0.5 * m_sx : 2 * m_xmax - m_xmin;
1939 double bymin = m_pery ? wire.y - 0.5 * m_sy : 2 * m_ymin - m_ymax;
1940 double bymax = m_pery ? wire.y + 0.5 * m_sy : 2 * m_ymax - m_ymin;
1941
1942 // If the initial area is almost zero in 1 direction, make it square.
1943 if (std::abs(bxmax - bxmin) < 0.1 * std::abs(bymax - bymin)) {
1944 bxmin = wire.x - 0.5 * std::abs(bymax - bymin);
1945 bxmax = wire.x + 0.5 * std::abs(bymax - bymin);
1946 } else if (std::abs(bymax - bymin) < 0.1 * std::abs(bxmax - bxmin)) {
1947 bymin = wire.y - 0.5 * std::abs(bxmax - bxmin);
1948 bymax = wire.y + 0.5 * std::abs(bxmax - bxmin);
1949 }
1950 const double dw = 2 * wire.r;
1951 // Scan the other wires.
1952 for (unsigned int j = 0; j < m_nWires; ++j) {
1953 if (j == iw) continue;
1954 const double xj = m_w[j].x;
1955 const double yj = m_w[j].y;
1956 const double dj = 2 * m_w[j].r;
1957 double xnear = m_perx ? xj - m_sx * int(round((xj - wire.x) / m_sx)) : xj;
1958 double ynear = m_pery ? yj - m_sy * int(round((yj - wire.y) / m_sy)) : yj;
1959 if (std::abs(xnear - wire.x) > std::abs(ynear - wire.y)) {
1960 if (xnear < wire.x) {
1961 bxmin = std::max(bxmin, xnear + dj + dw);
1962 if (m_perx) bxmax = std::min(bxmax, xnear + m_sx - dj - dw);
1963 } else {
1964 bxmax = std::min(bxmax, xnear - dj - dw);
1965 if (m_perx) bxmin = std::max(bxmin, xnear - m_sx + dj + dw);
1966 }
1967 } else {
1968 if (ynear < wire.y) {
1969 bymin = std::max({bymin, ynear - dj - dw, ynear + dj + dw});
1970 if (m_pery) bymax = std::min(bymax, ynear + m_sy - dj - dw);
1971 } else {
1972 bymax = std::min({bymax, ynear - dj - dw, ynear + dj + dw});
1973 if (m_pery) bymin = std::max(bymin, ynear - m_sy + dj + dw);
1974 }
1975 }
1976 }
1977 // Scan the planes.
1978 if (m_ynplan[0]) bxmin = std::max(bxmin, m_coplan[0] + dw);
1979 if (m_ynplan[1]) bxmax = std::min(bxmax, m_coplan[1] - dw);
1980 if (m_ynplan[2]) bymin = std::max(bymin, m_coplan[2] + dw);
1981 if (m_ynplan[3]) bymax = std::min(bymax, m_coplan[3] - dw);
1982
1983 // If there is a tube, check all corners.
1984 if (m_tube) {
1985 const double d2 = m_cotube2 - dw * dw;
1986 if (d2 < Small) {
1987 std::cerr << m_className << "::ForcesOnWire:\n Diameter of wire " << iw
1988 << " is too large compared to the tube.\n";
1989 return false;
1990 }
1991
1992 double corr = sqrt((bxmin * bxmin + bymin * bymin) / d2);
1993 if (corr > 1.) {
1994 bxmin /= corr;
1995 bymin /= corr;
1996 }
1997 corr = sqrt((bxmin * bxmin + bymax * bymax) / d2);
1998 if (corr > 1.) {
1999 bxmin /= corr;
2000 bymax /= corr;
2001 }
2002 corr = sqrt((bxmax * bxmax + bymin * bymin) / d2);
2003 if (corr > 1.) {
2004 bxmax /= corr;
2005 bymin /= corr;
2006 }
2007 corr = sqrt((bxmax * bxmax + bymax * bymax) / d2);
2008 if (corr > 1) {
2009 bxmax /= corr;
2010 bymax /= corr;
2011 }
2012 }
2013 // Make sure we found a reasonable 'safe area'.
2014 if ((bxmin - wire.x) * (wire.x - bxmax) <= 0 ||
2015 (bymin - wire.y) * (wire.y - bymax) <= 0) {
2016 std::cerr << m_className << "::ForcesOnWire:\n Unable to find an area "
2017 << "free of elements around wire " << iw << ".\n";
2018 return false;
2019 }
2020 // Now set a reasonable scanning range.
2021 double sxmin = bxmin;
2022 double sxmax = bxmax;
2023 double symin = bymin;
2024 double symax = bymax;
2025 if (m_scanRange == ScanningRange::User) {
2026 // User-specified range.
2027 sxmin = wire.x + m_xScanMin;
2028 symin = wire.y + m_yScanMin;
2029 sxmax = wire.x + m_xScanMax;
2030 symax = wire.y + m_yScanMax;
2031 } else if (m_scanRange == ScanningRange::FirstOrder) {
2032 // Get the field and force at the nominal position.
2033 double ex = 0., ey = 0.;
2034 ElectricFieldAtWire(iw, ex, ey);
2035 double fx = -ex * wire.e * Internal2Newton;
2036 double fy = -ey * wire.e * Internal2Newton;
2037 if (m_useGravitationalForce) {
2038 // Mass per unit length [kg / cm].
2039 const double m = 1.e-3 * wire.density * Pi * wire.r * wire.r;
2040 fx -= m_down[0] * GravitationalAcceleration * m;
2041 fy -= m_down[1] * GravitationalAcceleration * m;
2042 }
2043 const double u2 = wire.u * wire.u;
2044 const double shiftx =
2045 -125 * fx * u2 / (GravitationalAcceleration * wire.tension);
2046 const double shifty =
2047 -125 * fy * u2 / (GravitationalAcceleration * wire.tension);
2048 // If 0th order estimate of shift is not small.
2049 const double tol = 0.1 * wire.r;
2050 if (std::abs(shiftx) > tol || std::abs(shifty) > tol) {
2051 sxmin = std::max(bxmin, std::min(wire.x + m_scaleRange * shiftx,
2052 wire.x - shiftx / m_scaleRange));
2053 symin = std::max(bymin, std::min(wire.y + m_scaleRange * shifty,
2054 wire.y - shifty / m_scaleRange));
2055 sxmax = std::min(bxmax, std::max(wire.x + m_scaleRange * shiftx,
2056 wire.x - shiftx / m_scaleRange));
2057 symax = std::min(bymax, std::max(wire.y + m_scaleRange * shifty,
2058 wire.y - shifty / m_scaleRange));
2059 // If one is very small, make the area square within bounds.
2060 if (std::abs(sxmax - sxmin) < 0.1 * std::abs(symax - symin)) {
2061 sxmin = std::max(bxmin, wire.x - 0.5 * std::abs(symax - symin));
2062 sxmax = std::min(bxmax, wire.x + 0.5 * std::abs(symax - symin));
2063 } else if (std::abs(symax - symin) < 0.1 * std::abs(sxmax - sxmin)) {
2064 symin = std::max(bymin, wire.y - 0.5 * std::abs(sxmax - sxmin));
2065 symax = std::min(bymax, wire.y + 0.5 * std::abs(sxmax - sxmin));
2066 }
2067 }
2068 }
2069 if (m_debug) {
2070 std::cout << m_className << "::ForcesOnWire:\n";
2071 std::printf(" Free area %12.5e < x < %12.5e\n", bxmin, bxmax);
2072 std::printf(" %12.5e < y < %12.5e\n", bymin, bymax);
2073 std::printf(" Scan area %12.5e < x < %12.5e\n", sxmin, sxmax);
2074 std::printf(" %12.5e < y < %12.5e\n", symin, symax);
2075 }
2076
2077 xMap.resize(m_nScanX);
2078 const double stepx = (sxmax - sxmin) / (m_nScanX - 1);
2079 for (unsigned int i = 0; i < m_nScanX; ++i) {
2080 xMap[i] = sxmin + i * stepx;
2081 }
2082 yMap.resize(m_nScanY);
2083 const double stepy = (symax - symin) / (m_nScanY - 1);
2084 for (unsigned int i = 0; i < m_nScanY; ++i) {
2085 yMap[i] = symin + i * stepy;
2086 }
2087 // Save the original coordinates of the wire.
2088 const double x0 = wire.x;
2089 const double y0 = wire.y;
2090 // Prepare interpolation tables.
2091 fxMap.assign(m_nScanX, std::vector<double>(m_nScanY, 0.));
2092 fyMap.assign(m_nScanX, std::vector<double>(m_nScanY, 0.));
2093 bool ok = true;
2094 for (unsigned int i = 0; i < m_nScanX; ++i) {
2095 for (unsigned int j = 0; j < m_nScanY; ++j) {
2096 // Get the wire position for this shift.
2097 m_w[iw].x = xMap[i];
2098 m_w[iw].y = yMap[j];
2099 // Verify the current situation.
2100 if (!WireCheck()) {
2101 std::cerr << m_className << "::ForcesOnWire: Wire " << iw << ".\n"
2102 << " Scan involves a disallowed wire position.\n";
2103 ok = false;
2104 continue;
2105 }
2106 // Recompute the charges for this configuration.
2107 if (!Setup()) {
2108 std::cerr << m_className << "::ForcesOnWire: Wire " << iw << ".\n"
2109 << " Failed to compute charges at a scan point.\n";
2110 ok = false;
2111 continue;
2112 }
2113 // Compute the forces.
2114 double ex = 0., ey = 0.;
2115 ElectricFieldAtWire(iw, ex, ey);
2116 fxMap[i][j] = -ex * wire.e * Internal2Newton;
2117 fyMap[i][j] = -ey * wire.e * Internal2Newton;
2118 }
2119 }
2120 // Place the wire back in its shifted position.
2121 m_w[iw].x = x0;
2122 m_w[iw].y = y0;
2123 Setup();
2124 return ok;
2125}
2126
2128 if (n == 0) {
2129 std::cerr << m_className << "::SetNumberOfSteps:\n"
2130 << " Number of steps must be > 0.\n";
2131 return;
2132 }
2133 m_nSteps = n;
2134}
2135
2137 const unsigned int iw, const bool detailed, std::vector<double>& csag,
2138 std::vector<double>& xsag, std::vector<double>& ysag, double& stretch,
2139 const bool print) {
2140 if (!m_cellset && !Prepare()) {
2141 std::cerr << m_className << "::WireDisplacement: Cell not set up.\n";
2142 return false;
2143 }
2144 if (iw >= m_nWires) {
2145 std::cerr << m_className
2146 << "::WireDisplacement: Wire index out of range.\n";
2147 return false;
2148 }
2149 if (m_polar) {
2150 std::cerr << m_className
2151 << "::WireDisplacement: Cannot handle polar cells.\n";
2152 return false;
2153 }
2154 const auto& wire = m_w[iw];
2155 // Save the original coordinates.
2156 const double x0 = wire.x;
2157 const double y0 = wire.y;
2158 // First-order approximation.
2159 if (!Setup()) {
2160 std::cerr << m_className << "::WireDisplacement:\n"
2161 << " Charge calculation failed at central position.\n";
2162 return false;
2163 }
2164
2165 double fx = 0., fy = 0.;
2166 if (m_useElectrostaticForce) {
2167 double ex = 0., ey = 0.;
2168 ElectricFieldAtWire(iw, ex, ey);
2169 fx -= ex * wire.e * Internal2Newton;
2170 fy -= ey * wire.e * Internal2Newton;
2171 }
2172 if (m_useGravitationalForce) {
2173 // Mass per unit length [kg / cm].
2174 const double m = 1.e-3 * wire.density * Pi * wire.r * wire.r;
2175 fx -= m_down[0] * GravitationalAcceleration * m;
2176 fy -= m_down[1] * GravitationalAcceleration * m;
2177 }
2178 const double u2 = wire.u * wire.u;
2179 const double shiftx =
2180 -125 * fx * u2 / (GravitationalAcceleration * wire.tension);
2181 const double shifty =
2182 -125 * fy * u2 / (GravitationalAcceleration * wire.tension);
2183 // Get the elongation from this.
2184 const double s = 4 * sqrt(shiftx * shiftx + shifty * shifty) / wire.u;
2185 double length = wire.u;
2186 if (s > Small) {
2187 const double t = sqrt(1 + s * s);
2188 length *= 0.5 * (t + log(s + t) / s);
2189 }
2190 stretch = (length - wire.u) / wire.u;
2191 if (print) {
2192 std::cout << m_className
2193 << "::WireDisplacement:\n"
2194 << " Forces and displacement in 0th order.\n"
2195 << " Wire information: number = " << iw << "\n"
2196 << " type = " << wire.type << "\n"
2197 << " location = (" << wire.x << ", " << wire.y
2198 << ") cm\n"
2199 << " voltage = " << wire.v << " V\n"
2200 << " length = " << wire.u << " cm\n"
2201 << " tension = " << wire.tension << " g\n"
2202 << " In this position: Fx = " << fx << " N/cm\n"
2203 << " Fy = " << fy << " N/cm\n"
2204 << " x-shift = " << shiftx << " cm\n"
2205 << " y-shift = " << shifty << " cm\n"
2206 << " stretch = " << 100. * stretch << "%\n";
2207 }
2208 if (!detailed) {
2209 csag = {0.};
2210 xsag = {shiftx};
2211 ysag = {shifty};
2212 return true;
2213 }
2214 std::vector<double> xMap(m_nScanX, 0.);
2215 std::vector<double> yMap(m_nScanY, 0.);
2216 std::vector<std::vector<double> > fxMap(m_nScanX,
2217 std::vector<double>(m_nScanY, 0.));
2218 std::vector<std::vector<double> > fyMap(m_nScanX,
2219 std::vector<double>(m_nScanY, 0.));
2220 if (!ForcesOnWire(iw, xMap, yMap, fxMap, fyMap)) {
2221 std::cerr << m_className << "::WireDisplacement:\n"
2222 << " Could not compute the electrostatic force table.\n";
2223 return false;
2224 }
2225 // Compute the detailed wire shift.
2226 if (!SagDetailed(wire, xMap, yMap, fxMap, fyMap, csag, xsag, ysag)) {
2227 std::cerr << m_className << "::WireDisplacement: Wire " << iw << ".\n"
2228 << " Computation of the wire sag failed.\n";
2229 return false;
2230 }
2231 // Verify that the wire is in range.
2232 const double sxmin = xMap.front();
2233 const double sxmax = xMap.back();
2234 const double symin = yMap.front();
2235 const double symax = yMap.back();
2236 const unsigned int nSag = xsag.size();
2237 bool outside = false;
2238 length = 0.;
2239 double xAvg = 0.;
2240 double yAvg = 0.;
2241 double xMax = 0.;
2242 double yMax = 0.;
2243 for (unsigned int i = 0; i < nSag; ++i) {
2244 if (x0 + xsag[i] < sxmin || x0 + xsag[i] > sxmax || y0 + ysag[i] < symin ||
2245 y0 + ysag[i] > symax) {
2246 outside = true;
2247 }
2248 xAvg += xsag[i];
2249 yAvg += ysag[i];
2250 xMax = std::max(xMax, std::abs(xsag[i]));
2251 yMax = std::max(yMax, std::abs(ysag[i]));
2252 if (i == 0) continue;
2253 const double dx = xsag[i] - xsag[i - 1];
2254 const double dy = ysag[i] - ysag[i - 1];
2255 const double dz = csag[i] - csag[i - 1];
2256 length += sqrt(dx * dx + dy * dy + dz * dz);
2257 }
2258 xAvg /= nSag;
2259 yAvg /= nSag;
2260 stretch = (length - wire.u) / wire.u;
2261 // Warn if a point outside the scanning area was found.
2262 if (outside) {
2263 std::cerr
2264 << m_className << "::WireDisplacement: Warning.\n "
2265 << "The wire profile is located partially outside the scanning area.\n";
2266 }
2267 if (print) {
2268 std::cout << " Sag profile for wire " << iw << ".\n"
2269 << " Point z [cm] x-sag [um] y-sag [um]\n";
2270 for (unsigned int i = 0; i < nSag; ++i) {
2271 std::printf(" %3d %10.4f %10.4f %10.4f\n",
2272 i, csag[i], xsag[i] * 1.e4, ysag[i] * 1.e4);
2273 }
2274 std::printf(" Average sag in x and y: %10.4f and %10.4f micron\n",
2275 1.e4 * xAvg, 1.e4 * yAvg);
2276 std::printf(" Maximum sag in x and y: %10.4f and %10.4f micron\n",
2277 1.e4 * xMax, 1.e4 * yMax);
2278 std::cout << " Elongation: " << 100. * stretch << "%\n";
2279 }
2280 return true;
2281}
2282
2283int ComponentAnalyticField::Field(const double xin, const double yin,
2284 const double zin, double& ex, double& ey,
2285 double& ez, double& volt, const bool opt) {
2286 //-----------------------------------------------------------------------
2287 // EFIELD - Subroutine calculating the electric field and the potential
2288 // at a given place. It makes use of the routines POT...,
2289 // depending on the type of the cell.
2290 // VARIABLES : XPOS : x-coordinate of the place where the field
2291 // is to be calculated.
2292 // YPOS, ZPOS : y- and z-coordinates
2293 // EX, EY, EZ : x-, y-, z-component of the electric field.
2294 // VOLT : potential at (XPOS,YPOS).
2295 // IOPT : 1 if both E and V are required, 0 if only E
2296 // is to be computed.
2297 // ILOC : Tells where the point is located (0: normal
2298 // I > 0: in wire I, -1: outside a plane,
2299 // -5: in a material, -6: outside the mesh,
2300 // -10: unknown potential).
2301 // (Last changed on 28/ 9/07.)
2302 //-----------------------------------------------------------------------
2303
2304 // Initialise the field for returns without actual calculations.
2305 ex = ey = ez = volt = 0.;
2306
2307 // Make sure the charges have been calculated.
2308 if (!m_cellset && !Prepare()) return -11;
2309
2310 double xpos = xin, ypos = yin;
2311 if (m_polar) Cartesian2Internal(xin, yin, xpos, ypos);
2312 // In case of periodicity, move the point into the basic cell.
2313 if (m_perx) {
2314 xpos -= m_sx * int(round(xin / m_sx));
2315 }
2316 double arot = 0.;
2317 if (m_pery && m_tube) {
2318 Cartesian2Polar(xin, yin, xpos, ypos);
2319 arot = RadToDegree * m_sy * int(round(DegreeToRad * ypos / m_sy));
2320 ypos -= arot;
2321 Polar2Cartesian(xpos, ypos, xpos, ypos);
2322 } else if (m_pery) {
2323 ypos -= m_sy * int(round(ypos / m_sy));
2324 }
2325
2326 // Move the point to the correct side of the plane.
2327 if (m_perx && m_ynplan[0] && xpos <= m_coplan[0]) xpos += m_sx;
2328 if (m_perx && m_ynplan[1] && xpos >= m_coplan[1]) xpos -= m_sx;
2329 if (m_pery && m_ynplan[2] && ypos <= m_coplan[2]) ypos += m_sy;
2330 if (m_pery && m_ynplan[3] && ypos >= m_coplan[3]) ypos -= m_sy;
2331
2332 // In case (XPOS,YPOS) is located behind a plane there is no field.
2333 if (m_tube) {
2334 if (!InTube(xpos, ypos, m_cotube, m_ntube)) {
2335 volt = m_vttube;
2336 return -4;
2337 }
2338 } else {
2339 if (m_ynplan[0] && xpos < m_coplan[0]) {
2340 volt = m_vtplan[0];
2341 return -4;
2342 }
2343 if (m_ynplan[1] && xpos > m_coplan[1]) {
2344 volt = m_vtplan[1];
2345 return -4;
2346 }
2347 if (m_ynplan[2] && ypos < m_coplan[2]) {
2348 volt = m_vtplan[2];
2349 return -4;
2350 }
2351 if (m_ynplan[3] && ypos > m_coplan[3]) {
2352 volt = m_vtplan[3];
2353 return -4;
2354 }
2355 }
2356
2357 // If (xpos, ypos) is within a wire, there is no field either.
2358 for (int i = m_nWires; i--;) {
2359 double dx = xpos - m_w[i].x;
2360 double dy = ypos - m_w[i].y;
2361 // Correct for periodicities.
2362 if (m_perx) dx -= m_sx * int(round(dx / m_sx));
2363 if (m_pery) dy -= m_sy * int(round(dy / m_sy));
2364 // Check the actual position.
2365 if (dx * dx + dy * dy < m_w[i].r * m_w[i].r) {
2366 volt = m_w[i].v;
2367 return i + 1;
2368 }
2369 }
2370
2371 // Call the appropriate potential calculation function.
2372 switch (m_cellType) {
2373 case A00:
2374 FieldA00(xpos, ypos, ex, ey, volt, opt);
2375 break;
2376 case B1X:
2377 FieldB1X(xpos, ypos, ex, ey, volt, opt);
2378 break;
2379 case B1Y:
2380 FieldB1Y(xpos, ypos, ex, ey, volt, opt);
2381 break;
2382 case B2X:
2383 FieldB2X(xpos, ypos, ex, ey, volt, opt);
2384 break;
2385 case B2Y:
2386 FieldB2Y(xpos, ypos, ex, ey, volt, opt);
2387 break;
2388 case C10:
2389 FieldC10(xpos, ypos, ex, ey, volt, opt);
2390 break;
2391 case C2X:
2392 FieldC2X(xpos, ypos, ex, ey, volt, opt);
2393 break;
2394 case C2Y:
2395 FieldC2Y(xpos, ypos, ex, ey, volt, opt);
2396 break;
2397 case C30:
2398 FieldC30(xpos, ypos, ex, ey, volt, opt);
2399 break;
2400 case D10:
2401 FieldD10(xpos, ypos, ex, ey, volt, opt);
2402 break;
2403 case D20:
2404 FieldD20(xpos, ypos, ex, ey, volt, opt);
2405 break;
2406 case D30:
2407 FieldD30(xpos, ypos, ex, ey, volt, opt);
2408 break;
2409 default:
2410 // Unknown cell type
2411 std::cerr << m_className << "::Field:\n";
2412 std::cerr << " Unknown cell type (id " << m_cellType << ")\n";
2413 return -10;
2414 }
2415
2416 // Add dipole terms if requested
2417 if (m_dipole) {
2418 double exd = 0., eyd = 0., voltd = 0.;
2419 switch (m_cellType) {
2420 case A00:
2421 DipoleFieldA00(xpos, ypos, exd, eyd, voltd, opt);
2422 break;
2423 case B1X:
2424 DipoleFieldB1X(xpos, ypos, exd, eyd, voltd, opt);
2425 break;
2426 case B1Y:
2427 DipoleFieldB1Y(xpos, ypos, exd, eyd, voltd, opt);
2428 break;
2429 case B2X:
2430 DipoleFieldB2X(xpos, ypos, exd, eyd, voltd, opt);
2431 break;
2432 case B2Y:
2433 DipoleFieldB2Y(xpos, ypos, exd, eyd, voltd, opt);
2434 break;
2435 default:
2436 break;
2437 }
2438 ex += exd;
2439 ey += eyd;
2440 volt += voltd;
2441 }
2442
2443 // Rotate the field in some special cases.
2444 if (m_pery && m_tube) {
2445 double xaux, yaux;
2446 Cartesian2Polar(ex, ey, xaux, yaux);
2447 yaux += arot;
2448 Polar2Cartesian(xaux, yaux, ex, ey);
2449 }
2450
2451 // Correct for the equipotential planes.
2452 ex -= m_corvta;
2453 ey -= m_corvtb;
2454 volt += m_corvta * xpos + m_corvtb * ypos + m_corvtc;
2455
2456 // Add three dimensional point charges.
2457 if (!m_ch3d.empty()) {
2458 double ex3d = 0., ey3d = 0., ez3d = 0., volt3d = 0.;
2459 switch (m_cellType) {
2460 case A00:
2461 case B1X:
2462 case B1Y:
2463 Field3dA00(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2464 break;
2465 case B2X:
2466 Field3dB2X(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2467 break;
2468 case B2Y:
2469 Field3dB2Y(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2470 break;
2471 case D10:
2472 Field3dD10(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2473 break;
2474 default:
2475 Field3dA00(xin, yin, zin, ex3d, ey3d, ez3d, volt3d);
2476 break;
2477 }
2478 ex += ex3d;
2479 ey += ey3d;
2480 ez += ez3d;
2481 volt += volt3d;
2482 }
2483
2484 if (m_polar) {
2485 const double r = exp(xpos);
2486 const double er = ex / r;
2487 const double ep = ey / r;
2488 const double theta = atan2(yin, xin);
2489 const double ct = cos(theta);
2490 const double st = sin(theta);
2491 ex = +ct * er - st * ep;
2492 ey = +st * er + ct * ep;
2493 }
2494 return 0;
2495}
2496
2497void ComponentAnalyticField::CellInit() {
2498
2499 m_cellset = false;
2500 m_sigset = false;
2501
2502 // Coordinate system
2503 m_polar = false;
2504
2505 // Cell type
2506 m_cellType = A00;
2507
2508 // Bounding box and voltage range.
2509 m_xmin = m_xmax = 0.;
2510 m_ymin = m_ymax = 0.;
2511 m_zmin = m_zmax = 0.;
2512 m_vmin = m_vmax = 0.;
2513
2514 // Periodicities
2515 m_perx = m_pery = false;
2516 m_periodic[0] = m_periodic[1] = false;
2517 m_sx = m_sy = 1.;
2518
2519 // Signals
2520 m_nFourier = 1;
2521 m_cellTypeFourier = A00;
2522 m_fperx = false;
2523 m_fpery = false;
2524 m_mxmin = 0;
2525 m_mxmax = 0;
2526 m_mymin = 0;
2527 m_mymax = 0;
2528 m_mfexp = 0;
2529
2530 m_readout.clear();
2531
2532 // Wires.
2533 m_nWires = 0;
2534 m_w.clear();
2535
2536 // Dipole settings
2537 m_dipole = false;
2538 m_cosph2.clear();
2539 m_sinph2.clear();
2540 m_amp2.clear();
2541
2542 // B2 type cells
2543 m_b2sin.clear();
2544 // C type cells
2545 m_mode = 0;
2546 m_zmult = std::complex<double>(0., 0.);
2547 m_p1 = m_p2 = m_c1 = 0.;
2548 // D3 type cells
2549 wmap.clear();
2550 m_kappa = 0.;
2551
2552 // Reference potential
2553 m_v0 = 0.;
2554 m_corvta = m_corvtb = m_corvtc = 0.;
2555
2556 // Planes
2557 for (int i = 0; i < 5; ++i) {
2558 m_planes[i].type = '?';
2559 m_planes[i].ind = -1;
2560 m_planes[i].ewxcor = 0.;
2561 m_planes[i].ewycor = 0.;
2562 m_planes[i].strips1.clear();
2563 m_planes[i].strips2.clear();
2564 m_planes[i].pixels.clear();
2565 }
2566 for (int i = 0; i < 4; ++i) {
2567 m_ynplan[i] = false;
2568 m_coplan[i] = 0.;
2569 m_vtplan[i] = 0.;
2570 }
2571 // Plane shorthand
2572 m_ynplax = m_ynplay = false;
2573 m_coplax = m_coplay = 1.;
2574
2575 // Tube properties
2576 m_tube = false;
2577 m_ntube = 0;
2578 m_mtube = 1;
2579 m_cotube = 1.;
2580 m_cotube2 = 1.;
2581 m_vttube = 0.;
2582
2583 // Capacitance matrices
2584 m_a.clear();
2585 m_sigmat.clear();
2586 m_qplane.clear();
2587
2588 // 3D charges
2589 m_ch3d.clear();
2590
2591 // Gravity
2592 m_down = {0, 0, 1};
2593}
2594
2595bool ComponentAnalyticField::Prepare() {
2596 std::lock_guard<std::mutex> guard(m_mutex);
2597 // Check that the cell makes sense.
2598 if (!CellCheck()) {
2599 std::cerr << m_className << "::Prepare:\n"
2600 << " The cell does not meet the requirements.\n";
2601 return false;
2602 }
2603 if (m_debug) std::cout << m_className << "::Prepare: Cell check ok.\n";
2604
2605 // Determine the cell type.
2606 if (!CellType()) {
2607 std::cerr << m_className << "::Prepare:\n"
2608 << " Type identification of the cell failed.\n";
2609 return false;
2610 }
2611 if (m_debug) {
2612 std::cout << m_className << "::Prepare:\n"
2613 << " Cell is of type " << CellType() << ".\n";
2614 }
2615
2616 // Calculate the charges.
2617 if (!Setup()) {
2618 std::cerr << m_className << "::Prepare: Calculation of charges failed.\n";
2619 return false;
2620 }
2621 if (m_debug) {
2622 std::cout << m_className << "::Prepare:\n"
2623 << " Calculation of charges was successful.\n";
2624 }
2625
2626 // Assign default gaps for strips and pixels.
2627 if (!PrepareStrips()) {
2628 std::cerr << m_className << "::Prepare: Strip/pixel preparation failed.\n";
2629 return false;
2630 }
2631
2632 m_cellset = true;
2633
2634 // Add dipole terms if required
2635 if (m_dipole) {
2636 if (!SetupDipoleTerms()) {
2637 std::cerr << m_className << "::Prepare:\n"
2638 << " Computing the dipole moments failed.\n";
2639 m_dipole = false;
2640 }
2641 }
2642 return true;
2643}
2644
2645bool ComponentAnalyticField::CellCheck() {
2646 //-----------------------------------------------------------------------
2647 // CELCHK - Subroutine checking the wire positions, The equipotential
2648 // planes and the periodicity. Two planes having different
2649 // voltages are not allowed to have a common line, wires are
2650 // not allowed to be at the same position etc.
2651 // This routine determines also the cell-dimensions.
2652 // VARIABLE : WRONG(I) : .TRUE. if wire I will be removed
2653 // IPLAN. : Number of wires with coord > than plane .
2654 // (Last changed on 16/ 2/05.)
2655 //-----------------------------------------------------------------------
2656
2657 // Checks on the planes, first move the x planes to the basic cell.
2658 if (m_perx) {
2659 const std::string xr = m_polar ? "r" : "x";
2660 double conew1 = m_coplan[0] - m_sx * int(round(m_coplan[0] / m_sx));
2661 double conew2 = m_coplan[1] - m_sx * int(round(m_coplan[1] / m_sx));
2662 // Check that they are not one on top of the other.
2663 if (m_ynplan[0] && m_ynplan[1] && conew1 == conew2) {
2664 if (conew1 > 0.)
2665 conew1 -= m_sx;
2666 else
2667 conew2 += m_sx;
2668 }
2669 // Print some warnings if the planes have been moved.
2670 if ((conew1 != m_coplan[0] && m_ynplan[0]) ||
2671 (conew2 != m_coplan[1] && m_ynplan[1])) {
2672 std::cout << m_className << "::CellCheck:\n The planes in "
2673 << xr << " are moved to the basic period.\n"
2674 << " This should not affect the results.\n";
2675 }
2676 m_coplan[0] = conew1;
2677 m_coplan[1] = conew2;
2678
2679 // Two planes should now be separated by SX, cancel PERX if not.
2680 if (m_ynplan[0] && m_ynplan[1] && fabs(m_coplan[1] - m_coplan[0]) != m_sx) {
2681 std::cerr << m_className << "::CellCheck:\n The separation of the "
2682 << xr << " planes does not match the period.\n"
2683 << " The periodicity is cancelled.\n";
2684 m_perx = false;
2685 }
2686 // If there are two planes left, they should have identical V's.
2687 if (m_ynplan[0] && m_ynplan[1] && m_vtplan[0] != m_vtplan[1]) {
2688 std::cerr << m_className << "::CellCheck:\n The voltages of the two "
2689 << xr << " planes differ.\n"
2690 << " The periodicity is cancelled.\n";
2691 m_perx = false;
2692 }
2693 }
2694
2695 // Idem for the y or phi planes: move them to the basic period.
2696 if (m_pery) {
2697 const std::string yp = m_polar ? "phi" : "y";
2698 double conew3 = m_coplan[2] - m_sy * int(round(m_coplan[2] / m_sy));
2699 double conew4 = m_coplan[3] - m_sy * int(round(m_coplan[3] / m_sy));
2700 // Check that they are not one on top of the other.
2701 if (m_ynplan[2] && m_ynplan[3] && conew3 == conew4) {
2702 if (conew3 > 0.)
2703 conew3 -= m_sy;
2704 else
2705 conew4 += m_sy;
2706 }
2707 // Print some warnings if the planes have been moved.
2708 if ((conew3 != m_coplan[2] && m_ynplan[2]) ||
2709 (conew4 != m_coplan[3] && m_ynplan[3])) {
2710 std::cout << m_className << "::CellCheck:\n The planes in "
2711 << yp << " are moved to the basic period.\n"
2712 << " This should not affect the results.\n";
2713 }
2714 m_coplan[2] = conew3;
2715 m_coplan[3] = conew4;
2716
2717 // Two planes should now be separated by SY, cancel PERY if not.
2718 if (m_ynplan[2] && m_ynplan[3] && fabs(m_coplan[3] - m_coplan[2]) != m_sy) {
2719 std::cerr << m_className << "::CellCheck:\n The separation of the two "
2720 << yp << " planes does not match the period.\n"
2721 << " The periodicity is cancelled.\n";
2722 m_pery = false;
2723 }
2724 // If there are two planes left, they should have identical V's.
2725 if (m_ynplan[2] && m_ynplan[3] && m_vtplan[2] != m_vtplan[3]) {
2726 std::cerr << m_className << "::CellCheck:\n The voltages of the two "
2727 << yp << " planes differ.\n"
2728 << " The periodicity is cancelled.\n";
2729 m_pery = false;
2730 }
2731 }
2732
2733 // Check that there is no voltage conflict of crossing planes.
2734 for (int i = 0; i < 2; ++i) {
2735 for (int j = 2; j < 3; ++j) {
2736 if (m_ynplan[i] && m_ynplan[j] && m_vtplan[i] != m_vtplan[j]) {
2737 const std::string yp = m_polar ? "phi" : "y";
2738 std::cerr << m_className << "::CellCheck:\n"
2739 << " Conflicting potential of two crossing planes.\n"
2740 << " One " << yp << " plane is removed.\n";
2741 m_ynplan[j] = false;
2742 }
2743 }
2744 }
2745
2746 // Make sure the coordinates of the planes are properly ordered.
2747 for (int i = 0; i < 3; i += 2) {
2748 if (m_ynplan[i] && m_ynplan[i + 1]) {
2749 if (m_coplan[i] == m_coplan[i + 1]) {
2750 std::cerr << m_className << "::CellCheck:\n"
2751 << " Two planes are on top of each other.\n"
2752 << " One of them is removed.\n";
2753 m_ynplan[i + 1] = false;
2754 }
2755 if (m_coplan[i] > m_coplan[i + 1]) {
2756 if (m_debug) {
2757 std::cout << m_className << "::CellCheck:\n Planes "
2758 << i << " and " << i + 1 << " are interchanged.\n";
2759 }
2760 // Interchange the two planes.
2761 const double cohlp = m_coplan[i];
2762 m_coplan[i] = m_coplan[i + 1];
2763 m_coplan[i + 1] = cohlp;
2764
2765 const double vthlp = m_vtplan[i];
2766 m_vtplan[i] = m_vtplan[i + 1];
2767 m_vtplan[i + 1] = vthlp;
2768
2769 Plane plahlp = m_planes[i];
2770 m_planes[i] = m_planes[i + 1];
2771 m_planes[i + 1] = plahlp;
2772 }
2773 }
2774 }
2775
2776 // Checks on the wires, start moving them to the basic x period.
2777 if (m_perx) {
2778 for (auto& wire : m_w) {
2779 double xnew = wire.x - m_sx * int(round(wire.x / m_sx));
2780 if (m_ynplan[0] && xnew <= m_coplan[0]) xnew += m_sx;
2781 if (m_ynplan[1] && xnew >= m_coplan[1]) xnew -= m_sx;
2782 if (fabs(xnew - wire.x) > 1.e-8) {
2783 double xprt = wire.x;
2784 double yprt = wire.y;
2785 if (m_polar) Internal2Polar(wire.x, wire.y, xprt, yprt);
2786 const std::string xr = m_polar ? "r" : "x";
2787 std::cout << m_className << "::CellCheck:\n The " << wire.type
2788 << "-wire at (" << xprt << ", " << yprt
2789 << ") is moved to the basic " << xr << " period.\n"
2790 << " This should not affect the results.\n";
2791 }
2792 wire.x = xnew;
2793 }
2794 }
2795
2796 // In case of y-periodicity, all wires should be in the first y-period.
2797 if (m_tube && m_pery) {
2798 for (unsigned int i = 0; i < m_nWires; ++i) {
2799 double xnew = m_w[i].x;
2800 double ynew = m_w[i].y;
2801 Cartesian2Polar(xnew, ynew, xnew, ynew);
2802 if (int(round(DegreeToRad * ynew / m_sy)) != 0) {
2803 std::cout << m_className << "::CellCheck:\n";
2804 std::cout << " The " << m_w[i].type << "-wire at (" << m_w[i].x
2805 << ", " << m_w[i].y
2806 << ") is moved to the basic phi period.\n";
2807 std::cout << " This should not affect the results.\n";
2808 ynew -= RadToDegree * m_sy * int(round(DegreeToRad * ynew / m_sy));
2809 Polar2Cartesian(xnew, ynew, m_w[i].x, m_w[i].y);
2810 }
2811 }
2812 } else if (m_pery) {
2813 for (auto& wire : m_w) {
2814 double ynew = wire.y - m_sy * int(round(wire.y / m_sy));
2815 if (m_ynplan[2] && ynew <= m_coplan[2]) ynew += m_sy;
2816 if (m_ynplan[3] && ynew >= m_coplan[3]) ynew -= m_sy;
2817 if (fabs(ynew - wire.y) > 1.e-8) {
2818 double xprt = wire.x;
2819 double yprt = wire.y;
2820 if (m_polar) Internal2Polar(wire.x, wire.y, xprt, yprt);
2821 const std::string yp = m_polar ? "phi" : "y";
2822 std::cout << m_className << "::CellCheck:\n The " << wire.type
2823 << "-wire at (" << xprt << ", " << yprt
2824 << ") is moved to the basic " << yp << " period.\n"
2825 << " This should not affect the results.\n";
2826 }
2827 wire.y = ynew;
2828 }
2829 }
2830
2831 // Make sure the plane numbering is standard: P1 wires P2, P3 wires P4.
2832 int iplan1 = 0, iplan2 = 0, iplan3 = 0, iplan4 = 0;
2833 for (const auto& wire : m_w) {
2834 if (m_ynplan[0] && wire.x <= m_coplan[0]) ++iplan1;
2835 if (m_ynplan[1] && wire.x <= m_coplan[1]) ++iplan2;
2836 if (m_ynplan[2] && wire.y <= m_coplan[2]) ++iplan3;
2837 if (m_ynplan[3] && wire.y <= m_coplan[3]) ++iplan4;
2838 }
2839
2840 // Find out whether smaller (-1) or larger (+1) coord. are to be kept.
2841 const int imid = int(m_nWires) / 2;
2842 if (m_ynplan[0] && m_ynplan[1]) {
2843 if (iplan1 > imid) {
2844 m_ynplan[1] = false;
2845 iplan1 = -1;
2846 } else {
2847 iplan1 = +1;
2848 }
2849 if (iplan2 < imid) {
2850 m_ynplan[0] = false;
2851 iplan2 = +1;
2852 } else {
2853 iplan2 = -1;
2854 }
2855 }
2856 if (m_ynplan[0] && !m_ynplan[1]) {
2857 if (iplan1 > imid) {
2858 iplan1 = -1;
2859 } else {
2860 iplan1 = +1;
2861 }
2862 }
2863 if (m_ynplan[1] && !m_ynplan[0]) {
2864 if (iplan2 < imid) {
2865 iplan2 = +1;
2866 } else {
2867 iplan2 = -1;
2868 }
2869 }
2870
2871 if (m_ynplan[2] && m_ynplan[3]) {
2872 if (iplan3 > imid) {
2873 m_ynplan[3] = false;
2874 iplan3 = -1;
2875 } else {
2876 iplan3 = +1;
2877 }
2878 if (iplan4 < imid) {
2879 m_ynplan[2] = false;
2880 iplan4 = +1;
2881 } else {
2882 iplan4 = -1;
2883 }
2884 }
2885 if (m_ynplan[2] && !m_ynplan[3]) {
2886 if (iplan3 > imid) {
2887 iplan3 = -1;
2888 } else {
2889 iplan3 = +1;
2890 }
2891 }
2892 if (m_ynplan[3] && !m_ynplan[2]) {
2893 if (iplan4 < imid) {
2894 iplan4 = +1;
2895 } else {
2896 iplan4 = -1;
2897 }
2898 }
2899
2900 // Adapt the numbering of the planes if necessary.
2901 if (iplan1 == -1) {
2902 m_ynplan[0] = false;
2903 m_ynplan[1] = true;
2904 m_coplan[1] = m_coplan[0];
2905 m_vtplan[1] = m_vtplan[0];
2906 m_planes[1] = m_planes[0];
2907 }
2908
2909 if (iplan2 == +1) {
2910 m_ynplan[1] = false;
2911 m_ynplan[0] = true;
2912 m_coplan[0] = m_coplan[1];
2913 m_vtplan[0] = m_vtplan[1];
2914 m_planes[0] = m_planes[1];
2915 }
2916
2917 if (iplan3 == -1) {
2918 m_ynplan[2] = false;
2919 m_ynplan[3] = true;
2920 m_coplan[3] = m_coplan[2];
2921 m_vtplan[3] = m_vtplan[2];
2922 m_planes[3] = m_planes[2];
2923 }
2924
2925 if (iplan4 == +1) {
2926 m_ynplan[3] = false;
2927 m_ynplan[2] = true;
2928 m_coplan[2] = m_coplan[3];
2929 m_vtplan[2] = m_vtplan[3];
2930 m_planes[2] = m_planes[3];
2931 }
2932
2933 std::vector<bool> wrong(m_nWires, false);
2934 // Second pass for the wires, check position relative to the planes.
2935 for (unsigned int i = 0; i < m_nWires; ++i) {
2936 const double rw = m_w[i].r;
2937 const double dw = 2. * rw;
2938 if (m_ynplan[0] && m_w[i].x - rw <= m_coplan[0]) wrong[i] = true;
2939 if (m_ynplan[1] && m_w[i].x + rw >= m_coplan[1]) wrong[i] = true;
2940 if (m_ynplan[2] && m_w[i].y - rw <= m_coplan[2]) wrong[i] = true;
2941 if (m_ynplan[3] && m_w[i].y + rw >= m_coplan[3]) wrong[i] = true;
2942 if (m_tube) {
2943 if (!InTube(m_w[i].x, m_w[i].y, m_cotube, m_ntube)) {
2944 std::cerr << m_className << "::CellCheck:\n";
2945 std::cerr << " The " << m_w[i].type << "-wire at (" << m_w[i].x
2946 << ", " << m_w[i].y << ") is located outside the tube.\n";
2947 std::cerr << " This wire is removed.\n";
2948 wrong[i] = true;
2949 }
2950 } else if (wrong[i]) {
2951 double xprt = m_w[i].x;
2952 double yprt = m_w[i].y;
2953 if (m_polar) Internal2Polar(m_w[i].x, m_w[i].y, xprt, yprt);
2954 std::cerr << m_className << "::CellCheck:\n The " << m_w[i].type
2955 << "-wire at (" << xprt << ", " << yprt << ") is located "
2956 << "outside the planes.\n This wire is removed.\n";
2957 } else if ((m_perx && dw >= m_sx) || (m_pery && dw >= m_sy)) {
2958 double xprt = m_w[i].x;
2959 double yprt = m_w[i].y;
2960 if (m_polar) Internal2Polar(m_w[i].x, m_w[i].y, xprt, yprt);
2961 std::cerr << m_className << "::CellCheck:\n The diameter of the "
2962 << m_w[i].type << "-wire at (" << xprt << ", " << yprt
2963 << ") exceeds 1 period.\n This wire is removed.\n";
2964 wrong[i] = true;
2965 }
2966 }
2967
2968 // Check the wire spacing.
2969 for (unsigned int i = 0; i < m_nWires; ++i) {
2970 if (wrong[i]) continue;
2971 for (unsigned int j = i + 1; j < m_nWires; ++j) {
2972 if (wrong[j]) continue;
2973 double xsepar = 0.;
2974 double ysepar = 0.;
2975 if (m_tube) {
2976 if (m_pery) {
2977 double xaux1, xaux2, yaux1, yaux2;
2978 Cartesian2Polar(m_w[i].x, m_w[i].y, xaux1, yaux1);
2979 Cartesian2Polar(m_w[j].x, m_w[j].y, xaux2, yaux2);
2980 yaux1 -= m_sy * int(round(yaux1 / m_sy));
2981 yaux2 -= m_sy * int(round(yaux2 / m_sy));
2982 Polar2Cartesian(xaux1, yaux1, xaux1, yaux1);
2983 Polar2Cartesian(xaux2, yaux2, xaux2, yaux2);
2984 xsepar = xaux1 - xaux2;
2985 ysepar = yaux1 - yaux2;
2986 } else {
2987 xsepar = m_w[i].x - m_w[j].x;
2988 ysepar = m_w[i].y - m_w[j].y;
2989 }
2990 } else {
2991 xsepar = fabs(m_w[i].x - m_w[j].x);
2992 if (m_perx) xsepar -= m_sx * int(round(xsepar / m_sx));
2993 ysepar = fabs(m_w[i].y - m_w[j].y);
2994 if (m_pery) ysepar -= m_sy * int(round(ysepar / m_sy));
2995 }
2996 const double rij = m_w[i].r + m_w[j].r;
2997 if (xsepar * xsepar + ysepar * ysepar > rij * rij) continue;
2998 double xprti = m_w[i].x;
2999 double yprti = m_w[i].y;
3000 double xprtj = m_w[j].x;
3001 double yprtj = m_w[j].y;
3002 if (m_polar) {
3003 Internal2Polar(m_w[i].x, m_w[i].y, xprti, yprti);
3004 Internal2Polar(m_w[j].x, m_w[j].y, xprtj, yprtj);
3005 }
3006 std::cerr << m_className << "::CellCheck:\n Wires " << m_w[i].type
3007 << " at (" << xprti << ", " << yprti << ") and " << m_w[j].type
3008 << " at (" << xprtj << ", " << yprtj
3009 << ") overlap at least partially.\n"
3010 << " The latter wire is removed.\n";
3011 wrong[j] = true;
3012 }
3013 }
3014
3015 // Remove the wires which are not acceptable for one reason or another.
3016 const int iWires = m_nWires;
3017 m_nWires = 0;
3018 for (int i = 0; i < iWires; ++i) {
3019 if (!wrong[i]) {
3020 m_w[m_nWires] = m_w[i];
3021 ++m_nWires;
3022 }
3023 }
3024
3025 // Ensure that some elements are left.
3026 int nElements = m_nWires;
3027 if (m_ynplan[0]) ++nElements;
3028 if (m_ynplan[1]) ++nElements;
3029 if (m_ynplan[2]) ++nElements;
3030 if (m_ynplan[3]) ++nElements;
3031 if (m_tube) ++nElements;
3032
3033 if (nElements < 2) {
3034 std::cerr << m_className << "::CellCheck:\n";
3035 std::cerr << " At least 2 elements are necessary.\n";
3036 std::cerr << " Cell rejected.\n";
3037 return false;
3038 }
3039
3040 // Determine maximum and minimum coordinates and potentials.
3041 bool setx = false;
3042 bool sety = false;
3043 bool setz = false;
3044 bool setv = false;
3045
3046 m_xmin = m_xmax = 0.;
3047 m_ymin = m_ymax = 0.;
3048 m_zmin = m_zmax = 0.;
3049 m_vmin = m_vmax = 0.;
3050
3051 // Loop over the wires.
3052 for (const auto& wire : m_w) {
3053 const double rw = wire.r;
3054 if (setx) {
3055 m_xmin = std::min(m_xmin, wire.x - rw);
3056 m_xmax = std::max(m_xmax, wire.x + rw);
3057 } else {
3058 m_xmin = wire.x - rw;
3059 m_xmax = wire.x + rw;
3060 setx = true;
3061 }
3062 if (sety) {
3063 m_ymin = std::min(m_ymin, wire.y - rw);
3064 m_ymax = std::max(m_ymax, wire.y + rw);
3065 } else {
3066 m_ymin = wire.y - rw;
3067 m_ymax = wire.y + rw;
3068 sety = true;
3069 }
3070 if (setz) {
3071 m_zmin = std::min(m_zmin, -0.5 * wire.u);
3072 m_zmax = std::max(m_zmax, +0.5 * wire.u);
3073 } else {
3074 m_zmin = -0.5 * wire.u;
3075 m_zmax = +0.5 * wire.u;
3076 setz = true;
3077 }
3078 if (setv) {
3079 m_vmin = std::min(m_vmin, wire.v);
3080 m_vmax = std::max(m_vmax, wire.v);
3081 } else {
3082 m_vmin = m_vmax = wire.v;
3083 setv = true;
3084 }
3085 }
3086 // Consider the planes.
3087 for (int i = 0; i < 4; ++i) {
3088 if (!m_ynplan[i]) continue;
3089 if (i < 2) {
3090 if (setx) {
3091 m_xmin = std::min(m_xmin, m_coplan[i]);
3092 m_xmax = std::max(m_xmax, m_coplan[i]);
3093 } else {
3094 m_xmin = m_xmax = m_coplan[i];
3095 setx = true;
3096 }
3097 } else {
3098 if (sety) {
3099 m_ymin = std::min(m_ymin, m_coplan[i]);
3100 m_ymax = std::max(m_ymax, m_coplan[i]);
3101 } else {
3102 m_ymin = m_ymax = m_coplan[i];
3103 sety = true;
3104 }
3105 }
3106 if (setv) {
3107 m_vmin = std::min(m_vmin, m_vtplan[i]);
3108 m_vmax = std::max(m_vmax, m_vtplan[i]);
3109 } else {
3110 m_vmin = m_vmax = m_vtplan[i];
3111 setv = true;
3112 }
3113 }
3114
3115 // Consider the tube.
3116 if (m_tube) {
3117 m_xmin = -1.1 * m_cotube;
3118 m_xmax = +1.1 * m_cotube;
3119 setx = true;
3120 m_ymin = -1.1 * m_cotube;
3121 m_ymax = +1.1 * m_cotube;
3122 sety = true;
3123 m_vmin = std::min(m_vmin, m_vttube);
3124 m_vmax = std::max(m_vmax, m_vttube);
3125 setv = true;
3126 }
3127
3128 // In case of x-periodicity, XMAX-XMIN should be SX,
3129 if (m_perx && m_sx > (m_xmax - m_xmin)) {
3130 m_xmin = -0.5 * m_sx;
3131 m_xmax = +0.5 * m_sx;
3132 setx = true;
3133 }
3134 // in case of y-periodicity, YMAX-YMIN should be SY,
3135 if (m_pery && m_sy > (m_ymax - m_ymin)) {
3136 m_ymin = -0.5 * m_sy;
3137 m_ymax = +0.5 * m_sy;
3138 sety = true;
3139 }
3140 // in case the cell is polar, the y range should be < 2 pi.
3141 if (m_polar && (m_ymax - m_ymin) >= TwoPi) {
3142 m_ymin = -Pi;
3143 m_ymax = +Pi;
3144 sety = true;
3145 }
3146
3147 // Fill in missing dimensions.
3148 if (setx && m_xmin != m_xmax && (m_ymin == m_ymax || !sety)) {
3149 m_ymin -= 0.5 * fabs(m_xmax - m_xmin);
3150 m_ymax += 0.5 * fabs(m_xmax - m_xmin);
3151 sety = true;
3152 }
3153 if (sety && m_ymin != m_ymax && (m_xmin == m_xmax || !setx)) {
3154 m_xmin -= 0.5 * fabs(m_ymax - m_ymin);
3155 m_xmax += 0.5 * fabs(m_ymax - m_ymin);
3156 setx = true;
3157 }
3158
3159 if (!setz) {
3160 m_zmin = -0.25 * (fabs(m_xmax - m_xmin) + fabs(m_ymax - m_ymin));
3161 m_zmax = +0.25 * (fabs(m_xmax - m_xmin) + fabs(m_ymax - m_ymin));
3162 setz = true;
3163 }
3164
3165 // Ensure that all dimensions are now set.
3166 if (!(setx && sety && setz)) {
3167 std::cerr << m_className << "::CellCheck:\n";
3168 std::cerr << " Unable to establish"
3169 << " default dimensions in all directions.\n";
3170 }
3171
3172 // Check that at least some different voltages are present.
3173 if (m_vmin == m_vmax || !setv) {
3174 std::cerr << m_className << "::CellCheck:\n";
3175 std::cerr << " All potentials in the cell are the same.\n";
3176 std::cerr << " There is no point in going on.\n";
3177 return false;
3178 }
3179
3180 // Cell seems to be alright since it passed all critical tests.
3181 return true;
3182}
3183
3184bool ComponentAnalyticField::WireCheck() const {
3185 //-----------------------------------------------------------------------
3186 // CELWCH - Subroutine checking the wire positions only, contrary
3187 // to CELCHK, this routine does not modify the cell.
3188 //-----------------------------------------------------------------------
3189
3190 if (m_nWires == 0) return false;
3191
3192 if (m_nWires == 1 &&
3193 !(m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3]) && !m_tube) {
3194 return false;
3195 }
3196 // Check position relative to the planes.
3197 for (unsigned int i = 0; i < m_nWires; ++i) {
3198 const auto& wire = m_w[i];
3199 if (m_ynplan[0] && wire.x - wire.r <= m_coplan[0]) return false;
3200 if (m_ynplan[1] && wire.x + wire.r >= m_coplan[1]) return false;
3201 if (m_ynplan[2] && wire.y - wire.r <= m_coplan[2]) return false;
3202 if (m_ynplan[3] && wire.y + wire.r >= m_coplan[3]) return false;
3203 if (m_tube) {
3204 if (!InTube(wire.x, wire.y, m_cotube, m_ntube)) return false;
3205 } else if ((m_perx && 2 * wire.r >= m_sx) ||
3206 (m_pery && 2 * wire.r >= m_sy)) {
3207 return false;
3208 }
3209 }
3210 // Check the wire spacing.
3211 for (unsigned int i = 0; i < m_nWires; ++i) {
3212 const double xi = m_w[i].x;
3213 const double yi = m_w[i].y;
3214 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3215 const double xj = m_w[j].x;
3216 const double yj = m_w[j].y;
3217 double xsepar = std::abs(xi - xj);
3218 double ysepar = std::abs(yi - yj);
3219 if (m_tube) {
3220 if (m_pery) {
3221 double xaux1 = 0., yaux1 = 0.;
3222 double xaux2 = 0., yaux2 = 0.;
3223 Cartesian2Polar(xi, yi, xaux1, yaux1);
3224 Cartesian2Polar(xj, yj, xaux2, yaux2);
3225 yaux1 -= m_sy * int(round(yaux1 / m_sy));
3226 yaux2 -= m_sy * int(round(yaux2 / m_sy));
3227 Polar2Cartesian(xaux1, yaux1, xaux1, yaux1);
3228 Polar2Cartesian(xaux2, yaux2, xaux2, yaux2);
3229 xsepar = xaux1 - xaux2;
3230 ysepar = yaux1 - yaux2;
3231 }
3232 } else {
3233 if (m_perx) xsepar -= m_sx * int(round(xsepar / m_sx));
3234 if (m_pery) ysepar -= m_sy * int(round(ysepar / m_sy));
3235 }
3236 const double rij = m_w[i].r + m_w[j].r;
3237 if (xsepar * xsepar + ysepar * ysepar < rij * rij) return false;
3238 }
3239 }
3240 return true;
3241}
3242
3243bool ComponentAnalyticField::CellType() {
3244 // Tube geometries
3245 if (m_tube) {
3246 if (m_ntube == 0) {
3247 if (m_pery) {
3248 m_cellType = D20;
3249 } else {
3250 m_cellType = D10;
3251 }
3252 } else if (m_ntube >= 3 && m_ntube <= 8) {
3253 if (m_pery) {
3254 m_cellType = D40;
3255 } else {
3256 m_cellType = D30;
3257 }
3258 } else {
3259 std::cerr << m_className << "::CellType:\n"
3260 << " Potentials for tube with " << m_ntube
3261 << " edges are not yet available.\n"
3262 << " Using a round tube instead.\n";
3263 m_ntube = 0;
3264 m_cellType = D30;
3265 }
3266 return true;
3267 }
3268
3269 // Find the 'A' type cell.
3270 if (!(m_perx || m_pery) && !(m_ynplan[0] && m_ynplan[1]) &&
3271 !(m_ynplan[2] && m_ynplan[3])) {
3272 m_cellType = A00;
3273 return true;
3274 }
3275
3276 // Find the 'B1X' type cell.
3277 if (m_perx && !m_pery && !(m_ynplan[0] || m_ynplan[1]) &&
3278 !(m_ynplan[2] && m_ynplan[3])) {
3279 m_cellType = B1X;
3280 return true;
3281 }
3282
3283 // Find the 'B1Y' type cell.
3284 if (m_pery && !m_perx && !(m_ynplan[0] && m_ynplan[1]) &&
3285 !(m_ynplan[2] || m_ynplan[3])) {
3286 m_cellType = B1Y;
3287 return true;
3288 }
3289
3290 // Find the 'B2X' type cell.
3291 if (m_perx && !m_pery && !(m_ynplan[2] && m_ynplan[3])) {
3292 m_cellType = B2X;
3293 return true;
3294 }
3295
3296 if (!(m_perx || m_pery) && !(m_ynplan[2] && m_ynplan[3]) &&
3297 (m_ynplan[0] && m_ynplan[1])) {
3298 m_sx = fabs(m_coplan[1] - m_coplan[0]);
3299 m_cellType = B2X;
3300 return true;
3301 }
3302
3303 // Find the 'B2Y' type cell.
3304 if (m_pery && !m_perx && !(m_ynplan[0] && m_ynplan[1])) {
3305 m_cellType = B2Y;
3306 return true;
3307 }
3308
3309 if (!(m_perx || m_pery) && !(m_ynplan[0] && m_ynplan[1]) &&
3310 (m_ynplan[2] && m_ynplan[3])) {
3311 m_sy = fabs(m_coplan[3] - m_coplan[2]);
3312 m_cellType = B2Y;
3313 return true;
3314 }
3315
3316 // Find the 'C1 ' type cell.
3317 if (!(m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3]) && m_perx &&
3318 m_pery) {
3319 m_cellType = C10;
3320 return true;
3321 }
3322
3323 // Find the 'C2X' type cell.
3324 if (!((m_ynplan[2] && m_pery) || (m_ynplan[2] && m_ynplan[3]))) {
3325 if (m_ynplan[0] && m_ynplan[1]) {
3326 m_sx = fabs(m_coplan[1] - m_coplan[0]);
3327 m_cellType = C2X;
3328 return true;
3329 }
3330 if (m_perx && m_ynplan[0]) {
3331 m_cellType = C2X;
3332 return true;
3333 }
3334 }
3335
3336 // Find the 'C2Y' type cell.
3337 if (!((m_ynplan[0] && m_perx) || (m_ynplan[0] && m_ynplan[1]))) {
3338 if (m_ynplan[2] && m_ynplan[3]) {
3339 m_sy = fabs(m_coplan[3] - m_coplan[2]);
3340 m_cellType = C2Y;
3341 return true;
3342 }
3343 if (m_pery && m_ynplan[2]) {
3344 m_cellType = C2Y;
3345 return true;
3346 }
3347 }
3348
3349 // Find the 'C3 ' type cell.
3350 if (m_perx && m_pery) {
3351 m_cellType = C30;
3352 return true;
3353 }
3354
3355 if (m_perx) {
3356 m_sy = fabs(m_coplan[3] - m_coplan[2]);
3357 m_cellType = C30;
3358 return true;
3359 }
3360
3361 if (m_pery) {
3362 m_sx = fabs(m_coplan[1] - m_coplan[0]);
3363 m_cellType = C30;
3364 return true;
3365 }
3366
3367 if (m_ynplan[0] && m_ynplan[1] && m_ynplan[2] && m_ynplan[3]) {
3368 m_sx = fabs(m_coplan[1] - m_coplan[0]);
3369 m_sy = fabs(m_coplan[3] - m_coplan[2]);
3370 m_cellType = C30;
3371 return true;
3372 }
3373
3374 // Cell is not recognised.
3375 return false;
3376}
3377
3378std::string ComponentAnalyticField::GetCellType(const Cell) const {
3379 switch (m_cellType) {
3380 case A00:
3381 return "A ";
3382 case B1X:
3383 return "B1X";
3384 case B1Y:
3385 return "B1Y";
3386 case B2X:
3387 return "B2X";
3388 case B2Y:
3389 return "B2Y";
3390 case C10:
3391 return "C1 ";
3392 case C2X:
3393 return "C2X";
3394 case C2Y:
3395 return "C2Y";
3396 case C30:
3397 return "C3 ";
3398 case D10:
3399 return "D1 ";
3400 case D20:
3401 return "D2 ";
3402 case D30:
3403 return "D3 ";
3404 case D40:
3405 return "D4 ";
3406 default:
3407 break;
3408 }
3409 return "Unknown";
3410}
3411
3412bool ComponentAnalyticField::PrepareStrips() {
3413 // -----------------------------------------------------------------------
3414 // CELSTR - Assigns default anode-cathode gaps, if applicable.
3415 // (Last changed on 7/12/00.)
3416 // -----------------------------------------------------------------------
3417
3418 double gapDef[4] = {0., 0., 0., 0.};
3419
3420 // Compute default gaps.
3421 if (m_ynplan[0]) {
3422 if (m_ynplan[1]) {
3423 gapDef[0] = m_coplan[1] - m_coplan[0];
3424 } else if (m_nWires <= 0) {
3425 gapDef[0] = -1.;
3426 } else {
3427 gapDef[0] = m_w[0].x - m_coplan[0];
3428 for (const auto& wire : m_w) {
3429 gapDef[0] = std::min(wire.x - m_coplan[0], gapDef[0]);
3430 }
3431 }
3432 }
3433
3434 if (m_ynplan[1]) {
3435 if (m_ynplan[0]) {
3436 gapDef[1] = m_coplan[1] - m_coplan[0];
3437 } else if (m_nWires <= 0) {
3438 gapDef[1] = -1.;
3439 } else {
3440 gapDef[1] = m_coplan[1] - m_w[0].x;
3441 for (const auto& wire : m_w) {
3442 gapDef[1] = std::min(m_coplan[1] - wire.x, gapDef[1]);
3443 }
3444 }
3445 }
3446
3447 if (m_ynplan[2]) {
3448 if (m_ynplan[3]) {
3449 gapDef[2] = m_coplan[3] - m_coplan[2];
3450 } else if (m_nWires <= 0) {
3451 gapDef[2] = -1.;
3452 } else {
3453 gapDef[2] = m_w[0].y - m_coplan[2];
3454 for (const auto& wire : m_w) {
3455 gapDef[2] = std::min(wire.y - m_coplan[2], gapDef[2]);
3456 }
3457 }
3458 }
3459
3460 if (m_ynplan[3]) {
3461 if (m_ynplan[2]) {
3462 gapDef[3] = m_coplan[3] - m_coplan[2];
3463 } else if (m_nWires <= 0) {
3464 gapDef[3] = -1.;
3465 } else {
3466 gapDef[3] = m_coplan[3] - m_w[0].y;
3467 for (const auto& wire : m_w) {
3468 gapDef[3] = std::min(m_coplan[3] - wire.y, gapDef[3]);
3469 }
3470 }
3471 }
3472
3473 // Assign.
3474 for (unsigned int i = 0; i < 4; ++i) {
3475 for (auto& strip : m_planes[i].strips1) {
3476 if (strip.gap < 0. && gapDef[i] < 0.) {
3477 std::cerr << m_className << "::PrepareStrips:\n"
3478 << " Not able to set a default anode-cathode gap\n";
3479 if (m_polar) {
3480 std::cerr << " for r/phi-strips of plane " << i << ".\n";
3481 } else {
3482 std::cerr << " for x/y-strips of plane " << i << ".\n";
3483 }
3484 return false;
3485 }
3486 if (strip.gap < 0.) {
3487 strip.gap = gapDef[i];
3488 } else if (m_polar && i < 2) {
3489 if (i == 0) {
3490 strip.gap = log1p(strip.gap / exp(m_coplan[i]));
3491 } else {
3492 strip.gap = -log1p(-strip.gap / exp(m_coplan[i]));
3493 }
3494 }
3495 }
3496 for (auto& strip : m_planes[i].strips2) {
3497 if (strip.gap < 0. && gapDef[i] < 0.) {
3498 std::cerr << m_className << "::PrepareStrips:\n"
3499 << " Not able to set a default anode-cathode gap\n"
3500 << " for z-strips of plane " << i << ".\n";
3501 return false;
3502 }
3503 if (strip.gap < 0.) {
3504 strip.gap = gapDef[i];
3505 } else if (m_polar && i < 2) {
3506 if (i == 0) {
3507 strip.gap = log1p(strip.gap / exp(m_coplan[i]));
3508 } else {
3509 strip.gap = -log1p(-strip.gap / exp(m_coplan[i]));
3510 }
3511 }
3512 }
3513 for (auto& pixel : m_planes[i].pixels) {
3514 if (pixel.gap < 0. && gapDef[i] < 0.) {
3515 std::cerr << m_className << "::PrepareStrips:\n"
3516 << " Not able to set a default anode-cathode gap\n"
3517 << " for pixels on plane " << i << ".\n";
3518 return false;
3519 }
3520 if (pixel.gap < 0.) {
3521 pixel.gap = gapDef[i];
3522 } else if (m_polar && i < 2) {
3523 if (i == 0) {
3524 pixel.gap = log1p(pixel.gap / exp(m_coplan[i]));
3525 } else {
3526 pixel.gap = -log1p(-pixel.gap / exp(m_coplan[i]));
3527 }
3528 }
3529 }
3530 }
3531
3532 return true;
3533}
3534
3535void ComponentAnalyticField::AddReadout(const std::string& label) {
3536 // Check if this readout group already exists.
3537 if (std::find(m_readout.begin(), m_readout.end(), label) != m_readout.end()) {
3538 std::cout << m_className << "::AddReadout:\n";
3539 std::cout << " Readout group " << label << " already exists.\n";
3540 return;
3541 }
3542 m_readout.push_back(label);
3543
3544 unsigned int nWiresFound = 0;
3545 for (const auto& wire : m_w) {
3546 if (wire.type == label) ++nWiresFound;
3547 }
3548
3549 unsigned int nPlanesFound = 0;
3550 unsigned int nStripsFound = 0;
3551 unsigned int nPixelsFound = 0;
3552 for (int i = 0; i < 5; ++i) {
3553 if (m_planes[i].type == label) ++nPlanesFound;
3554 for (const auto& strip : m_planes[i].strips1) {
3555 if (strip.type == label) ++nStripsFound;
3556 }
3557 for (const auto& strip : m_planes[i].strips2) {
3558 if (strip.type == label) ++nStripsFound;
3559 }
3560 for (const auto& pixel : m_planes[i].pixels) {
3561 if (pixel.type == label) ++nPixelsFound;
3562 }
3563 }
3564
3565 if (nWiresFound == 0 && nPlanesFound == 0 && nStripsFound == 0 &&
3566 nPixelsFound == 0) {
3567 std::cerr << m_className << "::AddReadout:\n";
3568 std::cerr << " At present there are no wires, planes or strips\n";
3569 std::cerr << " associated to readout group " << label << ".\n";
3570 } else {
3571 std::cout << m_className << "::AddReadout:\n";
3572 std::cout << " Readout group " << label << " comprises:\n";
3573 if (nWiresFound > 1) {
3574 std::cout << " " << nWiresFound << " wires\n";
3575 } else if (nWiresFound == 1) {
3576 std::cout << " 1 wire\n";
3577 }
3578 if (nPlanesFound > 1) {
3579 std::cout << " " << nPlanesFound << " planes\n";
3580 } else if (nPlanesFound == 1) {
3581 std::cout << " 1 plane\n";
3582 }
3583 if (nStripsFound > 1) {
3584 std::cout << " " << nStripsFound << " strips\n";
3585 } else if (nStripsFound == 1) {
3586 std::cout << " 1 strip\n";
3587 }
3588 if (nPixelsFound > 1) {
3589 std::cout << " " << nPixelsFound << " pixels\n";
3590 } else if (nPixelsFound == 1) {
3591 std::cout << " 1 pixel\n";
3592 }
3593 }
3594
3595 m_sigset = false;
3596}
3597
3598bool ComponentAnalyticField::Setup() {
3599 //-----------------------------------------------------------------------
3600 // SETUP - Routine calling the appropriate setup routine.
3601 // (Last changed on 19/ 9/07.)
3602 //-----------------------------------------------------------------------
3603
3604 // Set a separate set of plane variables to avoid repeated loops.
3605 if (m_ynplan[0]) {
3606 m_coplax = m_coplan[0];
3607 m_ynplax = true;
3608 } else if (m_ynplan[1]) {
3609 m_coplax = m_coplan[1];
3610 m_ynplax = true;
3611 } else {
3612 m_ynplax = false;
3613 }
3614
3615 if (m_ynplan[2]) {
3616 m_coplay = m_coplan[2];
3617 m_ynplay = true;
3618 } else if (m_ynplan[3]) {
3619 m_coplay = m_coplan[3];
3620 m_ynplay = true;
3621 } else {
3622 m_ynplay = false;
3623 }
3624
3625 // Set the correction parameters for the planes.
3626 if (m_tube) {
3627 m_corvta = 0.;
3628 m_corvtb = 0.;
3629 m_corvtc = m_vttube;
3630 } else if ((m_ynplan[0] && m_ynplan[1]) && !(m_ynplan[2] || m_ynplan[3])) {
3631 m_corvta = (m_vtplan[0] - m_vtplan[1]) / (m_coplan[0] - m_coplan[1]);
3632 m_corvtb = 0.;
3633 m_corvtc = (m_vtplan[1] * m_coplan[0] - m_vtplan[0] * m_coplan[1]) /
3634 (m_coplan[0] - m_coplan[1]);
3635 } else if ((m_ynplan[2] && m_ynplan[3]) && !(m_ynplan[0] || m_ynplan[1])) {
3636 m_corvta = 0.;
3637 m_corvtb = (m_vtplan[2] - m_vtplan[3]) / (m_coplan[2] - m_coplan[3]);
3638 m_corvtc = (m_vtplan[3] * m_coplan[2] - m_vtplan[2] * m_coplan[3]) /
3639 (m_coplan[2] - m_coplan[3]);
3640 } else {
3641 m_corvta = m_corvtb = m_corvtc = 0.;
3642 if (m_ynplan[0]) m_corvtc = m_vtplan[0];
3643 if (m_ynplan[1]) m_corvtc = m_vtplan[1];
3644 if (m_ynplan[2]) m_corvtc = m_vtplan[2];
3645 if (m_ynplan[3]) m_corvtc = m_vtplan[3];
3646 }
3647
3648 // Skip wire calculations if there aren't any.
3649 if (m_nWires <= 0) return true;
3650
3651 // Redimension the capacitance matrix
3652 m_a.assign(m_nWires, std::vector<double>(m_nWires, 0.));
3653
3654 bool ok = true;
3655
3656 // Call the set routine appropriate for the present cell type.
3657 switch (m_cellType) {
3658 case A00:
3659 ok = SetupA00();
3660 break;
3661 case B1X:
3662 ok = SetupB1X();
3663 break;
3664 case B1Y:
3665 ok = SetupB1Y();
3666 break;
3667 case B2X:
3668 ok = SetupB2X();
3669 break;
3670 case B2Y:
3671 ok = SetupB2Y();
3672 break;
3673 case C10:
3674 ok = SetupC10();
3675 break;
3676 case C2X:
3677 ok = SetupC2X();
3678 break;
3679 case C2Y:
3680 ok = SetupC2Y();
3681 break;
3682 case C30:
3683 ok = SetupC30();
3684 break;
3685 case D10:
3686 ok = SetupD10();
3687 break;
3688 case D20:
3689 ok = SetupD20();
3690 break;
3691 case D30:
3692 ok = SetupD30();
3693 break;
3694 default:
3695 std::cerr << m_className << "::Setup: Unknown cell type.\n";
3696 break;
3697 }
3698
3699
3700 m_a.clear();
3701
3702 if (!ok) {
3703 std::cerr << m_className << "::Setup:\n";
3704 std::cerr << " Preparing the cell for field calculations"
3705 << " did not succeed.\n";
3706 return false;
3707 }
3708 return true;
3709}
3710
3711bool ComponentAnalyticField::SetupA00() {
3712 //-----------------------------------------------------------------------
3713 // SETA00 - Subroutine preparing the field calculations by calculating
3714 // the charges on the wires, for the cell with one charge and
3715 // not more than one plane in either x or y.
3716 // The potential used is log(r).
3717 //-----------------------------------------------------------------------
3718
3719 // Loop over all wire combinations.
3720 for (unsigned int i = 0; i < m_nWires; ++i) {
3721 m_a[i][i] = m_w[i].r * m_w[i].r;
3722 // Take care of the equipotential planes.
3723 if (m_ynplax) m_a[i][i] /= 4. * pow(m_w[i].x - m_coplax, 2);
3724 if (m_ynplay) m_a[i][i] /= 4. * pow(m_w[i].y - m_coplay, 2);
3725 // Take care of combinations of equipotential planes.
3726 if (m_ynplax && m_ynplay)
3727 m_a[i][i] *=
3728 4.0 * (pow(m_w[i].x - m_coplax, 2) + pow(m_w[i].y - m_coplay, 2));
3729 // Define the final version of a[i][i].
3730 m_a[i][i] = -0.5 * log(m_a[i][i]);
3731 // Loop over all other wires for the off-diagonal elements.
3732 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3733 m_a[i][j] = pow(m_w[i].x - m_w[j].x, 2) + pow(m_w[i].y - m_w[j].y, 2);
3734 // Take care of equipotential planes.
3735 if (m_ynplax)
3736 m_a[i][j] = m_a[i][j] / (pow(m_w[i].x + m_w[j].x - 2. * m_coplax, 2) +
3737 pow(m_w[i].y - m_w[j].y, 2));
3738 if (m_ynplay)
3739 m_a[i][j] = m_a[i][j] / (pow(m_w[i].x - m_w[j].x, 2) +
3740 pow(m_w[i].y + m_w[j].y - 2. * m_coplay, 2));
3741 // Take care of pairs of equipotential planes in different directions.
3742 if (m_ynplax && m_ynplay)
3743 m_a[i][j] *= pow(m_w[i].x + m_w[j].x - 2. * m_coplax, 2) +
3744 pow(m_w[i].y + m_w[j].y - 2. * m_coplay, 2);
3745 // Define a final version of a[i][j].
3746 m_a[i][j] = -0.5 * log(m_a[i][j]);
3747 // Copy this to a[j][i] since the capacitance matrix is symmetric.
3748 m_a[j][i] = m_a[i][j];
3749 }
3750 }
3751 // Call CHARGE to calculate the charges really.
3752 return Charge();
3753}
3754
3755bool ComponentAnalyticField::SetupB1X() {
3756 //-----------------------------------------------------------------------
3757 // SETB1X - Routine preparing the field calculations by filling the
3758 // c-matrix, the potential used is re(log(sin Pi/s (z-z0))).
3759 // VARIABLES : xx : Difference in x of two wires * factor.
3760 // yy : Difference in y of two wires * factor.
3761 // yymirr : Difference in y of one wire and the mirror
3762 // image of another * factor.
3763 // r2plan : Periodic length of (xx,yymirr)
3764 //-----------------------------------------------------------------------
3765
3766 double xx = 0., yy = 0., yymirr = 0.;
3767 double r2plan = 0.;
3768
3769 // Loop over all wires and calculate the diagonal elements first.
3770 for (unsigned int i = 0; i < m_nWires; ++i) {
3771 m_a[i][i] = -log(m_w[i].r * Pi / m_sx);
3772 // Take care of a plane at constant y if it exist.
3773 if (m_ynplay) {
3774 yy = (Pi / m_sx) * 2. * (m_w[i].y - m_coplay);
3775 if (fabs(yy) > 20.) m_a[i][i] += fabs(yy) - CLog2;
3776 if (fabs(yy) <= 20.) m_a[i][i] += log(fabs(sinh(yy)));
3777 }
3778 // Loop over all other wires to obtain off-diagonal elements.
3779 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3780 xx = (Pi / m_sx) * (m_w[i].x - m_w[j].x);
3781 yy = (Pi / m_sx) * (m_w[i].y - m_w[j].y);
3782 if (fabs(yy) > 20.) m_a[i][j] = -fabs(yy) + CLog2;
3783 if (fabs(yy) <= 20.) {
3784 const double sinhy = sinh(yy);
3785 const double sinx = sin(xx);
3786 m_a[i][j] = -0.5 * log(sinhy * sinhy + sinx * sinx);
3787 }
3788 // Take equipotential planes into account if they exist.
3789 if (m_ynplay) {
3790 r2plan = 0.;
3791 yymirr = (Pi / m_sx) * (m_w[i].y + m_w[j].y - 2. * m_coplay);
3792 if (fabs(yymirr) > 20.) r2plan = fabs(yymirr) - CLog2;
3793 if (fabs(yymirr) <= 20.) {
3794 const double sinhy = sinh(yymirr);
3795 const double sinx = sin(xx);
3796 r2plan = 0.5 * log(sinhy * sinhy + sinx * sinx);
3797 }
3798 m_a[i][j] += r2plan;
3799 }
3800 // Copy a[i][j] to a[j][i], the capactance matrix is symmetric.
3801 m_a[j][i] = m_a[i][j];
3802 }
3803 }
3804 // Call function CHARGE calculating all kinds of useful things.
3805 return Charge();
3806}
3807
3808bool ComponentAnalyticField::SetupB1Y() {
3809 //-----------------------------------------------------------------------
3810 // SETB1Y - Routine preparing the field calculations by setting the
3811 // charges. The potential used is Re log(sinh Pi/sy(z-z0)).
3812 // VARIABLES : yy : Difference in y of two wires * factor.
3813 // xxmirr : Difference in x of one wire and the mirror
3814 // image of another * factor.
3815 // r2plan : Periodic length of (xxmirr,yy).
3816 //-----------------------------------------------------------------------
3817
3818 double xx = 0., yy = 0., xxmirr = 0.;
3819 double r2plan = 0.;
3820
3821 // Loop over all wires and calculate the diagonal elements first.
3822 for (unsigned int i = 0; i < m_nWires; ++i) {
3823 m_a[i][i] = -log(m_w[i].r * Pi / m_sy);
3824 // Take care of planes 1 and 2 if present.
3825 if (m_ynplax) {
3826 xx = (Pi / m_sy) * 2. * (m_w[i].x - m_coplax);
3827 if (fabs(xx) > 20.) m_a[i][i] += fabs(xx) - CLog2;
3828 if (fabs(xx) <= 20.) m_a[i][i] += log(fabs(sinh(xx)));
3829 }
3830 // Loop over all other wires to obtain off-diagonal elements.
3831 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3832 xx = (Pi / m_sy) * (m_w[i].x - m_w[j].x);
3833 yy = (Pi / m_sy) * (m_w[i].y - m_w[j].y);
3834 if (fabs(xx) > 20.) m_a[i][j] = -fabs(xx) + CLog2;
3835 if (fabs(xx) <= 20.) {
3836 const double sinhx = sinh(xx);
3837 const double siny = sin(yy);
3838 m_a[i][j] = -0.5 * log(sinhx * sinhx + siny * siny);
3839 }
3840 // Take care of a plane at constant x.
3841 if (m_ynplax) {
3842 xxmirr = (Pi / m_sy) * (m_w[i].x + m_w[j].x - 2. * m_coplax);
3843 r2plan = 0.;
3844 if (fabs(xxmirr) > 20.) r2plan = fabs(xxmirr) - CLog2;
3845 if (fabs(xxmirr) <= 20.) {
3846 const double sinhx = sinh(xxmirr);
3847 const double siny = sin(yy);
3848 r2plan = 0.5 * log(sinhx * sinhx + siny * siny);
3849 }
3850 m_a[i][j] += r2plan;
3851 }
3852 // Copy a[i][j] to a[j][i], the capacitance matrix is symmetric.
3853 m_a[j][i] = m_a[i][j];
3854 }
3855 }
3856 // Call function CHARGE calculating all kinds of useful things.
3857 return Charge();
3858}
3859
3860bool ComponentAnalyticField::SetupB2X() {
3861 //-----------------------------------------------------------------------
3862 // SETB2X - Routine preparing the field calculations by setting the
3863 // charges.
3864 // VARIABLES : xx : Difference in x of two wires * factor.
3865 // yy : Difference in y of two wires * factor.
3866 // xxneg : Difference in x of one wire and the mirror
3867 // image in period direction of another * fac.
3868 // yymirr : Difference in y of one wire and the mirror
3869 // image of another * factor.
3870 //-----------------------------------------------------------------------
3871
3872 m_b2sin.resize(m_nWires);
3873
3874 // Loop over all wires and calculate the diagonal elements first.
3875 for (unsigned int i = 0; i < m_nWires; ++i) {
3876 double xx = (Pi / m_sx) * (m_w[i].x - m_coplax);
3877 m_a[i][i] = (0.5 * m_w[i].r * Pi / m_sx) / sin(xx);
3878 // Take care of a plane at constant y if it exists.
3879 if (m_ynplay) {
3880 const double yymirr = (Pi / m_sx) * (m_w[i].y - m_coplay);
3881 if (fabs(yymirr) <= 20.) {
3882 const double sinhy = sinh(yymirr);
3883 const double sinx = sin(xx);
3884 m_a[i][i] *= sqrt(sinhy * sinhy + sinx * sinx) / sinhy;
3885 }
3886 }
3887 // Store the true value of a[i][i].
3888 m_a[i][i] = -log(fabs(m_a[i][i]));
3889 // Loop over all other wires to obtain off-diagonal elements.
3890 for (unsigned int j = i + 1; j < m_nWires; ++j) {
3891 xx = HalfPi * (m_w[i].x - m_w[j].x) / m_sx;
3892 const double yy = HalfPi * (m_w[i].y - m_w[j].y) / m_sx;
3893 const double xxneg =
3894 HalfPi * (m_w[i].x + m_w[j].x - 2. * m_coplax) / m_sx;
3895 if (fabs(yy) <= 20.) {
3896 const double sinhy = sinh(yy);
3897 const double sinxx = sin(xx);
3898 const double sinxxneg = sin(xxneg);
3899 m_a[i][j] = (sinhy * sinhy + sinxx * sinxx) /
3900 (sinhy * sinhy + sinxxneg * sinxxneg);
3901 }
3902 if (fabs(yy) > 20.) m_a[i][j] = 1.0;
3903 // Take an equipotential plane at constant y into account.
3904 if (m_ynplay) {
3905 const double yymirr =
3906 HalfPi * (m_w[i].y + m_w[j].y - 2. * m_coplay) / m_sx;
3907 if (fabs(yymirr) <= 20.) {
3908 const double sinhy = sinh(yymirr);
3909 const double sinxx = sin(xx);
3910 const double sinxxneg = sin(xxneg);
3911 m_a[i][j] *= (sinhy * sinhy + sinxxneg * sinxxneg) /
3912 (sinhy * sinhy + sinxx * sinxx);
3913 }
3914 }
3915 // Store the true value of a[i][j] in both a[i][j] and a[j][i].
3916 m_a[i][j] = -0.5 * log(m_a[i][j]);
3917 m_a[j][i] = m_a[i][j];
3918 }
3919 // Set the b2sin vector.
3920 m_b2sin[i] = sin(Pi * (m_coplax - m_w[i].x) / m_sx);
3921 }
3922 // Call function CHARGE calculating all kinds of useful things.
3923 return Charge();
3924}
3925
3926bool ComponentAnalyticField::SetupB2Y() {
3927 //-----------------------------------------------------------------------
3928 // SETB2Y - Routine preparing the field calculations by setting the
3929 // charges.
3930 // VARIABLES : xx : Difference in x of two wires * factor.
3931 // yy : Difference in y of two wires * factor.
3932 // xxmirr : Difference in x of one wire and the mirror
3933 // image of another * factor.
3934 // yyneg : Difference in y of one wire and the mirror
3935 // image in period direction of another * fac.
3936 //-----------------------------------------------------------------------
3937
3938 m_b2sin.resize(m_nWires);
3939
3940 // Loop over all wires and calculate the diagonal elements first.
3941 for (unsigned int i = 0; i < m_nWires; ++i) {
3942 double yy = (Pi / m_sy) * (m_w[i].y - m_coplay);
3943 m_a[i][i] = (0.5 * m_w[i].r * Pi / m_sy) / sin(yy);
3944 // Take care of a plane at constant x if present.
3945 if (m_ynplax) {
3946 const double xxmirr = (Pi / m_sy) * (m_w[i].x - m_coplax);
3947 if (fabs(xxmirr) <= 20.) {
3948 const double sinhx = sinh(xxmirr);
3949 const double sinyy = sin(yy);
3950 m_a[i][i] *= sqrt(sinhx * sinhx + sinyy * sinyy) / sinhx;
3951 }
3952 }
3953 // Store the true value of a[i][i].
3954 m_a[i][i] = -log(fabs(m_a[i][i]));
3955 // Loop over all other wires to obtain off-diagonal elements.
3956 for (unsigned int j = i + 1; j < m_nWires; j++) {
3957 const double xx = HalfPi * (m_w[i].x - m_w[j].x) / m_sy;
3958 yy = HalfPi * (m_w[i].y - m_w[j].y) / m_sy;
3959 const double yyneg =
3960 HalfPi * (m_w[i].y + m_w[j].y - 2. * m_coplay) / m_sy;
3961 if (fabs(xx) <= 20.) {
3962 const double sinhx = sinh(xx);
3963 const double sinyy = sin(yy);
3964 const double sinyyneg = sin(yyneg);
3965 m_a[i][j] = (sinhx * sinhx + sinyy * sinyy) /
3966 (sinhx * sinhx + sinyyneg * sinyyneg);
3967 }
3968 if (fabs(xx) > 20.) m_a[i][j] = 1.0;
3969 // Take an equipotential plane at constant x into account.
3970 if (m_ynplax) {
3971 const double xxmirr =
3972 HalfPi * (m_w[i].x + m_w[j].x - 2. * m_coplax) / m_sy;
3973 if (fabs(xxmirr) <= 20.) {
3974 const double sinhx = sinh(xxmirr);
3975 const double sinyy = sin(yy);
3976 const double sinyyneg = sin(yyneg);
3977 m_a[i][j] *= (sinhx * sinhx + sinyyneg * sinyyneg) /
3978 (sinhx * sinhx + sinyy * sinyy);
3979 }
3980 }
3981 // Store the true value of a[i][j] in both a[i][j] and a[j][i].
3982 m_a[i][j] = -0.5 * log(m_a[i][j]);
3983 m_a[j][i] = m_a[i][j];
3984 }
3985 // Set the b2sin vector.
3986 m_b2sin[i] = sin(Pi * (m_coplay - m_w[i].y) / m_sy);
3987 }
3988 // Call function CHARGE calculating all kinds of useful things.
3989 return Charge();
3990}
3991
3992bool ComponentAnalyticField::SetupC10() {
3993 //-----------------------------------------------------------------------
3994 // SETC10 - This initialising routine computes the wire charges E and
3995 // sets certain constants in common. The wire are located at
3996 // (x[j],y[j])+(LX*SX,LY*SY), J=1(1)NWIRE,
3997 // LX=-infinity(1)infinity, LY=-infinity(1)infinity.
3998 // Use is made of the function PH2.
3999 //
4000 // (Written by G.A.Erskine/DD, 14.8.1984 modified to some extent)
4001 //-----------------------------------------------------------------------
4002
4003 // Initialise the constants.
4004 double p = 0.;
4005 m_p1 = m_p2 = 0.;
4006
4007 m_mode = 0;
4008 if (m_sx <= m_sy) {
4009 m_mode = 1;
4010 if (m_sy / m_sx < 8.) p = exp(-Pi * m_sy / m_sx);
4011 m_zmult = std::complex<double>(Pi / m_sx, 0.);
4012 } else {
4013 m_mode = 0;
4014 if (m_sx / m_sy < 8.) p = exp(-Pi * m_sx / m_sy);
4015 m_zmult = std::complex<double>(0., Pi / m_sy);
4016 }
4017 m_p1 = p * p;
4018 if (m_p1 > 1.e-10) m_p2 = pow(p, 6);
4019
4020 if (m_debug) {
4021 std::cout << m_className << "::SetupC10:\n";
4022 std::cout << " p, p1, p2 = " << p << ", " << m_p1 << ", " << m_p2
4023 << "\n";
4024 std::cout << " zmult = " << m_zmult << "\n";
4025 std::cout << " mode = " << m_mode << "\n";
4026 }
4027
4028 // Store the capacitance matrix.
4029 for (unsigned int i = 0; i < m_nWires; ++i) {
4030 for (unsigned int j = 0; j < m_nWires; ++j) {
4031 const double xyi = m_mode == 0 ? m_w[i].x : m_w[i].y;
4032 const double xyj = m_mode == 0 ? m_w[j].x : m_w[j].y;
4033 const double temp = xyi * xyj * TwoPi / (m_sx * m_sy);
4034 if (i == j) {
4035 m_a[i][j] = Ph2Lim(m_w[i].r) - temp;
4036 } else {
4037 m_a[i][j] = Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) - temp;
4038 }
4039 }
4040 }
4041 // Call CHARGE to find the charges.
4042 if (!Charge()) return false;
4043 // Calculate the non-logarithmic term in the potential.
4044 double s = 0.;
4045 for (unsigned int j = 0; j < m_nWires; ++j) {
4046 const double xyj = m_mode == 0 ? m_w[j].x : m_w[j].y;
4047 s += m_w[j].e * xyj;
4048 }
4049 m_c1 = -s * 2. * Pi / (m_sx * m_sy);
4050 return true;
4051}
4052
4053bool ComponentAnalyticField::SetupC2X() {
4054 //-----------------------------------------------------------------------
4055 // SETC2X - This initializing subroutine stores the capacitance matrix
4056 // for the configuration:
4057 // wires at zw(j)+cmplx(lx*2*sx,ly*sy),
4058 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
4059 // but the signs of the charges alternate in the x-direction
4060 //-----------------------------------------------------------------------
4061
4062 // Initialise the constants.
4063 double p = 0.;
4064 m_p1 = m_p2 = 0.;
4065
4066 m_mode = 0;
4067 if (2. * m_sx <= m_sy) {
4068 m_mode = 1;
4069 if (m_sy / m_sx < 25.) p = exp(-HalfPi * m_sy / m_sx);
4070 m_zmult = std::complex<double>(HalfPi / m_sx, 0.);
4071 } else {
4072 m_mode = 0;
4073 if (m_sx / m_sy < 6.) p = exp(-2. * Pi * m_sx / m_sy);
4074 m_zmult = std::complex<double>(0., Pi / m_sy);
4075 }
4076 m_p1 = p * p;
4077 if (m_p1 > 1.e-10) m_p2 = pow(p, 6);
4078
4079 if (m_debug) {
4080 std::cout << m_className << "::SetupC2X:\n";
4081 std::cout << " p, p1, p2 = " << p << ", " << m_p1 << ", " << m_p2
4082 << "\n";
4083 std::cout << " zmult = " << m_zmult << "\n";
4084 std::cout << " mode = " << m_mode << "\n";
4085 }
4086
4087 // Fill the capacitance matrix.
4088 for (unsigned int i = 0; i < m_nWires; ++i) {
4089 const double cx =
4090 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
4091 for (unsigned int j = 0; j < m_nWires; ++j) {
4092 double temp = 0.;
4093 if (m_mode == 0) {
4094 temp = (m_w[i].x - cx) * (m_w[j].x - cx) * TwoPi / (m_sx * m_sy);
4095 }
4096 if (i == j) {
4097 m_a[i][i] =
4098 Ph2Lim(m_w[i].r) - Ph2(2. * (m_w[i].x - cx), 0.) - temp;
4099 } else {
4100 m_a[i][j] = Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
4101 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y - m_w[j].y) -
4102 temp;
4103 }
4104 }
4105 }
4106 // Call CHARGE to find the wire charges.
4107 if (!Charge()) return false;
4108 // Determine the non-logarithmic part of the potential (0 if MODE=1).
4109 m_c1 = 0.;
4110 if (m_mode == 0) {
4111 double s = 0.;
4112 for (unsigned int i = 0; i < m_nWires; ++i) {
4113 const double cx =
4114 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
4115 s += m_w[i].e * (m_w[i].x - cx);
4116 }
4117 m_c1 = -s * TwoPi / (m_sx * m_sy);
4118 }
4119 return true;
4120}
4121
4122bool ComponentAnalyticField::SetupC2Y() {
4123 //-----------------------------------------------------------------------
4124 // SETC2Y - This initializing subroutine stores the capacitance matrix
4125 // for the configuration:
4126 // wires at zw(j)+cmplx(lx*sx,ly*2*sy),
4127 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
4128 // but the signs of the charges alternate in the y-direction
4129 //-----------------------------------------------------------------------
4130
4131 // Initialise the constants.
4132 double p = 0.;
4133 m_p1 = m_p2 = 0.;
4134
4135 m_mode = 0;
4136 if (m_sx <= 2. * m_sy) {
4137 m_mode = 1;
4138 if (m_sy / m_sx <= 6.) p = exp(-2. * Pi * m_sy / m_sx);
4139 m_zmult = std::complex<double>(Pi / m_sx, 0.);
4140 } else {
4141 m_mode = 0;
4142 if (m_sx / m_sy <= 25.) p = exp(-HalfPi * m_sx / m_sy);
4143 m_zmult = std::complex<double>(0., HalfPi / m_sy);
4144 }
4145 m_p1 = p * p;
4146 if (m_p1 > 1.e-10) m_p2 = pow(p, 6);
4147
4148 if (m_debug) {
4149 std::cout << m_className << "::SetupC2Y:\n";
4150 std::cout << " p, p1, p2 = " << p << ", " << m_p1 << ", " << m_p2
4151 << "\n";
4152 std::cout << " zmult = " << m_zmult << "\n";
4153 std::cout << " mode = " << m_mode << "\n";
4154 }
4155
4156 // Fill the capacitance matrix.
4157 for (unsigned int i = 0; i < m_nWires; ++i) {
4158 const double cy =
4159 m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
4160 for (unsigned int j = 0; j < m_nWires; ++j) {
4161 double temp = 0.;
4162 if (m_mode == 1) {
4163 temp = (m_w[i].y - cy) * (m_w[j].y - cy) * TwoPi / (m_sx * m_sy);
4164 }
4165 if (i == j) {
4166 m_a[i][i] =
4167 Ph2Lim(m_w[i].r) - Ph2(0., 2. * (m_w[j].y - cy)) - temp;
4168 } else {
4169 m_a[i][j] = Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
4170 Ph2(m_w[i].x - m_w[j].x, m_w[i].y + m_w[j].y - 2. * cy) -
4171 temp;
4172 }
4173 }
4174 }
4175 // Call CHARGE to find the wire charges.
4176 if (!Charge()) return false;
4177 // The non-logarithmic part of the potential is zero if MODE=0.
4178 m_c1 = 0.;
4179 if (m_mode == 1) {
4180 double s = 0.;
4181 for (unsigned int i = 0; i < m_nWires; ++i) {
4182 const double cy =
4183 m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
4184 s += m_w[i].e * (m_w[i].y - cy);
4185 }
4186 m_c1 = -s * TwoPi / (m_sx * m_sy);
4187 }
4188 return true;
4189}
4190
4191bool ComponentAnalyticField::SetupC30() {
4192 //-----------------------------------------------------------------------
4193 // SETC30 - This initializing subroutine stores the capacitance matrix
4194 // for a configuration with
4195 // wires at zw(j)+cmplx(lx*2*sx,ly*2*sy),
4196 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
4197 // but the signs of the charges alternate in both directions.
4198 //-----------------------------------------------------------------------
4199
4200 // Initialise the constants.
4201 double p = 0.;
4202 m_p1 = m_p2 = 0.;
4203
4204 m_mode = 0;
4205 if (m_sx <= m_sy) {
4206 m_mode = 1;
4207 if (m_sy / m_sx <= 13.) p = exp(-Pi * m_sy / m_sx);
4208 m_zmult = std::complex<double>(HalfPi / m_sx, 0.);
4209 } else {
4210 m_mode = 0;
4211 if (m_sx / m_sy <= 13.) p = exp(-Pi * m_sx / m_sy);
4212 m_zmult = std::complex<double>(0., HalfPi / m_sy);
4213 }
4214 m_p1 = p * p;
4215 if (m_p1 > 1.e-10) m_p2 = pow(p, 6);
4216
4217 if (m_debug) {
4218 std::cout << m_className << "::SetupC30:\n";
4219 std::cout << " p, p1, p2 = " << p << ", " << m_p1 << ", " << m_p2
4220 << "\n";
4221 std::cout << " zmult = " << m_zmult << "\n";
4222 std::cout << " mode = " << m_mode << "\n";
4223 }
4224
4225 // Fill the capacitance matrix.
4226 for (unsigned int i = 0; i < m_nWires; ++i) {
4227 double cx = m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
4228 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
4229 for (unsigned int j = 0; j < m_nWires; ++j) {
4230 if (i == j) {
4231 m_a[i][i] = Ph2Lim(m_w[i].r) - Ph2(0., 2. * (m_w[i].y - cy)) -
4232 Ph2(2. * (m_w[i].x - cx), 0.) +
4233 Ph2(2. * (m_w[i].x - cx), 2. * (m_w[i].y - cy));
4234 } else {
4235 m_a[i][j] =
4236 Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
4237 Ph2(m_w[i].x - m_w[j].x, m_w[i].y + m_w[j].y - 2. * cy) -
4238 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y - m_w[j].y) +
4239 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y + m_w[j].y - 2. * cy);
4240 }
4241 }
4242 }
4243 // Call CHARGE to find the wire charges.
4244 if (!Charge()) return false;
4245 // The non-logarithmic part of the potential is zero in this case.
4246 m_c1 = 0.;
4247 return true;
4248}
4249
4250bool ComponentAnalyticField::SetupD10() {
4251 //-----------------------------------------------------------------------
4252 // SETD10 - Subroutine preparing the field calculations by calculating
4253 // the charges on the wires, for cells with a tube.
4254 //
4255 // (Last changed on 4/ 9/95.)
4256 //-----------------------------------------------------------------------
4257
4258 // Loop over all wires.
4259 for (unsigned int i = 0; i < m_nWires; ++i) {
4260 // Set the diagonal terms.
4261 m_a[i][i] = -log(m_w[i].r * m_cotube /
4262 (m_cotube2 - (m_w[i].x * m_w[i].x + m_w[i].y * m_w[i].y)));
4263 // Set a complex wire-coordinate to make things a little easier.
4264 std::complex<double> zi(m_w[i].x, m_w[i].y);
4265 // Loop over all other wires for the off-diagonal elements.
4266 for (unsigned int j = i + 1; j < m_nWires; ++j) {
4267 // Set a complex wire-coordinate to make things a little easier.
4268 std::complex<double> zj(m_w[j].x, m_w[j].y);
4269 m_a[i][j] = -log(abs(m_cotube * (zi - zj) / (m_cotube2 - conj(zi) * zj)));
4270 // Copy this to a[j][i] since the capacitance matrix is symmetric.
4271 m_a[j][i] = m_a[i][j];
4272 }
4273 }
4274 // Call CHARGE to calculate the charges really.
4275 return Charge();
4276}
4277
4278bool ComponentAnalyticField::SetupD20() {
4279 //-----------------------------------------------------------------------
4280 // SETD20 - Subroutine preparing the field calculations by calculating
4281 // the charges on the wires, for cells with a tube and a
4282 // phi periodicity. Assymetric capacitance matrix !
4283 //
4284 // (Last changed on 18/ 2/93.)
4285 //-----------------------------------------------------------------------
4286
4287 // Loop over all wires.
4288 for (unsigned int i = 0; i < m_nWires; ++i) {
4289 // Set a complex wire-coordinate to make things a little easier.
4290 std::complex<double> zi(m_w[i].x, m_w[i].y);
4291 if (abs(zi) < m_w[i].r) {
4292 // Case of a wire near the centre.
4293 // Inner loop over the wires.
4294 for (unsigned int j = 0; j < m_nWires; ++j) {
4295 if (i == j) {
4296 // Set the diagonal terms.
4297 m_a[i][i] =
4298 -log(m_w[i].r /
4299 (m_cotube -
4300 (m_w[i].x * m_w[i].x + m_w[i].y * m_w[i].y) / m_cotube));
4301 } else {
4302 // Off-diagonal terms.
4303 std::complex<double> zj(m_w[j].x, m_w[j].y);
4304 m_a[j][i] = -log(abs((1. / m_cotube) * (zi - zj) /
4305 (1. - conj(zi) * zj / m_cotube2)));
4306 }
4307 }
4308 } else {
4309 // Normal case.
4310 // Inner wire loop.
4311 for (unsigned int j = 0; j < m_nWires; ++j) {
4312 if (i == j) {
4313 // Diagonal elements.
4314 m_a[i][i] =
4315 -log(abs(m_w[i].r * m_mtube * pow(zi, m_mtube - 1) /
4316 (pow(m_cotube, m_mtube) *
4317 (1. - pow((abs(zi) / m_cotube), 2 * m_mtube)))));
4318 } else {
4319 // Off-diagonal terms.
4320 std::complex<double> zj(m_w[j].x, m_w[j].y);
4321 m_a[j][i] = -log(abs((1 / pow(m_cotube, m_mtube)) *
4322 (pow(zj, m_mtube) - pow(zi, m_mtube)) /
4323 (1. - pow(zj * conj(zi) / m_cotube2, m_mtube))));
4324 }
4325 }
4326 }
4327 }
4328 // Call CHARGE to calculate the charges really.
4329 return Charge();
4330}
4331
4332bool ComponentAnalyticField::SetupD30() {
4333 //-----------------------------------------------------------------------
4334 // SETD30 - Subroutine preparing the field calculations by calculating
4335 // the charges on the wires, for cells with wires inside a
4336 // polygon.
4337 //
4338 // (Last changed on 21/ 2/94.)
4339 //-----------------------------------------------------------------------
4340
4341 wmap.assign(m_nWires, std::complex<double>(0., 0.));
4342
4343 std::complex<double> wd = std::complex<double>(0., 0.);
4344
4345 // Evaluate kappa, a constant needed by ConformalMap.
4346 m_kappa = tgamma((m_ntube + 1.) / m_ntube) *
4347 tgamma((m_ntube - 2.) / m_ntube) / tgamma((m_ntube - 1.) / m_ntube);
4348 // Loop over all wire combinations.
4349 for (unsigned int i = 0; i < m_nWires; ++i) {
4350 // Compute wire mappings only once.
4351 ConformalMap(std::complex<double>(m_w[i].x, m_w[i].y) / m_cotube, wmap[i],
4352 wd);
4353 // Diagonal elements.
4354 m_a[i][i] = -log(
4355 abs((m_w[i].r / m_cotube) * wd / (1. - pow(abs(wmap[i]), 2))));
4356 // Loop over all other wires for the off-diagonal elements.
4357 for (unsigned int j = 0; j < i; ++j) {
4358 m_a[i][j] =
4359 -log(abs((wmap[i] - wmap[j]) / (1. - conj(wmap[i]) * wmap[j])));
4360 // Copy this to a[j][i] since the capacitance matrix is symmetric.
4361 m_a[j][i] = m_a[i][j];
4362 }
4363 }
4364 // Call CHARGE to calculate the charges really.
4365 return Charge();
4366}
4367
4368bool ComponentAnalyticField::Charge() {
4369 //-----------------------------------------------------------------------
4370 // CHARGE - Routine actually inverting the capacitance matrix filled in
4371 // the SET... routines thereby providing the charges.
4372 // (Last changed on 30/ 1/93.)
4373 //-----------------------------------------------------------------------
4374
4375 // Transfer the voltages to rhs vector,
4376 // correcting for the equipotential planes.
4377 std::vector<double> b(m_nWires, 0.);
4378 for (unsigned int i = 0; i < m_nWires; ++i) {
4379 b[i] = m_w[i].v - (m_corvta * m_w[i].x + m_corvtb * m_w[i].y + m_corvtc);
4380 }
4381
4382 bool ok = true;
4383 // Force sum charges = 0 in case of absence of equipotential planes.
4384 if (!(m_ynplan[0] || m_ynplan[1] || m_ynplan[2] || m_ynplan[3] || m_tube)) {
4385 // Add extra elements to A, acting as constraints.
4386 b.push_back(0.);
4387 m_a.resize(m_nWires + 1);
4388 m_a[m_nWires].clear();
4389 for (unsigned int i = 0; i < m_nWires; ++i) {
4390 m_a[i].push_back(1.);
4391 m_a[m_nWires].push_back(1.);
4392 }
4393 m_a[m_nWires].push_back(0.);
4394 // Solve equations to yield charges.
4395 if (Numerics::CERNLIB::deqinv(m_nWires + 1, m_a, b) != 0) {
4396 std::cerr << m_className << "::Charge: Matrix inversion failed.\n";
4397 return false;
4398 }
4399 // Modify A to give true inverse of capacitance matrix.
4400 if (m_a[m_nWires][m_nWires] != 0.) {
4401 const double t = 1. / m_a[m_nWires][m_nWires];
4402 for (unsigned int i = 0; i < m_nWires; ++i) {
4403 for (unsigned int j = 0; j < m_nWires; ++j) {
4404 m_a[i][j] -= t * m_a[i][m_nWires] * m_a[m_nWires][j];
4405 }
4406 }
4407 } else {
4408 std::cerr << m_className << "::Charge:\n"
4409 << " True inverse of the capacitance matrix"
4410 << " could not be calculated.\n";
4411 ok = false;
4412 }
4413 // Store reference potential.
4414 m_v0 = b[m_nWires];
4415 } else {
4416 // Handle the case when the sum of the charges is zero automatically.
4417 if (Numerics::CERNLIB::deqinv(m_nWires, m_a, b) != 0) {
4418 std::cerr << m_className << "::Charge: Matrix inversion failed.\n";
4419 return false;
4420 }
4421 // Reference potential chosen to be zero.
4422 m_v0 = 0.;
4423 }
4424
4425 // Check the error condition flag.
4426 if (!ok) {
4427 std::cerr << m_className << "::Charge:\n"
4428 << " Failure to solve the capacitance equations.\n"
4429 << " No charges are available.\n";
4430 return false;
4431 }
4432
4433 // Copy the charges to E.
4434 for (unsigned int i = 0; i < m_nWires; ++i) m_w[i].e = b[i];
4435
4436 // If debugging is on, print the capacitance matrix.
4437 if (m_debug) {
4438 std::cout << m_className << "::Charge:\n";
4439 std::cout << " Dump of the capacitance matrix after inversion:\n";
4440 for (unsigned int i = 0; i < m_nWires; i += 10) {
4441 for (unsigned int j = 0; j < m_nWires; j += 10) {
4442 std::cout << " (Block " << i / 10 << ", " << j / 10 << ")\n";
4443 for (unsigned int ii = 0; ii < 10; ++ii) {
4444 if (i + ii >= m_nWires) break;
4445 for (unsigned int jj = 0; jj < 10; ++jj) {
4446 if (j + jj >= m_nWires) break;
4447 std::cout << std::setw(6) << m_a[i + ii][j + jj] << " ";
4448 }
4449 std::cout << "\n";
4450 }
4451 std::cout << "\n";
4452 }
4453 }
4454 std::cout << m_className << "::Charge:\n";
4455 std::cout << " End of the inverted capacitance matrix.\n";
4456 }
4457
4458 // And also check the quality of the matrix inversion.
4459 if (m_chargeCheck) {
4460 std::cout << m_className << "::Charge:\n";
4461 std::cout << " Quality check of the charge calculation.\n";
4462 std::cout << " Wire E as obtained E reconstructed\n";
4463 for (unsigned int i = 0; i < m_nWires; ++i) {
4464 b[i] = 0.;
4465 for (unsigned int j = 0; j < m_nWires; ++j) {
4466 b[i] += m_a[i][j] *
4467 (m_w[j].v - m_v0 -
4468 (m_corvta * m_w[j].x + m_corvtb * m_w[j].y + m_corvtc));
4469 }
4470 std::cout << " " << i << " " << m_w[i].e << " " << b[i]
4471 << "\n";
4472 }
4473 }
4474 return true;
4475}
4476
4477double ComponentAnalyticField::Ph2(const double xpos, const double ypos) const {
4478 //-----------------------------------------------------------------------
4479 // PH2 - Logarithmic contribution to real single-wire potential,
4480 // for a doubly priodic wire array.
4481 // PH2LIM - Entry, PH2LIM(r) corresponds to z on the surface of a wire
4482 // of (small) radius r.
4483 //
4484 // Clenshaw's algorithm is used for the evaluation of the sum
4485 // ZTERM = SIN(zeta) - P1*SIN(3*zeta) + P2*SIN(5*zeta).
4486 //
4487 // (G.A.Erskine/DD, 14.8.1984; some minor modifications (i) common block
4488 // /EV2COM/ incorporated in /CELDAT/ (ii) large imag(zeta) corrected)
4489 //-----------------------------------------------------------------------
4490
4491 // Start of the main subroutine, off diagonal elements.
4492 std::complex<double> zeta = m_zmult * std::complex<double>(xpos, ypos);
4493 if (fabs(imag(zeta)) < 10.) {
4494 std::complex<double> zsin = sin(zeta);
4495 std::complex<double> zcof = 4. * zsin * zsin - 2.;
4496 std::complex<double> zu = -m_p1 - zcof * m_p2;
4497 std::complex<double> zunew = 1. - zcof * zu - m_p2;
4498 std::complex<double> zterm = (zunew + zu) * zsin;
4499 return -log(abs(zterm));
4500 }
4501
4502 return -fabs(imag(zeta)) + CLog2;
4503}
4504
4505void ComponentAnalyticField::ConformalMap(const std::complex<double>& z,
4506 std::complex<double>& ww,
4507 std::complex<double>& wd) const {
4508 //-----------------------------------------------------------------------
4509 // EFCMAP - Maps a the interior part of a regular in the unit circle.
4510 // Variables: Z - point to be mapped
4511 // W - the image of Z
4512 // WD - derivative of the mapping at Z
4513 // CC1 - coefficients for expansion around centre
4514 // CC2 - coefficients for expansion around corner
4515 // (Last changed on 19/ 2/94.)
4516 //-----------------------------------------------------------------------
4517
4518 // Coefficients for centre expansion in triangles, squares, pentagons,
4519 // hexagons, heptagons, octogons.
4520 constexpr std::array<std::array<double, 16>, 6> cc1 = {
4521 {{{0.1000000000e+01, -.1666666865e+00, 0.3174602985e-01, -.5731921643e-02,
4522 0.1040112227e-02, -.1886279933e-03, 0.3421107249e-04, -.6204730198e-05,
4523 0.1125329618e-05, -.2040969207e-06, 0.3701631357e-07, -.6713513301e-08,
4524 0.1217605794e-08, -.2208327132e-09, 0.4005162868e-10,
4525 -.7264017512e-11}},
4526 {{0.1000000000e+01, -.1000000238e+00, 0.8333332837e-02, -.7051283028e-03,
4527 0.5967194738e-04, -.5049648280e-05, 0.4273189802e-06, -.3616123934e-07,
4528 0.3060091514e-08, -.2589557457e-09, 0.2191374859e-10, -.1854418528e-11,
4529 0.1569274224e-12, -.1327975205e-13, 0.1123779363e-14,
4530 -.9509817570e-16}},
4531 {{0.1000000000e+01, -.6666666269e-01, 0.1212121220e-02, -.2626262140e-03,
4532 -.3322110570e-04, -.9413293810e-05, -.2570029210e-05, -.7695705904e-06,
4533 -.2422486887e-06, -.7945993730e-07, -.2691839640e-07, -.9361642128e-08,
4534 -.3327319087e-08, -.1204430555e-08, -.4428404310e-09,
4535 -.1650302672e-09}},
4536 {{0.1000000000e+01, -.4761904851e-01, -.1221001148e-02, -.3753788769e-03,
4537 -.9415557724e-04, -.2862767724e-04, -.9587882232e-05, -.3441659828e-05,
4538 -.1299798896e-05, -.5103651119e-06, -.2066504408e-06, -.8578405186e-07,
4539 -.3635090096e-07, -.1567239494e-07, -.6857355572e-08,
4540 -.3038770346e-08}},
4541 {{0.1000000000e+01, -.3571428731e-01, -.2040816238e-02, -.4936389159e-03,
4542 -.1446709794e-03, -.4963850370e-04, -.1877940667e-04, -.7600909157e-05,
4543 -.3232265954e-05, -.1427365532e-05, -.6493634714e-06, -.3026190711e-06,
4544 -.1438593245e-06, -.6953911225e-07, -.3409525462e-07,
4545 -.1692310647e-07}},
4546 {{0.1000000000e+01, -.2777777612e-01, -.2246732125e-02, -.5571441725e-03,
4547 -.1790652314e-03, -.6708275760e-04, -.2766949183e-04, -.1219387286e-04,
4548 -.5640039490e-05, -.2706697160e-05, -.1337270078e-05, -.6763995657e-06,
4549 -.3488264610e-06, -.1828456675e-06, -.9718036154e-07,
4550 -.5227070332e-07}}}};
4551 // Coefficients for corner expansion.
4552 constexpr std::array<std::array<double, 16>, 6> cc2 = {
4553 {{{0.3333333135e+00, -.5555555597e-01, 0.1014109328e-01, -.1837154618e-02,
4554 0.3332451452e-03, -.6043842586e-04, 0.1096152027e-04, -.1988050826e-05,
4555 0.3605655365e-06, -.6539443120e-07, 0.1186035448e-07, -.2151069323e-08,
4556 0.3901317047e-09, -.7075676156e-10, 0.1283289534e-10,
4557 -.2327455936e-11}},
4558 {{0.1000000000e+01, -.5000000000e+00, 0.3000000119e+00, -.1750000119e+00,
4559 0.1016666889e+00, -.5916666612e-01, 0.3442307562e-01, -.2002724260e-01,
4560 0.1165192947e-01, -.6779119372e-02, 0.3944106400e-02, -.2294691978e-02,
4561 0.1335057430e-02, -.7767395582e-03, 0.4519091453e-03,
4562 -.2629216760e-03}},
4563 {{0.1248050690e+01, -.7788147926e+00, 0.6355384588e+00, -.4899077415e+00,
4564 0.3713272810e+00, -.2838423252e+00, 0.2174729109e+00, -.1663445234e+00,
4565 0.1271933913e+00, -.9728997946e-01, 0.7442557812e-01, -.5692918226e-01,
4566 0.4354400188e-01, -.3330700099e-01, 0.2547712997e-01,
4567 -.1948769018e-01}},
4568 {{0.1333333015e+01, -.8888888955e+00, 0.8395061493e+00, -.7242798209e+00,
4569 0.6016069055e+00, -.5107235312e+00, 0.4393203855e+00, -.3745460510e+00,
4570 0.3175755739e+00, -.2703750730e+00, 0.2308617830e+00, -.1966916919e+00,
4571 0.1672732830e+00, -.1424439549e+00, 0.1214511395e+00,
4572 -.1034612656e+00}},
4573 {{0.1359752655e+01, -.9244638681e+00, 0.9593217969e+00, -.8771237731e+00,
4574 0.7490229011e+00, -.6677658558e+00, 0.6196745634e+00, -.5591596961e+00,
4575 0.4905325770e+00, -.4393517375e+00, 0.4029803872e+00, -.3631100059e+00,
4576 0.3199430704e+00, -.2866140604e+00, 0.2627358437e+00,
4577 -.2368256450e+00}},
4578 {{0.1362840652e+01, -.9286670089e+00, 0.1035511017e+01, -.9800255299e+00,
4579 0.8315343261e+00, -.7592730522e+00, 0.7612683773e+00, -.7132136226e+00,
4580 0.6074471474e+00, -.5554352999e+00, 0.5699443221e+00, -.5357525349e+00,
4581 0.4329345822e+00, -.3916820884e+00, 0.4401986003e+00,
4582 -.4197303057e+00}}}};
4583
4584 constexpr int nterm = 15;
4585 if (z == 0.) {
4586 // Z coincides with the centre. Results are trivial.
4587 ww = 0;
4588 wd = m_kappa;
4589 } else if (std::abs(z) < 0.75) {
4590 // Z is close to the centre. Series expansion.
4591 std::complex<double> zterm = pow(m_kappa * z, m_ntube);
4592 std::complex<double> wdsum = 0.;
4593 std::complex<double> wsum = cc1[m_ntube - 3][nterm];
4594 for (int i = nterm; i--;) {
4595 wdsum = wsum + zterm * wdsum;
4596 wsum = cc1[m_ntube - 3][i] + zterm * wsum;
4597 }
4598 // Return the results.
4599 ww = m_kappa * z * wsum;
4600 wd = m_kappa * (wsum + double(m_ntube) * zterm * wdsum);
4601 } else {
4602 // Z is close to the edge.
4603 // First rotate Z nearest to 1.
4604 double arot = -TwoPi *
4605 int(round(atan2(imag(z), real(z)) * m_ntube / TwoPi)) /
4606 m_ntube;
4607 const std::complex<double> zz =
4608 z * std::complex<double>(cos(arot), sin(arot));
4609 // Expand in a series.
4610 std::complex<double> zterm =
4611 pow(m_kappa * (1. - zz), m_ntube / (m_ntube - 2.));
4612 std::complex<double> wdsum = 0.;
4613 std::complex<double> wsum = cc2[m_ntube - 3][nterm];
4614 for (int i = nterm; i--;) {
4615 wdsum = wsum + zterm * wdsum;
4616 wsum = cc2[m_ntube - 3][i] + zterm * wsum;
4617 }
4618 // And return the results.
4619 ww = std::complex<double>(cos(arot), -sin(arot)) * (1. - zterm * wsum);
4620 wd = m_ntube * m_kappa * pow(m_kappa * (1. - zz), 2. / (m_ntube - 2.)) *
4621 (wsum + zterm * wdsum) / (m_ntube - 2.);
4622 }
4623}
4624
4625void ComponentAnalyticField::E2Sum(const double xpos, const double ypos,
4626 double& ex, double& ey) const {
4627 //-----------------------------------------------------------------------
4628 // E2SUM - Components of the elecrostatic field intensity in a doubly
4629 // periodic wire array.
4630 // Clenshaw's algorithm is used for the evaluation of the sums
4631 // ZTERM1 = SIN(ZETA) - P1*SIN(3*ZETA) + P2*SIN(5*ZETA),
4632 // ZTERM2 = COS(ZETA)- 3 P1*COS(3*ZETA)+ 5P2*COS(5*ZETA)
4633 // VARIABLES : (XPOS,YPOS): Position in the basic cell at which the
4634 // field is to be computed.
4635 // (Essentially by G.A.Erskine/DD, 14.8.1984)
4636 //-----------------------------------------------------------------------
4637
4638 constexpr std::complex<double> icons(0., 1.);
4639
4640 std::complex<double> wsum = 0.;
4641 for (const auto& wire : m_w) {
4642 const auto zeta =
4643 m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
4644 if (imag(zeta) > 15.) {
4645 wsum -= wire.e * icons;
4646 } else if (imag(zeta) < -15.) {
4647 wsum += wire.e * icons;
4648 } else {
4649 const auto zterm = Th1(zeta, m_p1, m_p2);
4650 wsum += wire.e * (zterm.second / zterm.first);
4651 }
4652 }
4653 ex = -real(-m_zmult * wsum);
4654 ey = imag(-m_zmult * wsum);
4655}
4656
4657void ComponentAnalyticField::FieldA00(const double xpos, const double ypos,
4658 double& ex, double& ey, double& volt,
4659 const bool opt) const {
4660 //-----------------------------------------------------------------------
4661 // EFCA00 - Subroutine performing the actual field calculations in case
4662 // only one charge and not more than 1 mirror-charge in either
4663 // x or y is present.
4664 // The potential used is 1/2*pi*eps0 log(r).
4665 // VARIABLES : R2 : Potential before taking -log(sqrt(...))
4666 // EX, EY : x,y-component of the electric field.
4667 // ETOT : Magnitude of electric field.
4668 // VOLT : Potential.
4669 // EXHELP etc : One term in the series to be summed.
4670 // (XPOS,YPOS): The position where the field is calculated.
4671 // (Last changed on 25/ 1/96.)
4672 //-----------------------------------------------------------------------
4673
4674 // Initialise the electric field and potential.
4675 ex = ey = 0.;
4676 volt = m_v0;
4677
4678 double xxmirr = 0., yymirr = 0.;
4679 // Loop over all wires.
4680 for (const auto& wire : m_w) {
4681 const double xx = xpos - wire.x;
4682 const double yy = ypos - wire.y;
4683 double r2 = xx * xx + yy * yy;
4684 // Calculate the field in case there are no planes.
4685 double exhelp = xx / r2;
4686 double eyhelp = yy / r2;
4687 // Take care of a plane at constant x.
4688 if (m_ynplax) {
4689 xxmirr = wire.x + (xpos - 2. * m_coplax);
4690 const double r2plan = xxmirr * xxmirr + yy * yy;
4691 exhelp -= xxmirr / r2plan;
4692 eyhelp -= yy / r2plan;
4693 r2 /= r2plan;
4694 }
4695 // Take care of a plane at constant y.
4696 if (m_ynplay) {
4697 yymirr = wire.y + (ypos - 2. * m_coplay);
4698 const double r2plan = xx * xx + yymirr * yymirr;
4699 exhelp -= xx / r2plan;
4700 eyhelp -= yymirr / r2plan;
4701 r2 /= r2plan;
4702 }
4703 // Take care of pairs of planes.
4704 if (m_ynplax && m_ynplay) {
4705 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
4706 exhelp += xxmirr / r2plan;
4707 eyhelp += yymirr / r2plan;
4708 r2 *= r2plan;
4709 }
4710 // Calculate the electric field and potential.
4711 if (opt) volt -= 0.5 * wire.e * log(r2);
4712 ex += wire.e * exhelp;
4713 ey += wire.e * eyhelp;
4714 }
4715}
4716
4717void ComponentAnalyticField::FieldB1X(const double xpos, const double ypos,
4718 double& ex, double& ey, double& volt,
4719 const bool opt) const {
4720 //-----------------------------------------------------------------------
4721 // EFCB1X - Routine calculating the potential for a row of positive
4722 // charges. The potential used is Re(Log(sin pi/s (z-z0))).
4723 // VARIABLES : See routine EFCA00 for most of the variables.
4724 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
4725 // ECOMPL : EX + I*EY ; I**2=-1
4726 //-----------------------------------------------------------------------
4727
4728 constexpr std::complex<double> icons(0., 1.);
4729
4730 std::complex<double> ecompl;
4731
4732 double r2 = 0.;
4733
4734 // Initialise the electric field and potential.
4735 ex = ey = 0.;
4736 volt = m_v0;
4737
4738 const double tx = Pi / m_sx;
4739 // Loop over all wires.
4740 for (const auto& wire : m_w) {
4741 const double xx = tx * (xpos - wire.x);
4742 const double yy = tx * (ypos - wire.y);
4743 // Calculate the field in case there are no equipotential planes.
4744 if (yy > 20.) {
4745 ecompl = -icons;
4746 } else if (yy < -20.) {
4747 ecompl = icons;
4748 } else {
4749 const std::complex<double> zz(xx, yy);
4750 const auto expzz = exp(2. * icons * zz);
4751 ecompl = icons * (expzz + 1.) / (expzz - 1.);
4752 }
4753
4754 if (opt) {
4755 if (fabs(yy) > 20.) r2 = -fabs(yy) + CLog2;
4756 if (fabs(yy) <= 20.) r2 = -0.5 * log(pow(sinh(yy), 2) + pow(sin(xx), 2));
4757 }
4758 // Take care of a plane at constant y.
4759 if (m_ynplay) {
4760 const double yymirr = tx * (ypos + wire.y - 2. * m_coplay);
4761 if (yymirr > 20.) {
4762 ecompl += icons;
4763 } else if (yymirr < -20.) {
4764 ecompl += -icons;
4765 } else {
4766 const std::complex<double> zzmirr(xx, yymirr);
4767 const auto expzzmirr = exp(2. * icons * zzmirr);
4768 ecompl += -icons * (expzzmirr + 1.) / (expzzmirr - 1.);
4769 }
4770 if (opt && fabs(yymirr) > 20.) r2 += fabs(yymirr) - CLog2;
4771 if (opt && fabs(yymirr) <= 20.)
4772 r2 += 0.5 * log(pow(sinh(yymirr), 2) + pow(sin(xx), 2));
4773 }
4774 // Calculate the electric field and potential.
4775 ex += wire.e * real(ecompl);
4776 ey -= wire.e * imag(ecompl);
4777 if (opt) volt += wire.e * r2;
4778 }
4779 ex *= tx;
4780 ey *= tx;
4781}
4782
4783void ComponentAnalyticField::FieldB1Y(const double xpos, const double ypos,
4784 double& ex, double& ey, double& volt,
4785 const bool opt) const {
4786 //-----------------------------------------------------------------------
4787 // EFCB1Y - Routine calculating the potential for a row of positive
4788 // charges. The potential used is Re(Log(sinh pi/sy(z-z0)).
4789 // VARIABLES : See routine EFCA00 for most of the variables.
4790 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
4791 // ECOMPL : EX + I*EY ; I**2=-1
4792 //-----------------------------------------------------------------------
4793
4794 std::complex<double> ecompl;
4795
4796 double r2 = 0.;
4797
4798 // Initialise the electric field and potential.
4799 ex = ey = 0.;
4800 volt = m_v0;
4801
4802 const double ty = Pi / m_sy;
4803 // Loop over all wires.
4804 for (const auto& wire : m_w) {
4805 const double xx = ty * (xpos - wire.x);
4806 const double yy = ty * (ypos - wire.y);
4807 // Calculate the field in case there are no equipotential planes.
4808 if (xx > 20.) {
4809 ecompl = 1.;
4810 } else if (xx < -20.) {
4811 ecompl = -1.;
4812 } else {
4813 const std::complex<double> zz(xx, yy);
4814 const auto expzz = exp(2. * zz);
4815 ecompl = (expzz + 1.) / (expzz - 1.);
4816 }
4817 if (opt) {
4818 if (fabs(xx) > 20.) r2 = -fabs(xx) + CLog2;
4819 if (fabs(xx) <= 20.) r2 = -0.5 * log(pow(sinh(xx), 2) + pow(sin(yy), 2));
4820 }
4821 // Take care of a plane at constant x.
4822 if (m_ynplax) {
4823 const double xxmirr = ty * (xpos + wire.x - 2. * m_coplax);
4824 if (xxmirr > 20.) {
4825 ecompl -= 1.;
4826 } else if (xxmirr < -20.) {
4827 ecompl += 1.;
4828 } else {
4829 const std::complex<double> zzmirr(xxmirr, yy);
4830 const auto expzzmirr = exp(2. * zzmirr);
4831 ecompl -= (expzzmirr + 1.) / (expzzmirr - 1.);
4832 }
4833 if (opt && fabs(xxmirr) > 20.) r2 += fabs(xxmirr) - CLog2;
4834 if (opt && fabs(xxmirr) <= 20.)
4835 r2 += 0.5 * log(pow(sinh(xxmirr), 2) + pow(sin(yy), 2));
4836 }
4837 // Calculate the electric field and potential.
4838 ex += wire.e * real(ecompl);
4839 ey -= wire.e * imag(ecompl);
4840 if (opt) volt += wire.e * r2;
4841 }
4842 ex *= ty;
4843 ey *= ty;
4844}
4845
4846void ComponentAnalyticField::FieldB2X(const double xpos, const double ypos,
4847 double& ex, double& ey, double& volt,
4848 const bool opt) const {
4849 //-----------------------------------------------------------------------
4850 // EFCB2X - Routine calculating the potential for a row of alternating
4851 // + - charges. The potential used is re log(sin pi/sx (z-z0))
4852 // VARIABLES : See routine EFCA00 for most of the variables.
4853 // Z, ZZMRR : X + i*Y , XXMIRR + i*YYMIRR ; i**2=-1
4854 // ECOMPL : EX + i*EY ; i**2=-1
4855 // (Cray vectorisable)
4856 //-----------------------------------------------------------------------
4857
4858 // Initialise the electric field and potential.
4859 ex = ey = 0.;
4860 volt = m_v0;
4861
4862 const double tx = HalfPi / m_sx;
4863 // Loop over all wires.
4864 for (unsigned int i = 0; i < m_nWires; ++i) {
4865 const double xx = tx * (xpos - m_w[i].x);
4866 const double yy = tx * (ypos - m_w[i].y);
4867 const double xxneg = tx * (xpos - m_w[i].x - 2 * m_coplax);
4868 // Calculate the field in case there are no equipotential planes.
4869 std::complex<double> ecompl(0., 0.);
4870 double r2 = 1.;
4871 if (fabs(yy) <= 20.) {
4872 const std::complex<double> zz(xx, yy);
4873 const std::complex<double> zzneg(xxneg, yy);
4874 ecompl = -m_b2sin[i] / (sin(zz) * sin(zzneg));
4875 if (opt) {
4876 const double sinhy = sinh(yy);
4877 const double sinxx = sin(xx);
4878 const double sinxxneg = sin(xxneg);
4879 r2 = (sinhy * sinhy + sinxx * sinxx) /
4880 (sinhy * sinhy + sinxxneg * sinxxneg);
4881 }
4882 }
4883 // Take care of a planes at constant y.
4884 if (m_ynplay) {
4885 const double yymirr = tx * (ypos + m_w[i].y - 2 * m_coplay);
4886 if (fabs(yymirr) <= 20.) {
4887 const std::complex<double> zzmirr(xx, yymirr);
4888 const std::complex<double> zznmirr(xxneg, yymirr);
4889 ecompl += m_b2sin[i] / (sin(zzmirr) * sin(zznmirr));
4890 if (opt) {
4891 const double sinhy = sinh(yymirr);
4892 const double sinxx = sin(xx);
4893 const double sinxxneg = sin(xxneg);
4894 const double r2plan = (sinhy * sinhy + sinxx * sinxx) /
4895 (sinhy * sinhy + sinxxneg * sinxxneg);
4896 r2 /= r2plan;
4897 }
4898 }
4899 }
4900 // Calculate the electric field and potential.
4901 ex += m_w[i].e * real(ecompl);
4902 ey -= m_w[i].e * imag(ecompl);
4903 if (opt) volt -= 0.5 * m_w[i].e * log(r2);
4904 }
4905 ex *= tx;
4906 ey *= tx;
4907}
4908
4909void ComponentAnalyticField::FieldB2Y(const double xpos, const double ypos,
4910 double& ex, double& ey, double& volt,
4911 const bool opt) const {
4912 //-----------------------------------------------------------------------
4913 // EFCB2Y - Routine calculating the potential for a row of alternating
4914 // + - charges. The potential used is re log(sin pi/sx (z-z0))
4915 // VARIABLES : See routine EFCA00 for most of the variables.
4916 // Z, ZMIRR : X + i*Y , XXMIRR + i*YYMIRR ; i**2=-1
4917 // ECOMPL : EX + i*EY ; i**2=-1
4918 // (Cray vectorisable)
4919 //-----------------------------------------------------------------------
4920
4921 const std::complex<double> icons(0., 1.);
4922
4923 // Initialise the electric field and potential.
4924 ex = ey = 0.;
4925 volt = m_v0;
4926
4927 const double ty = HalfPi / m_sy;
4928 // Loop over all wires.
4929 for (unsigned int i = 0; i < m_nWires; ++i) {
4930 const double xx = ty * (xpos - m_w[i].x);
4931 const double yy = ty * (ypos - m_w[i].y);
4932 const double yyneg = ty * (ypos + m_w[i].y - 2 * m_coplay);
4933 // Calculate the field in case there are no equipotential planes.
4934 std::complex<double> ecompl(0., 0.);
4935 double r2 = 1.;
4936 if (fabs(xx) <= 20.) {
4937 const std::complex<double> zz(xx, yy);
4938 const std::complex<double> zzneg(xx, yyneg);
4939 ecompl = icons * m_b2sin[i] / (sin(icons * zz) * sin(icons * zzneg));
4940 if (opt) {
4941 const double sinhx = sinh(xx);
4942 const double sinyy = sin(yy);
4943 const double sinyyneg = sin(yyneg);
4944 r2 = (sinhx * sinhx + sinyy * sinyy) /
4945 (sinhx * sinhx + sinyyneg * sinyyneg);
4946 }
4947 }
4948 // Take care of a plane at constant x.
4949 if (m_ynplax) {
4950 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
4951 if (fabs(xxmirr) <= 20.) {
4952 const std::complex<double> zzmirr(xxmirr, yy);
4953 const std::complex<double> zznmirr(xxmirr, yyneg);
4954 ecompl -=
4955 icons * m_b2sin[i] / (sin(icons * zzmirr) * sin(icons * zznmirr));
4956 if (opt) {
4957 const double sinhx = sinh(xxmirr);
4958 const double sinyy = sin(yy);
4959 const double sinyyneg = sin(yyneg);
4960 const double r2plan = (sinhx * sinhx + sinyy * sinyy) /
4961 (sinhx * sinhx + sinyyneg * sinyyneg);
4962 r2 /= r2plan;
4963 }
4964 }
4965 }
4966 // Calculate the electric field and potential.
4967 ex += m_w[i].e * real(ecompl);
4968 ey -= m_w[i].e * imag(ecompl);
4969 if (opt) volt -= 0.5 * m_w[i].e * log(r2);
4970 }
4971 ex *= ty;
4972 ey *= ty;
4973}
4974
4975void ComponentAnalyticField::FieldC10(const double xpos, const double ypos,
4976 double& ex, double& ey, double& volt,
4977 const bool opt) const {
4978 //-----------------------------------------------------------------------
4979 // EFCC10 - Routine returning the potential and electric field. It
4980 // calls the routines PH2 and E2SUM written by G.A.Erskine.
4981 // VARIABLES : No local variables.
4982 //-----------------------------------------------------------------------
4983
4984 // Calculate voltage first, if needed.
4985 if (opt) {
4986 if (m_mode == 0) volt = m_v0 + m_c1 * xpos;
4987 if (m_mode == 1) volt = m_v0 + m_c1 * ypos;
4988 for (const auto& wire : m_w) {
4989 volt += wire.e * Ph2(xpos - wire.x, ypos - wire.y);
4990 }
4991 }
4992
4993 // And finally the electric field.
4994 E2Sum(xpos, ypos, ex, ey);
4995 if (m_mode == 0) ex -= m_c1;
4996 if (m_mode == 1) ey -= m_c1;
4997}
4998
4999void ComponentAnalyticField::FieldC2X(const double xpos, const double ypos,
5000 double& ex, double& ey, double& volt,
5001 const bool opt) const {
5002 //-----------------------------------------------------------------------
5003 // EFCC2X - Routine returning the potential and electric field in a
5004 // configuration with 2 x planes and y periodicity.
5005 // VARIABLES : see the writeup
5006 //-----------------------------------------------------------------------
5007
5008 constexpr std::complex<double> icons(0., 1.);
5009
5010 // Initial values.
5011 std::complex<double> wsum1 = 0.;
5012 std::complex<double> wsum2 = 0.;
5013 volt = 0.;
5014
5015 // Wire loop.
5016 for (const auto& wire : m_w) {
5017 // Compute the direct contribution.
5018 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
5019 if (imag(zeta) > 15.) {
5020 wsum1 -= wire.e * icons;
5021 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5022 } else if (imag(zeta) < -15.) {
5023 wsum1 += wire.e * icons;
5024 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5025 } else {
5026 const auto zterm = Th1(zeta, m_p1, m_p2);
5027 wsum1 += wire.e * (zterm.second / zterm.first);
5028 if (opt) volt -= wire.e * log(abs(zterm.first));
5029 }
5030 // Find the plane nearest to the wire.
5031 const double cx = m_coplax - m_sx * int(round((m_coplax - wire.x) / m_sx));
5032 // Mirror contribution.
5033 zeta =
5034 m_zmult * std::complex<double>(2. * cx - xpos - wire.x, ypos - wire.y);
5035 if (imag(zeta) > 15.) {
5036 wsum2 -= wire.e * icons;
5037 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5038 } else if (imag(zeta) < -15.) {
5039 wsum2 += wire.e * icons;
5040 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5041 } else {
5042 const auto zterm = Th1(zeta, m_p1, m_p2);
5043 wsum2 += wire.e * (zterm.second / zterm.first);
5044 if (opt) volt += wire.e * log(abs(zterm.first));
5045 }
5046 // Correct the voltage, if needed (MODE).
5047 if (opt && m_mode == 0) {
5048 volt -= TwoPi * wire.e * (xpos - cx) * (wire.x - cx) / (m_sx * m_sy);
5049 }
5050 }
5051 // Convert the two contributions to a real field.
5052 ex = real(m_zmult * (wsum1 + wsum2));
5053 ey = -imag(m_zmult * (wsum1 - wsum2));
5054 // Constant correction terms.
5055 if (m_mode == 0) ex -= m_c1;
5056}
5057
5058void ComponentAnalyticField::FieldC2Y(const double xpos, const double ypos,
5059 double& ex, double& ey, double& volt,
5060 const bool opt) const {
5061 //-----------------------------------------------------------------------
5062 // EFCC2Y - Routine returning the potential and electric field in a
5063 // configuration with 2 y planes and x periodicity.
5064 // VARIABLES : see the writeup
5065 //-----------------------------------------------------------------------
5066
5067 constexpr std::complex<double> icons(0., 1.);
5068
5069 // Initial values.
5070 volt = 0.;
5071 std::complex<double> wsum1 = 0.;
5072 std::complex<double> wsum2 = 0.;
5073
5074 // Wire loop.
5075 for (const auto& wire : m_w) {
5076 // Compute the direct contribution.
5077 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
5078 if (imag(zeta) > 15.) {
5079 wsum1 -= wire.e * icons;
5080 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5081 } else if (imag(zeta) < -15.) {
5082 wsum1 += wire.e * icons;
5083 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5084 } else {
5085 const auto zterm = Th1(zeta, m_p1, m_p2);
5086 wsum1 += wire.e * (zterm.second / zterm.first);
5087 if (opt) volt -= wire.e * log(abs(zterm.first));
5088 }
5089 // Find the plane nearest to the wire.
5090 const double cy = m_coplay - m_sy * int(round((m_coplay - wire.y) / m_sy));
5091 // Mirror contribution from the y plane.
5092 zeta =
5093 m_zmult * std::complex<double>(xpos - wire.x, 2 * cy - ypos - wire.y);
5094 if (imag(zeta) > 15.) {
5095 wsum2 -= wire.e * icons;
5096 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5097 } else if (imag(zeta) < -15.) {
5098 wsum2 += wire.e * icons;
5099 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5100 } else {
5101 const auto zterm = Th1(zeta, m_p1, m_p2);
5102 wsum2 += wire.e * (zterm.second / zterm.first);
5103 if (opt) volt += wire.e * log(abs(zterm.first));
5104 }
5105 // Correct the voltage, if needed (MODE).
5106 if (opt && m_mode == 1) {
5107 volt -= TwoPi * wire.e * (ypos - cy) * (wire.y - cy) / (m_sx * m_sy);
5108 }
5109 }
5110 // Convert the two contributions to a real field.
5111 ex = real(m_zmult * (wsum1 - wsum2));
5112 ey = -imag(m_zmult * (wsum1 + wsum2));
5113 // Constant correction terms.
5114 if (m_mode == 1) ey -= m_c1;
5115}
5116
5117void ComponentAnalyticField::FieldC30(const double xpos, const double ypos,
5118 double& ex, double& ey, double& volt,
5119 const bool opt) const {
5120 //-----------------------------------------------------------------------
5121 // EFCC30 - Routine returning the potential and electric field in a
5122 // configuration with 2 y and 2 x planes.
5123 // VARIABLES : see the writeup
5124 //-----------------------------------------------------------------------
5125
5126 constexpr std::complex<double> icons(0., 1.);
5127
5128 // Initial values.
5129 std::complex<double> wsum1 = 0.;
5130 std::complex<double> wsum2 = 0.;
5131 std::complex<double> wsum3 = 0.;
5132 std::complex<double> wsum4 = 0.;
5133 volt = 0.;
5134
5135 // Wire loop.
5136 for (const auto& wire : m_w) {
5137 // Compute the direct contribution.
5138 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
5139 if (imag(zeta) > 15.) {
5140 wsum1 -= wire.e * icons;
5141 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5142 } else if (imag(zeta) < -15.) {
5143 wsum1 += wire.e * icons;
5144 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5145 } else {
5146 const auto zterm = Th1(zeta, m_p1, m_p2);
5147 wsum1 += wire.e * (zterm.second / zterm.first);
5148 if (opt) volt -= wire.e * log(abs(zterm.first));
5149 }
5150 // Find the plane nearest to the wire.
5151 const double cx = m_coplax - m_sx * int(round((m_coplax - wire.x) / m_sx));
5152 // Mirror contribution from the x plane.
5153 zeta =
5154 m_zmult * std::complex<double>(2. * cx - xpos - wire.x, ypos - wire.y);
5155 if (imag(zeta) > 15.) {
5156 wsum2 -= wire.e * icons;
5157 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5158 } else if (imag(zeta) < -15.) {
5159 wsum2 += wire.e * icons;
5160 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5161 } else {
5162 const auto zterm = Th1(zeta, m_p1, m_p2);
5163 wsum2 += wire.e * (zterm.second / zterm.first);
5164 if (opt) volt += wire.e * log(abs(zterm.first));
5165 }
5166 // Find the plane nearest to the wire.
5167 const double cy = m_coplay - m_sy * int(round((m_coplay - wire.y) / m_sy));
5168 // Mirror contribution from the x plane.
5169 zeta =
5170 m_zmult * std::complex<double>(xpos - wire.x, 2. * cy - ypos - wire.y);
5171 if (imag(zeta) > 15.) {
5172 wsum3 -= wire.e * icons;
5173 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5174 } else if (imag(zeta) < -15.) {
5175 wsum3 += wire.e * icons;
5176 if (opt) volt += wire.e * (fabs(imag(zeta)) - CLog2);
5177 } else {
5178 const auto zterm = Th1(zeta, m_p1, m_p2);
5179 wsum3 += wire.e * (zterm.second / zterm.first);
5180 if (opt) volt += wire.e * log(abs(zterm.first));
5181 }
5182 // Mirror contribution from both the x and the y plane.
5183 zeta = m_zmult * std::complex<double>(2. * cx - xpos - wire.x,
5184 2. * cy - ypos - wire.y);
5185 if (imag(zeta) > 15.) {
5186 wsum4 -= wire.e * icons;
5187 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5188 } else if (imag(zeta) < -15.) {
5189 wsum4 += wire.e * icons;
5190 if (opt) volt -= wire.e * (fabs(imag(zeta)) - CLog2);
5191 } else {
5192 const auto zterm = Th1(zeta, m_p1, m_p2);
5193 wsum4 += wire.e * (zterm.second / zterm.first);
5194 if (opt) volt -= wire.e * log(abs(zterm.first));
5195 }
5196 }
5197 // Convert the two contributions to a real field.
5198 ex = real(m_zmult * (wsum1 + wsum2 - wsum3 - wsum4));
5199 ey = -imag(m_zmult * (wsum1 - wsum2 + wsum3 - wsum4));
5200}
5201
5202void ComponentAnalyticField::FieldD10(const double xpos, const double ypos,
5203 double& ex, double& ey, double& volt,
5204 const bool opt) const {
5205 //-----------------------------------------------------------------------
5206 // EFCD10 - Subroutine performing the actual field calculations for a
5207 // cell which has a one circular plane and some wires.
5208 // VARIABLES : EX, EY, VOLT:Electric field and potential.
5209 // ETOT, VOLT : Magnitude of electric field, potential.
5210 // (XPOS,YPOS): The position where the field is calculated.
5211 // ZI, ZPOS : Shorthand complex notations.
5212 // (Last changed on 4/ 9/95.)
5213 //-----------------------------------------------------------------------
5214
5215 // Initialise the electric field and potential.
5216 ex = ey = 0.;
5217 volt = m_v0;
5218
5219 // Set the complex position coordinates.
5220 const std::complex<double> zpos = std::complex<double>(xpos, ypos);
5221 // Loop over all wires.
5222 for (const auto& wire : m_w) {
5223 // Set the complex version of the wire-coordinate for simplicity.
5224 const std::complex<double> zi(wire.x, wire.y);
5225 // Compute the contribution to the potential, if needed.
5226 if (opt) {
5227 volt -= wire.e *
5228 log(abs(m_cotube * (zpos - zi) / (m_cotube2 - zpos * conj(zi))));
5229 }
5230 // Compute the contribution to the electric field, always.
5231 const std::complex<double> wi =
5232 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
5233 ex += wire.e * real(wi);
5234 ey += wire.e * imag(wi);
5235 }
5236}
5237
5238void ComponentAnalyticField::FieldD20(const double xpos, const double ypos,
5239 double& ex, double& ey, double& volt,
5240 const bool opt) const {
5241 //-----------------------------------------------------------------------
5242 // EFCD20 - Subroutine performing the actual field calculations for a
5243 // cell which has a tube and phi periodicity.
5244 // VARIABLES : EX, EY, VOLT:Electric field and potential.
5245 // ETOT, VOLT : Magnitude of electric field, potential.
5246 // (XPOS,YPOS): The position where the field is calculated.
5247 // ZI, ZPOS : Shorthand complex notations.
5248 // (Last changed on 10/ 2/93.)
5249 //-----------------------------------------------------------------------
5250
5251 // Initialise the electric field and potential.
5252 ex = ey = 0.;
5253 volt = m_v0;
5254
5255 // Set the complex position coordinates.
5256 const std::complex<double> zpos = std::complex<double>(xpos, ypos);
5257 // Loop over all wires.
5258 for (const auto& wire : m_w) {
5259 // Set the complex version of the wire-coordinate for simplicity.
5260 const std::complex<double> zi(wire.x, wire.y);
5261 // Case of the wire which is not in the centre.
5262 if (abs(zi) > wire.r) {
5263 // Compute the contribution to the potential, if needed.
5264 if (opt) {
5265 volt -=
5266 wire.e * log(abs((1. / pow(m_cotube, m_mtube)) *
5267 (pow(zpos, m_mtube) - pow(zi, m_mtube)) /
5268 (1. - pow(zpos * conj(zi) / m_cotube2, m_mtube))));
5269 }
5270 // Compute the contribution to the electric field, always.
5271 const std::complex<double> wi =
5272 double(m_mtube) * pow(conj(zpos), m_mtube - 1) *
5273 (1. / conj(pow(zpos, m_mtube) - pow(zi, m_mtube)) +
5274 pow(zi, m_mtube) /
5275 (pow(m_cotube, 2 * m_mtube) - pow(conj(zpos) * zi, m_mtube)));
5276 ex += wire.e * real(wi);
5277 ey += wire.e * imag(wi);
5278 } else {
5279 // Case of the central wire.
5280 if (opt) {
5281 volt -= wire.e * log(abs((1. / m_cotube) * (zpos - zi) /
5282 (1. - zpos * conj(zi) / m_cotube2)));
5283 }
5284 const std::complex<double> wi =
5285 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
5286 // Compute the contribution to the electric field, always.
5287 ex += wire.e * real(wi);
5288 ey += wire.e * imag(wi);
5289 }
5290 }
5291}
5292
5293void ComponentAnalyticField::FieldD30(const double xpos, const double ypos,
5294 double& ex, double& ey, double& volt,
5295 const bool opt) const {
5296 //-----------------------------------------------------------------------
5297 // EFCD30 - Subroutine performing the actual field calculations for a
5298 // cell which has a polygon as tube and some wires.
5299 // VARIABLES : EX, EY, VOLT:Electric field and potential.
5300 // ETOT, VOLT : Magnitude of electric field, potential.
5301 // (XPOS,YPOS): The position where the field is calculated.
5302 // ZI, ZPOS : Shorthand complex notations.
5303 // (Last changed on 19/ 2/94.)
5304 //-----------------------------------------------------------------------
5305
5306 // Initialise the electric field and potential.
5307 ex = ey = 0.;
5308 volt = m_v0;
5309
5310 std::complex<double> whelp;
5311
5312 // Get the mapping of the position.
5313 std::complex<double> wpos, wdpos;
5314 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
5315 // Loop over all wires.
5316 for (int i = m_nWires; i--;) {
5317 // Compute the contribution to the potential, if needed.
5318 if (opt) {
5319 volt -=
5320 m_w[i].e * log(abs((wpos - wmap[i]) / (1. - wpos * conj(wmap[i]))));
5321 }
5322 whelp = wdpos * (1. - pow(abs(wmap[i]), 2)) /
5323 ((wpos - wmap[i]) * (1. - conj(wmap[i]) * wpos));
5324 // Compute the contribution to the electric field, always.
5325 ex += m_w[i].e * real(whelp);
5326 ey -= m_w[i].e * imag(whelp);
5327 }
5328 ex /= m_cotube;
5329 ey /= m_cotube;
5330}
5331
5332bool ComponentAnalyticField::InTube(const double x0, const double y0,
5333 const double a, const int n) const {
5334 //-----------------------------------------------------------------------
5335 // INTUBE - Determines whether a point is located inside a polygon.
5336 // ILOC is set to +1 if outside, 0 if inside and -1 if the
5337 // arguments are not valid.
5338 // (Last changed on 18/ 3/01.)
5339 //-----------------------------------------------------------------------
5340
5341 // Special case: x = y = 0
5342 if (x0 == 0. && y0 == 0.) return true;
5343
5344 // Special case: round tube.
5345 if (n == 0) {
5346 if (x0 * x0 + y0 * y0 > a * a) return false;
5347 return true;
5348 }
5349
5350 if (n < 0 || n == 1 || n == 2) {
5351 std::cerr << m_className << "::InTube:\n";
5352 std::cerr << " Invalid number of edges (n = " << n << ")\n";
5353 return false;
5354 }
5355
5356 // Truly polygonal tubes.
5357 // Reduce angle to the first sector.
5358 double phi = atan2(y0, x0);
5359 if (phi < 0.) phi += TwoPi;
5360 phi -= TwoPi * int(0.5 * n * phi / Pi) / n;
5361 // Compare the length to the local radius.
5362 if ((x0 * x0 + y0 * y0) * pow(cos(Pi / n - phi), 2) >
5363 a * a * pow(cos(Pi / n), 2))
5364 return false;
5365
5366 return true;
5367}
5368
5369void ComponentAnalyticField::Field3dA00(const double xpos, const double ypos,
5370 const double zpos, double& ex,
5371 double& ey, double& ez, double& volt) {
5372 //-----------------------------------------------------------------------
5373 // E3DA00 - Subroutine adding 3-dimensional charges for A cells.
5374 // The potential used is 1/2*pi*eps0 1/r
5375 // VARIABLES : EX, EY : x,y-component of the electric field.
5376 // ETOT : Magnitude of electric field.
5377 // VOLT : Potential.
5378 // EXHELP etc : One term in the series to be summed.
5379 // (XPOS,YPOS): The position where the field is calculated.
5380 // (Last changed on 5/12/94.)
5381 //-----------------------------------------------------------------------
5382
5383 // Initialise the electric field and potential.
5384 ex = ey = ez = volt = 0.;
5385
5386 // Loop over all charges.
5387 for (const auto& charge : m_ch3d) {
5388 // Calculate the field in case there are no planes.
5389 const double dx = xpos - charge.x;
5390 const double dy = ypos - charge.y;
5391 const double dz = zpos - charge.z;
5392 const double r = sqrt(dx * dx + dy * dy + dz * dz);
5393 if (fabs(r) < Small) continue;
5394 const double r3 = pow(r, 3);
5395 double exhelp = -dx / r3;
5396 double eyhelp = -dy / r3;
5397 double ezhelp = -dz / r3;
5398 double vhelp = 1. / r;
5399 // Take care of a plane at constant x.
5400 double dxm = 0., dym = 0.;
5401 if (m_ynplax) {
5402 dxm = charge.x + xpos - 2 * m_coplax;
5403 const double rplan = sqrt(dxm * dxm + dy * dy);
5404 if (fabs(rplan) < Small) continue;
5405 const double rplan3 = pow(rplan, 3);
5406 exhelp += dxm / rplan3;
5407 eyhelp += dy / rplan3;
5408 ezhelp += dz / rplan3;
5409 vhelp -= 1. / rplan;
5410 }
5411 // Take care of a plane at constant y.
5412 if (m_ynplay) {
5413 dym = charge.y + ypos - 2. * m_coplay;
5414 const double rplan = sqrt(dx * dx + dym * dym);
5415 if (fabs(rplan) < Small) continue;
5416 const double rplan3 = pow(rplan, 3);
5417 exhelp += dx / rplan3;
5418 eyhelp += dym / rplan3;
5419 ezhelp += dz / rplan3;
5420 vhelp -= 1. / rplan;
5421 }
5422 // Take care of pairs of planes.
5423 if (m_ynplax && m_ynplay) {
5424 const double rplan = sqrt(dxm * dxm + dym * dym);
5425 if (fabs(rplan) < Small) continue;
5426 const double rplan3 = pow(rplan, 3);
5427 exhelp -= dxm / rplan3;
5428 eyhelp -= dym / rplan3;
5429 ezhelp -= dz / rplan3;
5430 vhelp += 1. / rplan;
5431 }
5432 // Add the terms to the electric field and the potential.
5433 ex -= charge.e * exhelp;
5434 ey -= charge.e * eyhelp;
5435 ez -= charge.e * ezhelp;
5436 volt += charge.e * vhelp;
5437 }
5438}
5439
5440void ComponentAnalyticField::Field3dB2X(const double xpos, const double ypos,
5441 const double zpos, double& ex,
5442 double& ey, double& ez, double& volt) {
5443 //-----------------------------------------------------------------------
5444 // E3DB2X - Routine calculating the potential for a 3 dimensional point
5445 // charge between two plates at constant x.
5446 // The series expansions for the modified Bessel functions
5447 // have been taken from Abramowitz and Stegun.
5448 // VARIABLES : See routine E3DA00 for most of the variables.
5449 // (Last changed on 5/12/94.)
5450 //-----------------------------------------------------------------------
5451
5452 const double rcut = 1.;
5453
5454 double rr1, rr2, rm1, rm2, err, ezz;
5455 double exsum = 0., eysum = 0., ezsum = 0., vsum = 0.;
5456 double k0r, k1r, k0rm, k1rm;
5457
5458 // Initialise the sums for the field components.
5459 ex = ey = ez = volt = 0.;
5460
5461 // Loop over all charges.
5462 for (const auto& charge : m_ch3d) {
5463 // Skip coordinates that are on the charge.
5464 if (xpos == charge.x && ypos == charge.y && zpos == charge.z) continue;
5465 const double dx = xpos - charge.x;
5466 const double dy = ypos - charge.y;
5467 const double dz = zpos - charge.z;
5468 const double dxm = xpos + charge.x - 2 * m_coplax;
5469 // In the far away zone, sum the modified Bessel function series.
5470 if (dy * dy + dz * dz > pow(rcut * 2 * m_sx, 2)) {
5471 // Initialise the per-wire sum.
5472 exsum = eysum = ezsum = vsum = 0.;
5473 // Loop over the terms in the series.
5474 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5475 // Obtain reduced coordinates.
5476 const double rr = Pi * j * sqrt(dy * dy + dz * dz) / m_sx;
5477 const double zzp = Pi * j * dx / m_sx;
5478 const double zzn = Pi * j * dxm / m_sx;
5479 // Evaluate the Bessel functions for this R.
5480 if (rr < 2.) {
5481 k0r = Numerics::BesselK0S(rr);
5482 k1r = Numerics::BesselK1S(rr);
5483 } else {
5484 k0r = Numerics::BesselK0L(rr);
5485 k1r = Numerics::BesselK1L(rr);
5486 }
5487 // Get the field components.
5488 const double czzp = cos(zzp);
5489 const double czzn = cos(zzn);
5490 vsum += (1. / m_sx) * k0r * (czzp - czzn);
5491 err = (TwoPi * j / (m_sx * m_sx)) * k1r * (czzp - czzn);
5492 ezz = (TwoPi * j / (m_sx * m_sx)) * k0r * (sin(zzp) - sin(zzn));
5493 exsum += ezz;
5494 eysum += err * dy / sqrt(dy * dy + dz * dz);
5495 ezsum += err * dz / sqrt(dy * dy + dz * dz);
5496 continue;
5497 }
5498 } else {
5499 // Direct polynomial summing, obtain reduced coordinates.
5500 // Loop over the terms.
5501 for (unsigned int j = 0; j <= m_nTermPoly; ++j) {
5502 // Simplify the references to the distances.
5503 rr1 = sqrt(pow(dx + j * 2 * m_sx, 2) + dy * dy + dz * dz);
5504 rr2 = sqrt(pow(dx - j * 2 * m_sx, 2) + dy * dy + dz * dz);
5505 rm1 = sqrt(pow(dxm - j * 2 * m_sx, 2) + dy * dy + dz * dz);
5506 rm2 = sqrt(pow(dxm + j * 2 * m_sx, 2) + dy * dy + dz * dz);
5507 const double rr13 = pow(rr1, 3);
5508 const double rm13 = pow(rm1, 3);
5509 // Initialisation of the sum: only a charge and a mirror charge.
5510 if (j == 0) {
5511 vsum = 1. / rr1 - 1. / rm1;
5512 exsum = dx / rr13 - dxm / rm13;
5513 eysum = dy * (1. / rr13 - 1. / rm13);
5514 ezsum = dz * (1. / rr13 - 1. / rm13);
5515 continue;
5516 }
5517 const double rr23 = pow(rr2, 3);
5518 const double rm23 = pow(rm2, 3);
5519 // Further terms in the series: 2 charges and 2 mirror charges.
5520 vsum += 1. / rr1 + 1. / rr2 - 1. / rm1 - 1. / rm2;
5521 exsum += (dx + j * 2 * m_sx) / rr13 + (dx - j * 2 * m_sx) / rr23 -
5522 (dxm - j * 2 * m_sx) / rm13 - (dxm + j * 2 * m_sx) / rm23;
5523 eysum += dy * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5524 ezsum += dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5525 }
5526 }
5527 // Take care of a plane at constant y.
5528 if (m_ynplay) {
5529 const double dym = ypos + charge.y - 2. * m_coplay;
5530 if (dym * dym + dz * dz > pow(rcut * 2 * m_sx, 2)) {
5531 // Bessel function series.
5532 // Loop over the terms in the series.
5533 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5534 // Obtain reduced coordinates.
5535 const double rrm = Pi * j * sqrt(dym * dym + dz * dz) / m_sx;
5536 const double zzp = Pi * j * dx / m_sx;
5537 const double zzn = Pi * j * dxm / m_sx;
5538 // Evaluate the Bessel functions for this R.
5539 if (rrm < 2.) {
5540 k0rm = Numerics::BesselK0S(rrm);
5541 k1rm = Numerics::BesselK1S(rrm);
5542 } else {
5543 k0rm = Numerics::BesselK0L(rrm);
5544 k1rm = Numerics::BesselK1L(rrm);
5545 }
5546 // Get the field components.
5547 const double czzp = cos(zzp);
5548 const double czzn = cos(zzn);
5549 vsum += (1. / m_sx) * k0rm * (czzp - czzn);
5550 err = (TwoPi / (m_sx * m_sx)) * k1rm * (czzp - czzn);
5551 ezz = (TwoPi / (m_sx * m_sx)) * k0rm * (sin(zzp) - sin(zzn));
5552 exsum += ezz;
5553 eysum += err * dym / sqrt(dym * dym + dz * dz);
5554 ezsum += err * dz / sqrt(dym * dym + dz * dz);
5555 }
5556 } else {
5557 // Polynomial sum.
5558 // Loop over the terms.
5559 for (unsigned int j = 0; j <= m_nTermPoly; ++j) {
5560 // Simplify the references to the distances.
5561 rr1 = sqrt(pow(dx + j * 2 * m_sx, 2) + dym * dym + dz * dz);
5562 rr2 = sqrt(pow(dx - j * 2 * m_sx, 2) + dym * dym + dz * dz);
5563 rm1 = sqrt(pow(dxm - j * 2 * m_sx, 2) + dym * dym + dz * dz);
5564 rm2 = sqrt(pow(dxm + j * 2 * m_sx, 2) + dym * dym + dz * dz);
5565 const double rr13 = pow(rr1, 3);
5566 const double rm13 = pow(rm1, 3);
5567 // Initialisation of the sum: only a charge and a mirror charge.
5568 if (j == 0) {
5569 vsum += -1. / rr1 + 1. / rm1;
5570 exsum += -dx / rr13 + dxm / rm13;
5571 eysum += -dym * (1. / rr13 - 1. / rm13);
5572 ezsum += -dz * (1. / rr13 - 1. / rm13);
5573 continue;
5574 }
5575 const double rr23 = pow(rr2, 3);
5576 const double rm23 = pow(rm2, 3);
5577 // Further terms in the series: 2 charges and 2 mirror charges.
5578 vsum += -1. / rr1 - 1. / rr2 + 1. / rm1 + 1. / rm2;
5579 exsum += -(dx + j * 2 * m_sx) / rr13 - (dx - j * 2 * m_sx) / rr23 +
5580 (dxm - j * 2 * m_sx) / rm13 + (dxm + j * 2 * m_sx) / rm23;
5581 eysum += -dym * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5582 ezsum += -dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5583 }
5584 }
5585 }
5586 ex += charge.e * exsum;
5587 ey += charge.e * eysum;
5588 ez += charge.e * ezsum;
5589 volt += charge.e * vsum;
5590 }
5591}
5592
5593void ComponentAnalyticField::Field3dB2Y(const double xpos, const double ypos,
5594 const double zpos, double& ex,
5595 double& ey, double& ez, double& volt) {
5596 //-----------------------------------------------------------------------
5597 // E3DB2Y - Routine calculating the potential for a 3 dimensional point
5598 // charge between two plates at constant y.
5599 // The series expansions for the modified Bessel functions
5600 // have been taken from Abramowitz and Stegun.
5601 // VARIABLES : See routine E3DA00 for most of the variables.
5602 // (Last changed on 5/12/94.)
5603 //-----------------------------------------------------------------------
5604
5605 const double rcut = 1.;
5606
5607 double rr1, rr2, rm1, rm2, err, ezz;
5608 double exsum = 0., eysum = 0., ezsum = 0., vsum = 0.;
5609 double k0r, k1r, k0rm, k1rm;
5610
5611 // Initialise the sums for the field components.
5612 ex = ey = ez = volt = 0.;
5613
5614 // Loop over all charges.
5615 for (const auto& charge : m_ch3d) {
5616 // Skip wires that are on the charge.
5617 if (xpos == charge.x && ypos == charge.y && zpos == charge.z) continue;
5618 const double dx = xpos - charge.x;
5619 const double dy = ypos - charge.y;
5620 const double dz = zpos - charge.z;
5621 const double dym = ypos + charge.y - 2 * m_coplay;
5622 // In the far away zone, sum the modified Bessel function series.
5623 if (dx * dx + dz * dz > pow(rcut * 2 * m_sy, 2)) {
5624 // Initialise the per-wire sum.
5625 exsum = eysum = ezsum = vsum = 0.;
5626 // Loop over the terms in the series.
5627 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5628 // Obtain reduced coordinates.
5629 const double rr = Pi * j * sqrt(dx * dx + dz * dz) / m_sy;
5630 const double zzp = Pi * j * dy / m_sy;
5631 const double zzn = Pi * j * dym / m_sy;
5632 // Evaluate the Bessel functions for this R.
5633 if (rr < 2.) {
5634 k0r = Numerics::BesselK0S(rr);
5635 k1r = Numerics::BesselK1S(rr);
5636 } else {
5637 k0r = Numerics::BesselK0L(rr);
5638 k1r = Numerics::BesselK1L(rr);
5639 }
5640 // Get the field components.
5641 const double czzp = cos(zzp);
5642 const double czzn = cos(zzn);
5643 vsum += (1. / m_sy) * k0r * (czzp - czzn);
5644 err = (TwoPi * j / (m_sy * m_sy)) * k1r * (czzp - czzn);
5645 ezz = (TwoPi * j / (m_sy * m_sy)) * k0r * (sin(zzp) - sin(zzn));
5646 exsum += err * dx / sqrt(dx * dx + dz * dz);
5647 ezsum += err * dz / sqrt(dx * dx + dz * dz);
5648 eysum += ezz;
5649 continue;
5650 }
5651 } else {
5652 // Direct polynomial summing, obtain reduced coordinates.
5653 // Loop over the terms.
5654 for (unsigned int j = 0; j <= m_nTermPoly; ++j) {
5655 // Simplify the references to the distances.
5656 rr1 = sqrt(dx * dx + dz * dz + pow(dy + j * 2 * m_sy, 2));
5657 rr2 = sqrt(dx * dx + dz * dz + pow(dy - j * 2 * m_sy, 2));
5658 rm1 = sqrt(dx * dx + dz * dz + pow(dym - j * 2 * m_sy, 2));
5659 rm2 = sqrt(dx * dx + dz * dz + pow(dym + j * 2 * m_sy, 2));
5660 const double rr13 = pow(rr1, 3);
5661 const double rm13 = pow(rm1, 3);
5662 // Initialisation of the sum: only a charge and a mirror charge.
5663 if (j == 0) {
5664 vsum = 1. / rr1 - 1. / rm1;
5665 exsum = dx * (1. / rr13 - 1. / rm13);
5666 ezsum = dz * (1. / rr13 - 1. / rm13);
5667 eysum = dy / rr13 - dym / rm13;
5668 continue;
5669 }
5670 // Further terms in the series: 2 charges and 2 mirror charges.
5671 const double rr23 = pow(rr2, 3);
5672 const double rm23 = pow(rm2, 3);
5673 vsum += 1. / rr1 + 1. / rr2 - 1. / rm1 - 1. / rm2;
5674 exsum += dx * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5675 ezsum += dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5676 eysum += (dy + j * 2 * m_sy) / rr13 + (dy - j * 2 * m_sy) / rr23 -
5677 (dym - j * 2 * m_sy) / rm13 - (dym + j * 2 * m_sy) / rm23;
5678 }
5679 }
5680 // Take care of a plane at constant x.
5681 if (m_ynplax) {
5682 const double dxm = xpos + charge.x - 2. * m_coplax;
5683 if (dxm * dxm + dz * dz > pow(rcut * 2 * m_sy, 2)) {
5684 // Bessel function series.
5685 // Loop over the terms in the series.
5686 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5687 // Obtain reduced coordinates.
5688 const double rrm = Pi * j * sqrt(dxm * dxm + dz * dz) / m_sy;
5689 const double zzp = Pi * j * dy / m_sy;
5690 const double zzn = Pi * j * dym / m_sy;
5691 // Evaluate the Bessel functions for this R.
5692 if (rrm < 2.) {
5693 k0rm = Numerics::BesselK0S(rrm);
5694 k1rm = Numerics::BesselK1S(rrm);
5695 } else {
5696 k0rm = Numerics::BesselK0L(rrm);
5697 k1rm = Numerics::BesselK1L(rrm);
5698 }
5699 // Get the field components.
5700 const double czzp = cos(zzp);
5701 const double czzn = cos(zzn);
5702 vsum += (1. / m_sy) * k0rm * (czzp - czzn);
5703 err = (TwoPi / (m_sy * m_sy)) * k1rm * (czzp - czzn);
5704 ezz = (TwoPi / (m_sy * m_sy)) * k0rm * (sin(zzp) - sin(zzn));
5705 exsum += err * dxm / sqrt(dxm * dxm + dz * dz);
5706 ezsum += err * dz / sqrt(dxm * dxm + dz * dz);
5707 eysum += ezz;
5708 }
5709 } else {
5710 // Polynomial sum.
5711 // Loop over the terms.
5712 for (unsigned int j = 0; j <= m_nTermPoly; ++j) {
5713 // Simplify the references to the distances.
5714 rr1 = sqrt(pow(dy + j * 2 * m_sy, 2) + dxm * dxm + dz * dz);
5715 rr2 = sqrt(pow(dy - j * 2 * m_sy, 2) + dxm * dxm + dz * dz);
5716 rm1 = sqrt(pow(dym - j * 2 * m_sy, 2) + dxm * dxm + dz * dz);
5717 rm2 = sqrt(pow(dym + j * 2 * m_sy, 2) + dxm * dxm + dz * dz);
5718 const double rr13 = pow(rr1, 3);
5719 const double rm13 = pow(rm1, 3);
5720 // Initialisation of the sum: only a charge and a mirror charge.
5721 if (j == 0) {
5722 vsum += -1. / rr1 + 1. / rm1;
5723 exsum += -dxm * (1. / rr13 - 1. / rm13);
5724 ezsum += -dz * (1. / rr13 - 1. / rm13);
5725 eysum += -dy / rr13 + dym / rm13;
5726 continue;
5727 }
5728 const double rr23 = pow(rr2, 3);
5729 const double rm23 = pow(rm2, 3);
5730 // Further terms in the series: 2 charges and 2 mirror charges.
5731 vsum += -1. / rr1 - 1. / rr2 + 1. / rm1 + 1. / rm2;
5732 exsum += -dxm * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5733 ezsum += -dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5734 eysum += -(dy + j * 2 * m_sy) / rr13 - (dy - j * 2 * m_sy) / rr23 +
5735 (dym - j * 2 * m_sy) / rm13 + (dym + j * 2 * m_sy) / rm23;
5736 }
5737 }
5738 }
5739 ex += charge.e * exsum;
5740 ey += charge.e * eysum;
5741 ez += charge.e * ezsum;
5742 volt += charge.e * vsum;
5743 }
5744}
5745
5746void ComponentAnalyticField::Field3dD10(const double xxpos, const double yypos,
5747 const double zzpos, double& eex,
5748 double& eey, double& eez,
5749 double& volt) {
5750 //-----------------------------------------------------------------------
5751 // E3DD10 - Subroutine adding 3-dimensional charges to tubes with one
5752 // wire running down the centre.
5753 // The series expansions for the modified Bessel functions
5754 // have been taken from Abramowitz and Stegun.
5755 // VARIABLES : See routine E3DA00 for most of the variables.
5756 // (Last changed on 25/11/95.)
5757 //-----------------------------------------------------------------------
5758
5759 const double rcut = 1.;
5760
5761 double x3d, y3d, z3d;
5762 double exsum = 0., eysum = 0., ezsum = 0., vsum = 0.;
5763 double rr1, rr2, rm1, rm2, err, ezz;
5764 double k0r, k1r;
5765
5766 // Initialise the sums for the field components.
5767 eex = eey = eez = volt = 0.;
5768 double ex = 0., ey = 0., ez = 0.;
5769
5770 // Ensure that the routine can actually work.
5771 if (m_nWires < 1) {
5772 std::cerr << m_className << "::Field3dD10:\n";
5773 std::cerr << " Inappropriate potential function.\n";
5774 return;
5775 }
5776
5777 // Define a periodicity and one plane in the mapped frame.
5778 const double ssx = log(m_cotube / m_w[0].r);
5779 const double cpl = log(m_w[0].r);
5780
5781 // Transform the coordinates to the mapped frame.
5782 const double xpos = 0.5 * log(xxpos * xxpos + yypos * yypos);
5783 const double ypos = atan2(yypos, xxpos);
5784 const double zpos = zzpos;
5785
5786 // Loop over all point charges.
5787 for (const auto& charge : m_ch3d) {
5788 for (int ii = -1; ii <= 1; ++ii) {
5789 // TODO
5790 x3d = 0.5 * log(charge.x * charge.x + charge.y * charge.y);
5791 y3d = atan2(charge.y, charge.x + ii * TwoPi);
5792 z3d = charge.z;
5793 const double dx = xpos - x3d;
5794 const double dy = ypos - y3d;
5795 const double dz = zpos - z3d;
5796 const double dxm = xpos + x3d - 2 * cpl;
5797 // Skip wires that are on the charge.
5798 if (xpos == x3d && ypos == y3d && zpos == z3d) continue;
5799 // In the far away zone, sum the modified Bessel function series.
5800 if (dy * dy + dz * dz > pow(rcut * 2 * ssx, 2)) {
5801 // Initialise the per-wire sum.
5802 exsum = eysum = ezsum = vsum = 0.;
5803 // Loop over the terms in the series.
5804 for (unsigned int j = 1; j <= m_nTermBessel; ++j) {
5805 // Obtain reduced coordinates.
5806 const double rr = Pi * j * sqrt(dy * dy + dz * dz) / ssx;
5807 const double zzp = Pi * j * dx / ssx;
5808 const double zzn = Pi * j * dxm / ssx;
5809 // Evaluate the Bessel functions for this R.
5810 if (rr < 2.) {
5811 k0r = Numerics::BesselK0S(rr);
5812 k1r = Numerics::BesselK1S(rr);
5813 } else {
5814 k0r = Numerics::BesselK0L(rr);
5815 k1r = Numerics::BesselK1L(rr);
5816 }
5817 // Get the field components.
5818 const double czzp = cos(zzp);
5819 const double czzn = cos(zzn);
5820 vsum += (1. / ssx) * k0r * (czzp - czzn);
5821 err = (j * TwoPi / (ssx * ssx)) * k1r * (czzp - czzn);
5822 ezz = (j * TwoPi / (ssx * ssx)) * k0r * (sin(zzp) - sin(zzn));
5823 exsum += ezz;
5824 eysum += err * dy / sqrt(dy * dy + dz * dz);
5825 ezsum += err * dz / sqrt(dy * dy + dz * dz);
5826 }
5827 } else {
5828 // Direct polynomial summing, obtain reduced coordinates.
5829 // Loop over the terms.
5830 // TODO: <=?
5831 for (unsigned int j = 0; j < m_nTermPoly; ++j) {
5832 // Simplify the references to the distances.
5833 rr1 = sqrt(pow(dx + j * 2 * ssx, 2) + dy * dy + dz * dz);
5834 rr2 = sqrt(pow(dx - j * 2 * ssx, 2) + dy * dy + dz * dz);
5835 rm1 = sqrt(pow(dxm - j * 2 * ssx, 2) + dy * dy + dz * dz);
5836 rm2 = sqrt(pow(dxm + j * 2 * ssx, 2) + dy * dy + dz * dz);
5837 const double rr13 = pow(rr1, 3);
5838 const double rm13 = pow(rm1, 3);
5839 // Initialisation of the sum: only a charge and a mirror charge.
5840 if (j == 0) {
5841 vsum = 1. / rr1 - 1. / rm1;
5842 exsum = dxm / rr13 - dxm / rm13;
5843 eysum = dy * (1. / rr13 - 1. / rm13);
5844 ezsum = dz * (1. / rr13 - 1. / rm13);
5845 continue;
5846 }
5847 const double rr23 = pow(rr2, 3);
5848 const double rm23 = pow(rm2, 3);
5849 // Further terms in the series: 2 charges and 2 mirror charges.
5850 vsum += 1. / rr1 + 1. / rr2 - 1. / rm1 - 1. / rm2;
5851 exsum += (dx + j * 2 * ssx) / rr13 + (dx - j * 2 * ssx) / rr23 -
5852 (dxm - j * 2 * ssx) / rm13 - (dxm + j * 2 * ssx) / rm23;
5853 eysum += dy * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5854 ezsum += dz * (1. / rr13 + 1. / rr23 - 1. / rm13 - 1. / rm23);
5855 }
5856 }
5857 ex += charge.e * exsum;
5858 ey += charge.e * eysum;
5859 ez += charge.e * ezsum;
5860 // Finish the loop over the charges.
5861 }
5862 }
5863
5864 // Transform the field vectors back to Cartesian coordinates.
5865 eex = exp(-xpos) * (ex * cos(ypos) - ey * sin(ypos));
5866 eey = exp(-ypos) * (ex * sin(ypos) + ey * cos(ypos));
5867 eez = ez;
5868}
5869
5870bool ComponentAnalyticField::PrepareSignals() {
5871 //-----------------------------------------------------------------------
5872 // SIGINI - Initialises signal calculations.
5873 // (Last changed on 11/10/06.)
5874 //-----------------------------------------------------------------------
5875
5876 if (m_readout.empty()) {
5877 std::cerr << m_className << "::PrepareSignals:\n";
5878 std::cerr << " There are no readout groups defined.\n";
5879 std::cerr << " Calculation of weighting fields makes no sense.\n";
5880 return false;
5881 }
5882
5883 if (!m_cellset && !Prepare()) {
5884 std::cerr << m_className << "::PrepareSignals: Cell not set up.\n";
5885 return false;
5886 }
5887
5888 std::lock_guard<std::mutex> guard(m_mutex);
5889
5890 // If using natural periodicity, copy the cell type.
5891 // Otherwise, eliminate true periodicities.
5892 if (m_nFourier == 0) {
5893 m_cellTypeFourier = m_cellType;
5894 } else if (m_cellType == A00 || m_cellType == B1X || m_cellType == B1Y ||
5895 m_cellType == C10) {
5896 m_cellTypeFourier = A00;
5897 } else if (m_cellType == B2X || m_cellType == C2X) {
5898 m_cellTypeFourier = B2X;
5899 } else if (m_cellType == B2Y || m_cellType == C2Y) {
5900 m_cellTypeFourier = B2Y;
5901 } else if (m_cellType == C30) {
5902 m_cellTypeFourier = C30;
5903 } else if (m_cellType == D10) {
5904 m_cellTypeFourier = D10;
5905 } else if (m_cellType == D30) {
5906 m_cellTypeFourier = D30;
5907 } else {
5908 // Other cases.
5909 std::cerr << m_className << "::PrepareSignals:\n"
5910 << " No potentials available to handle cell type "
5911 << GetCellType(m_cellType) << "\n.";
5912 return false;
5913 }
5914
5915 // Establish the directions in which convolutions occur.
5916 m_fperx = m_fpery = false;
5917 if (m_nFourier == 0) {
5918 m_mfexp = 0;
5919 } else {
5920 if (m_cellType == B1X || m_cellType == C10 || m_cellType == C2Y) {
5921 m_fperx = true;
5922 }
5923 if (m_cellType == B1Y || m_cellType == C10 || m_cellType == C2X) {
5924 m_fpery = true;
5925 }
5926 m_mfexp = int(0.1 + log(1. * m_nFourier) / log(2.));
5927 if (m_mfexp == 0) {
5928 m_fperx = m_fpery = false;
5929 }
5930 }
5931 // Set maximum and minimum Fourier terms.
5932 m_mxmin = m_mymin = m_mxmax = m_mymax = 0;
5933 if (m_fperx) {
5934 m_mxmin = std::min(0, 1 - m_nFourier / 2);
5935 m_mxmax = m_nFourier / 2;
5936 }
5937 if (m_fpery) {
5938 m_mymin = std::min(0, 1 - m_nFourier / 2);
5939 m_mymax = m_nFourier / 2;
5940 }
5941
5942 // Print some debugging output if requested.
5943 if (m_debug) {
5944 std::cout << m_className << "::PrepareSignals:\n"
5945 << " Cell type: " << GetCellType(m_cellType) << "\n"
5946 << " Fourier cell type: " << GetCellType(m_cellTypeFourier)
5947 << "\n";
5948 std::cout << " x convolutions: " << m_fperx << "\n"
5949 << " y convolutions: " << m_fpery << "\n";
5950 std::cout << " No of Fourier terms: " << m_nFourier << " (= 2**"
5951 << m_mfexp << ")\n";
5952 }
5953
5954 // Prepare the signal matrices.
5955 if (!SetupWireSignals()) {
5956 std::cerr << m_className << "::PrepareSignals:\n";
5957 std::cerr << " Preparing wire signal capacitance matrices failed.\n";
5958 m_sigmat.clear();
5959 return false;
5960 }
5961 if (!SetupPlaneSignals()) {
5962 std::cerr << m_className << "::PrepareSignals:\n";
5963 std::cerr << " Preparing plane charges failed.\n";
5964 m_sigmat.clear();
5965 m_qplane.clear();
5966 return false;
5967 }
5968
5969 // And open the signal file.
5970 // CALL SIGIST('OPEN',0,DUMMY,DUMMY,0,0,0,0,IFAIL1)
5971
5972 // Associate wires, planes and strips with readout groups
5973 const unsigned int nReadout = m_readout.size();
5974 for (unsigned int i = 0; i < nReadout; ++i) {
5975 for (auto& wire : m_w) {
5976 if (wire.type == m_readout[i]) wire.ind = i;
5977 }
5978 for (unsigned int j = 0; j < 5; ++j) {
5979 if (m_planes[j].type == m_readout[i]) m_planes[j].ind = i;
5980 for (auto& strip : m_planes[j].strips1) {
5981 if (strip.type == m_readout[i]) strip.ind = i;
5982 }
5983 for (auto& strip : m_planes[j].strips2) {
5984 if (strip.type == m_readout[i]) strip.ind = i;
5985 }
5986 for (auto& pixel : m_planes[j].pixels) {
5987 if (pixel.type == m_readout[i]) pixel.ind = i;
5988 }
5989 }
5990 }
5991
5992 // Seems to have worked.
5993 m_sigset = true;
5994 return true;
5995}
5996
5997bool ComponentAnalyticField::SetupWireSignals() {
5998 //-----------------------------------------------------------------------
5999 // SIGIPR - Prepares the ion tail calculation by filling the signal
6000 // matrices (ie non-periodic capacitance matrices),
6001 // Fourier transforming them if necessary, inverting them and
6002 // Fourier transforming them back. Because of the large number
6003 // of terms involved, a (scratch) external file on unit 13 is
6004 // used to store the intermediate and final results. This file
6005 // is handled by the routines IONBGN and IONIO.
6006 // VARIABLES : FFTMAT : Matrix used for Fourier transforms.
6007 // (Last changed on 4/10/06.)
6008 //-----------------------------------------------------------------------
6009
6010 m_sigmat.assign(m_nWires, std::vector<std::complex<double> >(m_nWires));
6011
6012 std::vector<std::vector<std::complex<double> > > fftmat;
6013
6014 if (m_fperx || m_fpery) {
6015 fftmat.resize(m_nFourier);
6016 for (int i = 0; i < m_nFourier; ++i) {
6017 fftmat[i].resize(m_nWires);
6018 }
6019 }
6020
6021 if (m_fperx || m_fpery) {
6022 // CALL IONBGN(IFAIL)
6023 // IF(IFAIL.EQ.1)THEN
6024 // PRINT *,' !!!!!! SIGIPR WARNING : No storage'
6025 // ' available for the signal matrices; no'
6026 // ' induced currents.'
6027 // RETURN
6028 // ENDIF
6029 }
6030
6031 // Have the matrix/matrices filled (and stored).
6032 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6033 for (int my = m_mymin; my <= m_mymax; ++my) {
6034 // Select layer to be produced.
6035 if (m_cellTypeFourier == A00) {
6036 IprA00(mx, my);
6037 } else if (m_cellTypeFourier == B2X) {
6038 IprB2X(my);
6039 } else if (m_cellTypeFourier == B2Y) {
6040 IprB2Y(mx);
6041 } else if (m_cellTypeFourier == C2X) {
6042 IprC2X();
6043 } else if (m_cellTypeFourier == C2Y) {
6044 IprC2Y();
6045 } else if (m_cellTypeFourier == C30) {
6046 IprC30();
6047 } else if (m_cellTypeFourier == D10) {
6048 IprD10();
6049 } else if (m_cellTypeFourier == D30) {
6050 IprD30();
6051 } else {
6052 std::cerr << m_className << "::SetupWireSignals:\n";
6053 std::cerr << " Unknown signal cell type " << m_cellTypeFourier
6054 << "\n";
6055 return false;
6056 }
6057 if (m_debug) {
6058 std::cout << m_className << "::SetupWireSignals:\n";
6059 std::cout << " Signal matrix MX = " << mx << ", MY = " << my
6060 << " has been calculated.\n";
6061 }
6062 if (m_fperx || m_fpery) {
6063 // Store the matrix.
6064 // CALL IONIO(MX,MY,1,0,IFAIL)
6065 // Quit if storing failed.
6066 // IF(IFAIL.NE.0)GOTO 2010
6067 }
6068 // Dump the signal matrix before inversion, if DEBUG is requested.
6069 if (m_debug) {
6070 std::cout << m_className << "::SetupWireSignals:\n";
6071 std::cout << " Dump of signal matrix (" << mx << ", " << my
6072 << ") before inversion:\n";
6073 for (unsigned int i = 0; i < m_nWires; i += 10) {
6074 for (unsigned int j = 0; j < m_nWires; j += 10) {
6075 std::cout << " (Re-Block " << i / 10 << ", " << j / 10 << ")\n";
6076 for (unsigned int ii = 0; ii < 10; ++ii) {
6077 if (i + ii >= m_nWires) break;
6078 for (unsigned int jj = 0; jj < 10; ++jj) {
6079 if (j + jj >= m_nWires) break;
6080 std::cout << real(m_sigmat[i + ii][j + jj]) << " ";
6081 }
6082 std::cout << "\n";
6083 }
6084 std::cout << "\n";
6085 std::cout << " (Im-Block " << i / 10 << ", " << j / 10 << ")\n";
6086 for (unsigned int ii = 0; ii < 10; ++ii) {
6087 if (i + ii >= m_nWires) break;
6088 for (unsigned int jj = 0; jj < 10; ++jj) {
6089 if (j + jj >= m_nWires) break;
6090 std::cout << imag(m_sigmat[i + ii][j + jj]) << " ";
6091 }
6092 std::cout << "\n";
6093 }
6094 std::cout << "\n";
6095 }
6096 }
6097 std::cout << m_className << "::SetupWireSignals:\n";
6098 std::cout << " End of the uninverted capacitance matrix dump.\n";
6099 }
6100 // Next layer.
6101 }
6102 }
6103
6104 // Have them fourier transformed (singly periodic case).
6105 if (m_fperx != m_fpery) {
6106 for (unsigned int i = 0; i < m_nWires; ++i) {
6107 for (int m = -m_nFourier / 2; m < m_nFourier / 2; ++m) {
6108 // CALL IONIO(M,M,2,I,IFAIL)
6109 // IF(IFAIL.NE.0)GOTO 2010
6110 for (unsigned int j = 0; j < m_nWires; ++j) {
6111 fftmat[m + m_nFourier / 2][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 m = -m_nFourier / 2; m < m_nFourier / 2; ++m) {
6118 // CALL IONIO(M,M,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[m + m_nFourier / 2][j];
6122 }
6123 // CALL IONIO(M,M,1,I,IFAIL)
6124 // IF(IFAIL.NE.0)GOTO 2010
6125 }
6126 }
6127 }
6128 // Have them fourier transformed (doubly periodic case).
6129 if (m_fperx || m_fpery) {
6130 for (unsigned int i = 0; i < m_nWires; ++i) {
6131 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6132 for (int my = m_mymin; my <= m_mymax; ++my) {
6133 // CALL IONIO(MX,MY,2,I,IFAIL)
6134 // IF(IFAIL.NE.0)GOTO 2010
6135 for (unsigned int j = 0; j < m_nWires; ++j) {
6136 fftmat[my + m_nFourier / 2 - 1][j] = m_sigmat[i][j];
6137 }
6138 }
6139 for (unsigned int j = 0; j < m_nWires; ++j) {
6140 // CALL CFFT(FFTMAT(1,J),MFEXP)
6141 }
6142 for (int my = m_mymin; my <= m_mymax; ++my) {
6143 // CALL IONIO(MX,MY,2,I,IFAIL)
6144 // IF(IFAIL.NE.0)GOTO 2010
6145 for (unsigned int j = 0; j < m_nWires; ++j) {
6146 m_sigmat[i][j] = fftmat[my + m_nFourier / 2 - 1][j];
6147 }
6148 // CALL IONIO(MX,MY,1,I,IFAIL)
6149 // IF(IFAIL.NE.0)GOTO 2010
6150 }
6151 }
6152 for (int my = m_mymin; my <= m_mymax; ++my) {
6153 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6154 // CALL IONIO(MX,MY,2,I,IFAIL)
6155 // IF(IFAIL.NE.0)GOTO 2010
6156 for (unsigned int j = 0; j < m_nWires; ++j) {
6157 fftmat[mx + m_nFourier / 2 - 1][j] = m_sigmat[i][j];
6158 }
6159 }
6160 for (unsigned int j = 0; j < m_nWires; ++j) {
6161 // CALL CFFT(FFTMAT(1,J),MFEXP)
6162 }
6163 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6164 // CALL IONIO(MX,MY,2,I,IFAIL)
6165 // IF(IFAIL.NE.0)GOTO 2010
6166 for (unsigned int j = 0; j < m_nWires; ++j) {
6167 m_sigmat[i][j] = fftmat[mx + m_nFourier / 2 - 1][j];
6168 }
6169 // CALL IONIO(MX,MY,1,I,IFAIL)
6170 // IF(IFAIL.NE.0)GOTO 2010
6171 }
6172 }
6173 }
6174 }
6175
6176 // Invert the matrices.
6177 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6178 for (int my = m_mymin; my <= m_mymax; ++my) {
6179 // Retrieve the layer.
6180 if (m_fperx || m_fpery) {
6181 // CALL IONIO(MX,MY,2,0,IFAIL)
6182 // IF(IFAIL.NE.0)GOTO 2010
6183 }
6184 // Invert.
6185 if (m_nWires >= 1) {
6186 if (Numerics::CERNLIB::cinv(m_nWires, m_sigmat) != 0) {
6187 std::cerr << m_className << "::PrepareWireSignals:\n";
6188 std::cerr << " Inversion of signal matrix (" << mx << ", " << my
6189 << ") failed.\n";
6190 std::cerr << " No reliable results.\n";
6191 std::cerr << " Preparation of weighting fields is abandoned.\n";
6192 return false;
6193 }
6194 }
6195 // Store the matrix back.
6196 if (m_fperx || m_fpery) {
6197 // CALL IONIO(MX,MY,1,0,IFAIL)
6198 // IF(IFAIL.NE.0)GOTO 2010
6199 }
6200 // Next layer.
6201 }
6202 }
6203
6204 // And transform the matrices back to the original domain.
6205 if (m_fperx != m_fpery) {
6206 for (unsigned int i = 0; i < m_nWires; ++i) {
6207 for (int m = -m_nFourier / 2; m < m_nFourier / 2; ++m) {
6208 // CALL IONIO(M,M,2,I,IFAIL)
6209 // IF(IFAIL.NE.0)GOTO 2010
6210 for (unsigned int j = 0; j < m_nWires; ++j) {
6211 fftmat[m + m_nFourier / 2][j] = m_sigmat[i][j];
6212 }
6213 }
6214 for (unsigned int j = 0; j < m_nWires; ++j) {
6215 // CALL CFFT(FFTMAT(1,J),-MFEXP)
6216 }
6217 for (int m = -m_nFourier / 2; m < m_nFourier / 2; ++m) {
6218 // CALL IONIO(M,M,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] = fftmat[m + m_nFourier / 2][j] / double(m_nFourier);
6222 }
6223 // CALL IONIO(M,M,1,I,IFAIL)
6224 // IF(IFAIL.NE.0)GOTO 2010
6225 }
6226 }
6227 }
6228 // Have them transformed to the original domain (doubly periodic).
6229 if (m_fperx && m_fpery) {
6230 for (unsigned int i = 0; i < m_nWires; ++i) {
6231 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6232 for (int my = m_mymin; my <= m_mymax; ++my) {
6233 // CALL IONIO(MX,MY,2,I,IFAIL)
6234 // IF(IFAIL.NE.0)GOTO 2010
6235 for (unsigned int j = 0; j < m_nWires; ++j) {
6236 fftmat[my + m_nFourier / 2 - 1][j] = m_sigmat[i][j];
6237 }
6238 }
6239 for (unsigned int j = 0; j < m_nWires; ++j) {
6240 // CFFT(FFTMAT(1,J),-MFEXP)
6241 }
6242 for (int my = m_mymin; my <= m_mymax; ++my) {
6243 // CALL IONIO(MX,MY,2,I,IFAIL)
6244 // IF(IFAIL.NE.0)GOTO 2010
6245 for (unsigned int j = 0; j < m_nWires; ++j) {
6246 m_sigmat[i][j] =
6247 fftmat[my + m_nFourier / 2 - 1][j] / double(m_nFourier);
6248 }
6249 // CALL IONIO(MX,MY,1,I,IFAIL)
6250 // IF(IFAIL.NE.0)GOTO 2010
6251 }
6252 }
6253 for (int my = m_mymin; my <= m_mymax; ++my) {
6254 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6255 // CALL IONIO(MX,MY,2,I,IFAIL)
6256 // IF(IFAIL.NE.0)GOTO 2010
6257 for (unsigned int j = 0; j < m_nWires; ++j) {
6258 fftmat[mx + m_nFourier / 2 - 1][j] = m_sigmat[i][j];
6259 }
6260 }
6261 for (unsigned int j = 0; j < m_nWires; ++j) {
6262 // CALL CFFT(FFTMAT(1,J),-MFEXP)
6263 }
6264 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6265 // CALL IONIO(MX,MY,2,I,IFAIL)
6266 // IF(IFAIL.NE.0)GOTO 2010
6267 for (unsigned int j = 0; j < m_nWires; ++j) {
6268 m_sigmat[i][j] =
6269 fftmat[mx + m_nFourier / 2 - 1][j] / double(m_nFourier);
6270 }
6271 // CALL IONIO(MX,MY,1,I,IFAIL)
6272 // IF(IFAIL.NE.0)GOTO 2010
6273 }
6274 }
6275 }
6276 }
6277
6278 // Dump the signal matrix after inversion, if DEBUG is requested.
6279 if (m_debug) {
6280 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6281 for (int my = m_mymin; my <= m_mymax; ++my) {
6282 std::cout << m_className << "::SetupWireSignals:\n";
6283 std::cout << " Dump of signal matrix (" << mx << ", " << my
6284 << ") after inversion:\n";
6285 for (unsigned int i = 0; i < m_nWires; i += 10) {
6286 for (unsigned int j = 0; j < m_nWires; j += 10) {
6287 std::cout << " (Re-Block " << i / 10 << ", " << j / 10 << ")\n";
6288 for (unsigned int ii = 0; ii < 10; ++ii) {
6289 if (i + ii >= m_nWires) break;
6290 for (unsigned int jj = 0; jj < 10; ++jj) {
6291 if (j + jj >= m_nWires) break;
6292 std::cout << real(m_sigmat[i + ii][j + jj]) << " ";
6293 }
6294 std::cout << "\n";
6295 }
6296 std::cout << "\n";
6297 std::cout << " (Im-Block " << i / 10 << ", " << j / 10 << ")\n";
6298 for (int ii = 0; ii < 10; ++ii) {
6299 if (i + ii >= m_nWires) break;
6300 for (int jj = 0; jj < 10; ++jj) {
6301 if (j + jj >= m_nWires) break;
6302 std::cout << imag(m_sigmat[i + ii][j + jj]) << " ";
6303 }
6304 std::cout << "\n";
6305 }
6306 std::cout << "\n";
6307 }
6308 }
6309 std::cout << m_className << "::SetupWireSignals:\n";
6310 std::cout << " End of the inverted capacitance matrix dump.\n";
6311 }
6312 }
6313 }
6314 return true;
6315}
6316
6317bool ComponentAnalyticField::SetupPlaneSignals() {
6318 //-----------------------------------------------------------------------
6319 // SIGPLP - Computes the weighting field charges for the planes and
6320 // the tube.
6321 // (Last changed on 14/10/99.)
6322 //-----------------------------------------------------------------------
6323
6324 const int nPlanes = 5;
6325 m_qplane.assign(nPlanes, std::vector<double>(m_nWires, 0.));
6326
6327 double vw;
6328
6329 // Loop over the signal layers.
6330 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6331 for (int my = m_mymin; my <= m_mymax; ++my) {
6332 // Load the layers of the signal matrices.
6333 // CALL IONIO(MX,MY,2,0,IFAIL1)
6334 // IF(IFAIL1.NE.0)THEN
6335 // PRINT *,' !!!!!! SIGPLP WARNING : Signal matrix'//
6336 // ' store error; field for planes not prepared.'
6337 // RETURN
6338 // ENDIF
6339 // Initialise the plane matrices.
6340 m_qplane.assign(nPlanes, std::vector<double>(m_nWires, 0.));
6341 // Charges for plane 1, if present.
6342 if (m_ynplan[0]) {
6343 // Set the weighting field voltages.
6344 for (unsigned int i = 0; i < m_nWires; ++i) {
6345 if (m_ynplan[1]) {
6346 vw = -(m_coplan[1] - m_w[i].x) / (m_coplan[1] - m_coplan[0]);
6347 } else if (m_perx) {
6348 vw = -(m_coplan[0] + m_sx - m_w[i].x) / m_sx;
6349 } else {
6350 vw = -1;
6351 }
6352 // Multiply with the matrix.
6353 for (unsigned int j = 0; j < m_nWires; ++j) {
6354 m_qplane[0][j] += real(m_sigmat[i][j]) * vw;
6355 }
6356 }
6357 }
6358 // Charges for plane 2, if present.
6359 if (m_ynplan[1]) {
6360 // Set the weighting field voltages.
6361 for (unsigned int i = 0; i < m_nWires; ++i) {
6362 if (m_ynplan[0]) {
6363 vw = -(m_coplan[0] - m_w[i].x) / (m_coplan[0] - m_coplan[1]);
6364 } else if (m_perx) {
6365 vw = -(m_w[i].x - m_coplan[1] + m_sx) / m_sx;
6366 } else {
6367 vw = -1.;
6368 }
6369 // Multiply with the matrix.
6370 for (unsigned int j = 0; j < m_nWires; ++j) {
6371 m_qplane[1][j] += real(m_sigmat[i][j]) * vw;
6372 }
6373 }
6374 }
6375 // Charges for plane 3, if present.
6376 if (m_ynplan[2]) {
6377 // Set the weighting field voltages.
6378 for (unsigned int i = 0; i < m_nWires; ++i) {
6379 if (m_ynplan[3]) {
6380 vw = -(m_coplan[3] - m_w[i].y) / (m_coplan[3] - m_coplan[2]);
6381 } else if (m_pery) {
6382 vw = -(m_coplan[2] + m_sy - m_w[i].y) / m_sy;
6383 } else {
6384 vw = -1.;
6385 }
6386 // Multiply with the matrix.
6387 for (unsigned int j = 0; j < m_nWires; ++j) {
6388 m_qplane[2][i] += real(m_sigmat[i][j]) * vw;
6389 }
6390 }
6391 }
6392 // Charges for plane 4, if present.
6393 if (m_ynplan[3]) {
6394 // Set the weighting field voltages.
6395 for (unsigned int i = 0; i < m_nWires; ++i) {
6396 if (m_ynplan[2]) {
6397 vw = -(m_coplan[2] - m_w[i].y) / (m_coplan[2] - m_coplan[3]);
6398 } else if (m_pery) {
6399 vw = -(m_w[i].y - m_coplan[3] + m_sy) / m_sy;
6400 } else {
6401 vw = -1.;
6402 }
6403 // Multiply with the matrix.
6404 for (unsigned int j = 0; j < m_nWires; ++j) {
6405 m_qplane[3][i] += real(m_sigmat[i][j]) * vw;
6406 }
6407 }
6408 }
6409 // Charges for the tube, if present.
6410 if (m_tube) {
6411 for (unsigned int i = 0; i < m_nWires; ++i) {
6412 for (unsigned int j = 0; j < m_nWires; ++j) {
6413 m_qplane[4][i] -= real(m_sigmat[i][j]);
6414 }
6415 }
6416 }
6417 // Store the plane charges.
6418 // CALL IPLIO(MX,MY,1,IFAIL1)
6419 // IF(IFAIL1.NE.0)THEN
6420 // PRINT *,' !!!!!! SIGPLP WARNING : Plane matrix'//
6421 // ' store error; field for planes not prepared.'
6422 // RETURN
6423 // ENDIF
6424 // Next layer.
6425 }
6426 }
6427 // Compute the background weighting fields, first in x.
6428 if (m_ynplan[0] && m_ynplan[1]) {
6429 m_planes[0].ewxcor = 1. / (m_coplan[1] - m_coplan[0]);
6430 m_planes[1].ewxcor = 1. / (m_coplan[0] - m_coplan[1]);
6431 } else if (m_ynplan[0] && m_perx) {
6432 m_planes[0].ewxcor = 1. / m_sx;
6433 m_planes[1].ewxcor = 0.;
6434 } else if (m_ynplan[1] && m_perx) {
6435 m_planes[0].ewxcor = 0.;
6436 m_planes[1].ewxcor = -1. / m_sx;
6437 } else {
6438 m_planes[0].ewxcor = m_planes[1].ewxcor = 0.;
6439 }
6440 m_planes[2].ewxcor = m_planes[3].ewxcor = m_planes[4].ewxcor = 0.;
6441 // Next also in y.
6442 m_planes[0].ewycor = m_planes[1].ewycor = 0.;
6443 if (m_ynplan[2] && m_ynplan[3]) {
6444 m_planes[2].ewycor = 1. / (m_coplan[3] - m_coplan[2]);
6445 m_planes[3].ewycor = 1. / (m_coplan[2] - m_coplan[3]);
6446 } else if (m_ynplan[2] && m_pery) {
6447 m_planes[2].ewycor = 1. / m_sy;
6448 m_planes[3].ewycor = 0.;
6449 } else if (m_ynplan[3] && m_pery) {
6450 m_planes[2].ewycor = 0.;
6451 m_planes[3].ewycor = -1. / m_sy;
6452 } else {
6453 m_planes[2].ewycor = m_planes[3].ewycor = 0.;
6454 }
6455 // The tube has no correction field.
6456 m_planes[4].ewycor = 0.;
6457
6458 // Debugging output.
6459 if (m_debug) {
6460 std::cout << m_className << "::SetupPlaneSignals:\n";
6461 std::cout << " Charges for currents induced in the planes:\n";
6462 std::cout << " Wire x-Plane 1 x-Plane 2"
6463 << " y-Plane 1 y-Plane 2"
6464 << " Tube\n";
6465 for (unsigned int i = 0; i < m_nWires; ++i) {
6466 std::cout << " " << i << " " << m_qplane[0][i] << " "
6467 << m_qplane[1][i] << " " << m_qplane[2][i] << " "
6468 << m_qplane[3][i] << " " << m_qplane[4][i] << "\n";
6469 }
6470 std::cout << m_className << "::SetupPlaneSignals:\n";
6471 std::cout << " Bias fields:\n";
6472 std::cout << " Plane x-Bias [1/cm] y-Bias [1/cm]\n";
6473 for (int i = 0; i < 4; ++i) {
6474 std::cout << " " << i << " " << m_planes[i].ewxcor << " "
6475 << m_planes[i].ewycor << "\n";
6476 }
6477 }
6478
6479 return true;
6480}
6481
6482bool ComponentAnalyticField::IprA00(const int mx, const int my) {
6483 //-----------------------------------------------------------------------
6484 // IPRA00 - Routine filling the (MX,MY) th layer of the signal matrix
6485 // for cells with non-periodic type A (see SIGIPR).
6486 //-----------------------------------------------------------------------
6487
6488 const double dx = mx * m_sx;
6489 const double dy = my * m_sy;
6490 double aa = 0.;
6491
6492 for (unsigned int i = 0; i < m_nWires; ++i) {
6493 // Diagonal terms.
6494 if (dx != 0. || dy != 0.) {
6495 aa = dx * dx + dy * dy;
6496 } else {
6497 aa = m_w[i].r * m_w[i].r;
6498 }
6499 // Take care of single equipotential planes.
6500 if (m_ynplax) aa /= 2. * pow(m_w[i].x - m_coplax, 2) + dy * dy;
6501 if (m_ynplay) aa /= 2. * pow(m_w[i].y - m_coplay, 2) + dx * dx;
6502 // Take care of pairs of equipotential planes.
6503 if (m_ynplax && m_ynplay)
6504 aa *= 4. * (pow(m_w[i].x - m_coplax, 2) + pow(m_w[i].y - m_coplay, 2));
6505 // Define the final version of a[i][i].
6506 m_sigmat[i][i] = -0.5 * log(aa);
6507 for (unsigned int j = i + 1; j < m_nWires; ++j) {
6508 aa = pow(m_w[i].x + dx - m_w[j].x, 2) + pow(m_w[i].y + dy - m_w[j].y, 2);
6509 // Take care of single planes.
6510 if (m_ynplax)
6511 aa /= pow(2. * m_coplax - m_w[i].x - dx - m_w[j].x, 2) +
6512 pow(m_w[i].y + dy - m_w[j].y, 2);
6513 if (m_ynplay)
6514 aa /= pow(m_w[i].x + dx - m_w[j].x, 2) +
6515 pow(2. * m_coplay - m_w[i].y - dy - m_w[j].y, 2);
6516 // Take care of pairs of planes.
6517 if (m_ynplax && m_ynplay) {
6518 aa *= pow(2. * m_coplax - m_w[i].x - dx - m_w[j].x, 2) +
6519 pow(2. * m_coplay - m_w[i].y - dy - m_w[j].y, 2);
6520 }
6521 // Store the true versions after taking LOGs and SQRT's.
6522 m_sigmat[i][j] = -0.5 * log(aa);
6523 m_sigmat[j][i] = m_sigmat[i][j];
6524 }
6525 }
6526 return true;
6527}
6528
6529bool ComponentAnalyticField::IprB2X(const int my) {
6530 //-----------------------------------------------------------------------
6531 // IPRB2X - Routine filling the MY th layer of the signal matrix
6532 // for cells with non-periodic type B2X (see SIGIPR).
6533 // (Last changed on 26/ 4/92.)
6534 //-----------------------------------------------------------------------
6535
6536 m_b2sin.resize(m_nWires);
6537
6538 const double dy = my * m_sy;
6539 double aa = 0.;
6540 double xxneg;
6541
6542 // Loop over all wires and calculate the diagonal elements first.
6543 for (unsigned int i = 0; i < m_nWires; ++i) {
6544 double xx = (Pi / m_sx) * (m_w[i].x - m_coplan[0]);
6545 if (dy != 0.) {
6546 aa = pow(sinh(Pi * dy / m_sx) / sin(xx), 2);
6547 } else {
6548 aa = pow((0.5 * m_w[i].r * Pi / m_sx) / sin(xx), 2);
6549 }
6550 // Take care of a planes at constant y (no dy in this case).
6551 if (m_ynplay) {
6552 const double yymirr = (Pi / m_sx) * (m_w[i].y - m_coplay);
6553 if (fabs(yymirr) <= 20.) {
6554 const double sinhy = sinh(yymirr);
6555 const double sinxx = sin(xx);
6556 aa *= (sinhy * sinhy + sinxx * sinxx) / (sinhy * sinhy);
6557 }
6558 }
6559 // Store the true value of A[i][i].
6560 m_sigmat[i][i] = -0.5 * log(aa);
6561 // Loop over all other wires to obtain off-diagonal elements.
6562 for (unsigned int j = i + 1; j < m_nWires; ++j) {
6563 const double yy = HalfPi * (m_w[i].y + dy - m_w[j].y) / m_sx;
6564 xx = HalfPi * (m_w[i].x - m_w[j].x) / m_sx;
6565 xxneg = HalfPi * (m_w[i].x + m_w[j].x - 2. * m_coplan[0]) / m_sx;
6566 if (fabs(yy) <= 20.) {
6567 const double sinhy = sinh(yy);
6568 const double sinxx = sin(xx);
6569 const double sinxxneg = sin(xxneg);
6570 aa = (sinhy * sinhy + sinxx * sinxx) /
6571 (sinhy * sinhy + sinxxneg * sinxxneg);
6572 } else {
6573 aa = 1.;
6574 }
6575 // Take equipotential planes into account (no dy anyhow).
6576 if (m_ynplay) {
6577 const double yymirr =
6578 HalfPi * (m_w[i].y + m_w[j].y - 2. * m_coplay) / m_sx;
6579 if (fabs(yymirr) <= 20.) {
6580 const double sinhy = sinh(yymirr);
6581 const double sinxx = sin(xx);
6582 const double sinxxneg = sin(xxneg);
6583 aa *= (sinhy * sinhy + sinxxneg * sinxxneg) /
6584 (sinhy * sinhy + sinxx * sinxx);
6585 }
6586 }
6587 // Store the true value of a[i][j] in both a[i][j] and a[j][i].
6588 m_sigmat[i][j] = -0.5 * log(aa);
6589 m_sigmat[j][i] = m_sigmat[i][j];
6590 }
6591 // Fill the B2SIN vector.
6592 m_b2sin[i] = sin(Pi * (m_coplan[0] - m_w[i].x) / m_sx);
6593 }
6594
6595 return true;
6596}
6597
6598bool ComponentAnalyticField::IprB2Y(const int mx) {
6599 //-----------------------------------------------------------------------
6600 // IPRB2Y - Routine filling the MX th layer of the signal matrix
6601 // for cells with non-periodic type B2Y (see SIGIPR).
6602 // (Last changed on 26/ 4/92.)
6603 //-----------------------------------------------------------------------
6604
6605 m_b2sin.resize(m_nWires);
6606
6607 const double dx = mx * m_sx;
6608 double aa = 0.;
6609 double xx, yy, xxmirr, yyneg;
6610
6611 // Loop over all wires and calculate the diagonal elements first.
6612 for (unsigned int i = 0; i < m_nWires; ++i) {
6613 yy = (Pi / m_sy) * (m_w[i].y - m_coplan[2]);
6614 if (dx != 0.) {
6615 aa = pow(sinh(Pi * dx / m_sy) / sin(yy), 2);
6616 } else {
6617 aa = pow((0.5 * m_w[i].r * Pi / m_sy) / sin(yy), 2);
6618 }
6619 // Take care of a planes at constant x (no dx in this case).
6620 if (m_ynplax) {
6621 xxmirr = (Pi / m_sy) * (m_w[i].x - m_coplax);
6622 if (fabs(xxmirr) <= 20.) {
6623 aa *= (pow(sinh(xxmirr), 2) + pow(sin(yy), 2)) / pow(sinh(xxmirr), 2);
6624 }
6625 }
6626 // Store the true value of A[i][i].
6627 m_sigmat[i][i] = -0.5 * log(aa);
6628 // Loop over all other wires to obtain off-diagonal elements.
6629 for (unsigned int j = i + 1; j < m_nWires; ++j) {
6630 xx = HalfPi * (m_w[i].x + dx - m_w[j].x) / m_sy;
6631 yy = HalfPi * (m_w[i].y - m_w[j].y) / m_sy;
6632 yyneg = HalfPi * (m_w[i].y + m_w[j].y - 2. * m_coplan[2]) / m_sy;
6633 if (fabs(xx) <= 20.) {
6634 aa = (pow(sinh(xx), 2) + pow(sin(yy), 2)) /
6635 (pow(sinh(xx), 2) + pow(sin(yyneg), 2));
6636 } else {
6637 aa = 1.;
6638 }
6639 // Take equipotential planes into account (no dx anyhow).
6640 if (m_ynplax) {
6641 xxmirr = HalfPi * (m_w[i].x + m_w[j].x - 2. * m_coplax) / m_sy;
6642 if (fabs(xxmirr) <= 20.) {
6643 aa *= (pow(sinh(xxmirr), 2) + pow(sin(yyneg), 2)) /
6644 (pow(sinh(xxmirr), 2) + pow(sin(yy), 2));
6645 }
6646 }
6647 // Store the true value of a[i][j] in both a[i][j] and a[j][i].
6648 m_sigmat[i][j] = -0.5 * log(aa);
6649 m_sigmat[j][i] = m_sigmat[i][j];
6650 }
6651 // Fill the B2SIN vector.
6652 m_b2sin[i] = sin(Pi * (m_coplan[2] - m_w[i].y) / m_sy);
6653 }
6654 return true;
6655}
6656
6657bool ComponentAnalyticField::IprC2X() {
6658 //-----------------------------------------------------------------------
6659 // IPRC2X - This initializing subroutine stores the capacitance matrix
6660 // for the configuration:
6661 // wires at zw(j)+cmplx(lx*2*sx,ly*sy),
6662 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
6663 // but the signs of the charges alternate in the x-direction
6664 // (Last changed on 4/10/06.)
6665 //-----------------------------------------------------------------------
6666
6667 // Fill the capacitance matrix.
6668 for (unsigned int i = 0; i < m_nWires; ++i) {
6669 double cx = m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
6670 for (unsigned int j = 0; j < m_nWires; ++j) {
6671 double temp = 0.;
6672 if (m_mode == 0) {
6673 temp = (m_w[i].x - cx) * (m_w[j].x - cx) * TwoPi / (m_sx * m_sy);
6674 }
6675 if (i == j) {
6676 m_sigmat[i][j] =
6677 Ph2Lim(m_w[i].r) - Ph2(2. * (m_w[j].x - cx), 0.) - temp;
6678 } else {
6679 m_sigmat[i][j] =
6680 Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
6681 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y - m_w[j].y) - temp;
6682 }
6683 }
6684 }
6685 return true;
6686}
6687
6688bool ComponentAnalyticField::IprC2Y() {
6689 //-----------------------------------------------------------------------
6690 // IPRC2Y - This initializing subroutine stores the capacitance matrix
6691 // for the configuration:
6692 // wires at zw(j)+cmplx(lx*sx,ly*2*sy),
6693 // j=1(1)n, lx=-infinity(1)infinity, ly=-infinity(1)infinity.
6694 // but the signs of the charges alternate in the y-direction
6695 // (Last changed on 4/10/06.)
6696 //-----------------------------------------------------------------------
6697
6698 // Fill the capacitance matrix.
6699 for (unsigned int i = 0; i < m_nWires; ++i) {
6700 const double cy =
6701 m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
6702 for (unsigned int j = 0; j < m_nWires; ++j) {
6703 double temp = 0.;
6704 if (m_mode == 1) {
6705 temp = (m_w[i].y - cy) * (m_w[j].y - cy) * TwoPi / (m_sx * m_sy);
6706 }
6707 if (i == j) {
6708 m_sigmat[i][j] =
6709 Ph2Lim(m_w[i].r) - Ph2(0., 2. * (m_w[j].y - cy)) - temp;
6710 } else {
6711 m_sigmat[i][j] =
6712 Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
6713 Ph2(m_w[i].x - m_w[j].x, m_w[i].y + m_w[j].y - 2. * cy) - temp;
6714 }
6715 }
6716 }
6717 return true;
6718}
6719
6720bool ComponentAnalyticField::IprC30() {
6721 //-----------------------------------------------------------------------
6722 // IPRC30 - Routine filling the signal matrix for cells of type C30.
6723 // Since the signal matrix equals the capacitance matrix for
6724 // this potential, the routine is identical to SETC30 except
6725 // for the C and P parameters.
6726 // (Last changed on 11/11/97.)
6727 //-----------------------------------------------------------------------
6728
6729 // Fill the capacitance matrix.
6730 for (unsigned int i = 0; i < m_nWires; ++i) {
6731 const double cx =
6732 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
6733 const double cy =
6734 m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
6735 for (unsigned int j = 0; j < m_nWires; ++j) {
6736 if (i == j) {
6737 m_sigmat[i][i] = Ph2Lim(m_w[i].r) -
6738 Ph2(0., 2. * (m_w[i].y - cy)) -
6739 Ph2(2. * (m_w[i].x - cx), 0.) +
6740 Ph2(2. * (m_w[i].x - cx), 2. * (m_w[i].y - cy));
6741 } else {
6742 m_sigmat[i][j] =
6743 Ph2(m_w[i].x - m_w[j].x, m_w[i].y - m_w[j].y) -
6744 Ph2(m_w[i].x - m_w[j].x, m_w[i].y + m_w[j].y - 2. * cy) -
6745 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y - m_w[j].y) +
6746 Ph2(m_w[i].x + m_w[j].x - 2. * cx, m_w[i].y + m_w[j].y - 2. * cy);
6747 }
6748 }
6749 }
6750 return true;
6751}
6752
6753bool ComponentAnalyticField::IprD10() {
6754 //-----------------------------------------------------------------------
6755 // IPRD10 - Signal matrix preparation for D1 cells.
6756 // VARIABLES :
6757 // (Last changed on 2/ 2/93.)
6758 //-----------------------------------------------------------------------
6759
6760 // Loop over all wires.
6761 for (unsigned int i = 0; i < m_nWires; ++i) {
6762 // Set the diagonal terms.
6763 m_sigmat[i][i] = -log(
6764 m_w[i].r /
6765 (m_cotube - (m_w[i].x * m_w[i].x + m_w[i].y * m_w[i].y) / m_cotube));
6766 // Set a complex wire-coordinate to make things a little easier.
6767 std::complex<double> zi(m_w[i].x, m_w[i].y);
6768 // Loop over all other wires for the off-diagonal elements.
6769 for (unsigned int j = i + 1; j < m_nWires; ++j) {
6770 // Set a complex wire-coordinate to make things a little easier.
6771 std::complex<double> zj(m_w[j].x, m_w[j].y);
6772 m_sigmat[i][j] = -log(
6773 abs((1. / m_cotube) * (zi - zj) / (1. - conj(zi) * zj / m_cotube2)));
6774 // Copy this to a[j][i] since the capacitance matrix is symmetric.
6775 m_sigmat[j][i] = m_sigmat[i][j];
6776 }
6777 }
6778 return true;
6779}
6780
6781bool ComponentAnalyticField::IprD30() {
6782 //-----------------------------------------------------------------------
6783 // IPRD30 - Signal matrix preparation for polygonal cells (type D3).
6784 // Variables :
6785 // (Last changed on 19/ 6/97.)
6786 //-----------------------------------------------------------------------
6787
6788 wmap.resize(m_nWires);
6789
6790 std::complex<double> wd;
6791
6792 // Loop over all wire combinations.
6793 for (int i = 0; i < int(m_nWires); ++i) {
6794 // We need to compute the wire mapping again to obtain WD.
6795 ConformalMap(std::complex<double>(m_w[i].x, m_w[i].y) / m_cotube, wmap[i],
6796 wd);
6797 // Diagonal elements.
6798 m_sigmat[i][i] = -log(
6799 abs((m_w[i].r / m_cotube) * wd / (1. - pow(abs(wmap[i]), 2))));
6800 // Loop over all other wires for the off-diagonal elements.
6801 for (int j = 0; j < i - 1; ++j) {
6802 m_sigmat[i][j] =
6803 -log(abs((wmap[i] - wmap[j]) / (1. - conj(wmap[i]) * wmap[j])));
6804 // Copy this to a[j][i] since the capacitance matrix is symmetric.
6805 m_sigmat[j][i] = m_sigmat[i][j];
6806 }
6807 }
6808 return true;
6809}
6810
6811bool ComponentAnalyticField::Wfield(const double xin, const double yin,
6812 const double zpos, double& exsum,
6813 double& eysum, double& ezsum,
6814 const std::string& label) const {
6815 //-----------------------------------------------------------------------
6816 // SIGFLS - Sums the weighting field components at (XPOS,YPOS,ZPOS).
6817 // (Last changed on 11/10/06.)
6818 //-----------------------------------------------------------------------
6819
6820 // Initialise the sums.
6821 exsum = eysum = ezsum = 0.;
6822 double ex = 0., ey = 0., ez = 0.;
6823
6824 double xpos = xin, ypos = yin;
6825 if (m_polar) Cartesian2Internal(xin, yin, xpos, ypos);
6826 // Stop here if there are no weighting fields defined.
6827 if (m_readout.empty()) return false;
6828 if (!m_sigset) {
6829 std::cerr << m_className << "::Wfield: No weighting fields available.\n";
6830 return false;
6831 }
6832
6833 // In case (xpos, ypos) is located behind a plane there is no field.
6834 if (m_tube) {
6835 if (!InTube(xpos, ypos, m_cotube, m_ntube)) return false;
6836 } else {
6837 if (!m_perx) {
6838 if ((m_ynplan[0] && xpos < m_coplan[0]) ||
6839 (m_ynplan[1] && xpos > m_coplan[1])) {
6840 return false;
6841 }
6842 }
6843 if (!m_pery) {
6844 if ((m_ynplan[2] && ypos < m_coplan[2]) ||
6845 (m_ynplan[3] && ypos > m_coplan[3])) {
6846 return false;
6847 }
6848 }
6849 }
6850
6851 if (label.empty()) return false;
6852 const auto it = std::find(m_readout.cbegin(), m_readout.cend(), label);
6853 if (it == m_readout.end()) return false;
6854 const auto isw = it - m_readout.begin();
6855
6856 // Loop over the signal layers.
6857 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
6858 for (int my = m_mymin; my <= m_mymax; ++my) {
6859 // Load the layers of the wire matrices.
6860 // CALL IONIO(MX,MY,2,0,IFAIL)
6861 // if (!LoadWireLayers(mx, my)) {
6862 // std::cerr << m_className << "::LoadWireLayers:\n";
6863 // std::cerr << " Wire matrix store error.\n";
6864 // std::cerr << " No weighting field returned.\n";
6865 // exsum = eysum = ezsum = 0.;
6866 // return false;
6867 //}
6868 // Loop over all wires.
6869 for (size_t iw = 0; iw < m_nWires; ++iw) {
6870 // Pick out those wires that are part of this read out group.
6871 if (m_w[iw].ind != isw) continue;
6872 ex = ey = ez = 0.;
6873 if (m_cellTypeFourier == A00) {
6874 WfieldWireA00(xpos, ypos, ex, ey, mx, my, iw);
6875 } else if (m_cellTypeFourier == B2X) {
6876 WfieldWireB2X(xpos, ypos, ex, ey, my, iw);
6877 } else if (m_cellTypeFourier == B2Y) {
6878 WfieldWireB2Y(xpos, ypos, ex, ey, mx, iw);
6879 } else if (m_cellTypeFourier == C2X) {
6880 WfieldWireC2X(xpos, ypos, ex, ey, iw);
6881 } else if (m_cellTypeFourier == C2Y) {
6882 WfieldWireC2Y(xpos, ypos, ex, ey, iw);
6883 } else if (m_cellTypeFourier == C30) {
6884 WfieldWireC30(xpos, ypos, ex, ey, iw);
6885 } else if (m_cellTypeFourier == D10) {
6886 WfieldWireD10(xpos, ypos, ex, ey, iw);
6887 } else if (m_cellTypeFourier == D30) {
6888 WfieldWireD30(xpos, ypos, ex, ey, iw);
6889 } else {
6890 std::cerr << m_className << "::Wfield:\n";
6891 std::cerr << " Unknown signal field type " << m_cellTypeFourier
6892 << " received. Program error!\n";
6893 std::cerr << " Encountered for wire " << iw
6894 << ", readout group = " << m_w[iw].ind << "\n";
6895 exsum = eysum = ezsum = 0.;
6896 return false;
6897 }
6898 exsum += ex;
6899 eysum += ey;
6900 ezsum += ez;
6901 }
6902 // Load the layers of the plane matrices.
6903 // CALL IPLIO(MX,MY,2,IFAIL)
6904 // if (!LoadPlaneLayers(mx, my)) {
6905 // std::cerr << m_className << "::Wfield:\n";
6906 // std::cerr << " Plane matrix store error.\n";
6907 // std::cerr << " No weighting field returned.\n";
6908 // exsum = eysum = ezsum = 0.;
6909 // return;
6910 //}
6911 // Loop over all planes.
6912 for (int ip = 0; ip < 5; ++ip) {
6913 // Pick out those that are part of this read out group.
6914 if (m_planes[ip].ind != isw) continue;
6915 ex = ey = ez = 0.;
6916 if (m_cellTypeFourier == A00) {
6917 WfieldPlaneA00(xpos, ypos, ex, ey, mx, my, ip);
6918 } else if (m_cellTypeFourier == B2X) {
6919 WfieldPlaneB2X(xpos, ypos, ex, ey, my, ip);
6920 } else if (m_cellTypeFourier == B2Y) {
6921 WfieldPlaneB2Y(xpos, ypos, ex, ey, mx, ip);
6922 } else if (m_cellTypeFourier == C2X) {
6923 WfieldPlaneC2X(xpos, ypos, ex, ey, ip);
6924 } else if (m_cellTypeFourier == C2Y) {
6925 WfieldPlaneC2Y(xpos, ypos, ex, ey, ip);
6926 } else if (m_cellTypeFourier == C30) {
6927 WfieldPlaneC30(xpos, ypos, ex, ey, ip);
6928 } else if (m_cellTypeFourier == D10) {
6929 WfieldPlaneD10(xpos, ypos, ex, ey, ip);
6930 } else if (m_cellTypeFourier == D30) {
6931 WfieldPlaneD30(xpos, ypos, ex, ey, ip);
6932 } else {
6933 std::cerr << m_className << "::Wfield:\n";
6934 std::cerr << " Unkown field type " << m_cellTypeFourier
6935 << " received. Program error!\n";
6936 std::cerr << " Encountered for plane " << ip
6937 << ", readout group = " << m_planes[ip].ind << "\n";
6938 exsum = eysum = ezsum = 0.;
6939 return false;
6940 }
6941 exsum += ex;
6942 eysum += ey;
6943 ezsum += ez;
6944 }
6945 // Next signal layer.
6946 }
6947 }
6948 // Add the field due to the planes themselves.
6949 for (int ip = 0; ip < 5; ++ip) {
6950 if (m_planes[ip].ind != isw) continue;
6951 exsum += m_planes[ip].ewxcor;
6952 eysum += m_planes[ip].ewycor;
6953 }
6954
6955 // Add strips and pixels, if there are any.
6956 for (unsigned int ip = 0; ip < 5; ++ip) {
6957 for (const auto& strip : m_planes[ip].strips1) {
6958 if (strip.ind != isw) continue;
6959 WfieldStripXy(xpos, ypos, zpos, ex, ey, ez, ip, strip);
6960 exsum += ex;
6961 eysum += ey;
6962 ezsum += ez;
6963 }
6964 for (const auto& strip : m_planes[ip].strips2) {
6965 if (strip.ind != isw) continue;
6966 WfieldStripZ(xpos, ypos, ex, ey, ip, strip);
6967 exsum += ex;
6968 eysum += ey;
6969 }
6970 for (const auto& pixel : m_planes[ip].pixels) {
6971 if (pixel.ind != isw) continue;
6972 WfieldPixel(xpos, ypos, zpos, ex, ey, ez, ip, pixel);
6973 exsum += ex;
6974 eysum += ey;
6975 ezsum += ez;
6976 }
6977 }
6978 if (m_polar) {
6979 const double r = exp(xpos);
6980 const double er = exsum / r;
6981 const double ep = eysum / r;
6982 const double theta = atan2(yin, xin);
6983 const double ct = cos(theta);
6984 const double st = sin(theta);
6985 exsum = +ct * er - st * ep;
6986 eysum = +st * er + ct * ep;
6987 }
6988 return true;
6989}
6990
6991double ComponentAnalyticField::Wpot(const double xin, const double yin,
6992 const double zpos,
6993 const std::string& label) const {
6994
6995 double vsum = 0.;
6996 double xpos = xin, ypos = yin;
6997 if (m_polar) Cartesian2Internal(xin, yin, xpos, ypos);
6998 // Stop here if there are no weighting fields defined.
6999 if (m_readout.empty()) return 0.;
7000 if (!m_sigset) {
7001 std::cerr << m_className << "::Wpot: No weighting potentials available.\n";
7002 return 0.;
7003 }
7004
7005 // In case (xpos, ypos) is located behind a plane there is no field.
7006 if (m_tube) {
7007 if (!InTube(xpos, ypos, m_cotube, m_ntube)) return 0.;
7008 } else {
7009 if (!m_perx) {
7010 if ((m_ynplan[0] && xpos < m_coplan[0]) ||
7011 (m_ynplan[1] && xpos > m_coplan[1])) {
7012 return 0.;
7013 }
7014 }
7015 if (!m_pery) {
7016 if ((m_ynplan[2] && ypos < m_coplan[2]) ||
7017 (m_ynplan[3] && ypos > m_coplan[3])) {
7018 return 0.;
7019 }
7020 }
7021 }
7022 if (label.empty()) return 0.;
7023 const auto it = std::find(m_readout.cbegin(), m_readout.cend(), label);
7024 if (it == m_readout.end()) return 0.;
7025 const auto isw = it - m_readout.begin();
7026
7027 // Loop over the signal layers.
7028 for (int mx = m_mxmin; mx <= m_mxmax; ++mx) {
7029 for (int my = m_mymin; my <= m_mymax; ++my) {
7030 // Loop over all wires.
7031 for (int iw = m_nWires; iw--;) {
7032 // Pick out those wires that are part of this read out group.
7033 if (m_w[iw].ind != isw) continue;
7034 if (m_cellTypeFourier == A00) {
7035 vsum += WpotWireA00(xpos, ypos, mx, my, iw);
7036 } else if (m_cellTypeFourier == B2X) {
7037 vsum += WpotWireB2X(xpos, ypos, my, iw);
7038 } else if (m_cellTypeFourier == B2Y) {
7039 vsum += WpotWireB2Y(xpos, ypos, mx, iw);
7040 } else if (m_cellTypeFourier == C2X) {
7041 vsum += WpotWireC2X(xpos, ypos, iw);
7042 } else if (m_cellTypeFourier == C2Y) {
7043 vsum += WpotWireC2Y(xpos, ypos, iw);
7044 } else if (m_cellTypeFourier == C30) {
7045 vsum += WpotWireC30(xpos, ypos, iw);
7046 } else if (m_cellTypeFourier == D10) {
7047 vsum += WpotWireD10(xpos, ypos, iw);
7048 } else if (m_cellTypeFourier == D30) {
7049 vsum += WpotWireD30(xpos, ypos, iw);
7050 } else {
7051 std::cerr << m_className << "::Wpot:\n";
7052 std::cerr << " Unknown signal field type " << m_cellTypeFourier
7053 << " received. Program error!\n";
7054 std::cerr << " Encountered for wire " << iw
7055 << ", readout group = " << m_w[iw].ind << "\n";
7056 return 0.;
7057 }
7058 }
7059 // Loop over all planes.
7060 for (int ip = 0; ip < 5; ++ip) {
7061 // Pick out those that are part of this read out group.
7062 if (m_planes[ip].ind != isw) continue;
7063 if (m_cellTypeFourier == A00) {
7064 vsum += WpotPlaneA00(xpos, ypos, mx, my, ip);
7065 } else if (m_cellTypeFourier == B2X) {
7066 vsum += WpotPlaneB2X(xpos, ypos, my, ip);
7067 } else if (m_cellTypeFourier == B2Y) {
7068 vsum += WpotPlaneB2Y(xpos, ypos, mx, ip);
7069 } else if (m_cellTypeFourier == C2X) {
7070 vsum += WpotPlaneC2X(xpos, ypos, ip);
7071 } else if (m_cellTypeFourier == C2Y) {
7072 vsum += WpotPlaneC2Y(xpos, ypos, ip);
7073 } else if (m_cellTypeFourier == C30) {
7074 vsum += WpotPlaneC30(xpos, ypos, ip);
7075 } else if (m_cellTypeFourier == D10) {
7076 vsum += WpotPlaneD10(xpos, ypos, ip);
7077 } else if (m_cellTypeFourier == D30) {
7078 vsum += WpotPlaneD30(xpos, ypos, ip);
7079 } else {
7080 std::cerr << m_className << "::Wpot:\n";
7081 std::cerr << " Unkown field type " << m_cellTypeFourier
7082 << " received. Program error!\n";
7083 std::cerr << " Encountered for plane " << ip
7084 << ", readout group = " << m_planes[ip].ind << "\n";
7085 return 0.;
7086 }
7087 }
7088 // Next signal layer.
7089 }
7090 }
7091 // Add the field due to the planes themselves.
7092 for (int ip = 0; ip < 5; ++ip) {
7093 if (m_planes[ip].ind != isw) continue;
7094 if (ip == 0 || ip == 1) {
7095 double xx = xpos;
7096 if (m_perx) {
7097 xx -= m_sx * int(round(xpos / m_sx));
7098 if (m_ynplan[0] && xx <= m_coplan[0]) xx += m_sx;
7099 if (m_ynplan[1] && xx >= m_coplan[1]) xx -= m_sx;
7100 }
7101 vsum += 1. - m_planes[ip].ewxcor * (xx - m_coplan[ip]);
7102 } else if (ip == 2 || ip == 3) {
7103 double yy = ypos;
7104 if (m_pery) {
7105 yy -= m_sy * int(round(ypos / m_sy));
7106 if (m_ynplan[2] && yy <= m_coplan[2]) yy += m_sy;
7107 if (m_ynplan[3] && yy >= m_coplan[3]) yy -= m_sy;
7108 }
7109 vsum += 1. - m_planes[ip].ewycor * (yy - m_coplan[ip]);
7110 }
7111 }
7112
7113 // Add strips and pixels, if there are any.
7114 for (unsigned int ip = 0; ip < 5; ++ip) {
7115 for (const auto& strip : m_planes[ip].strips1) {
7116 if (strip.ind != isw) continue;
7117 vsum += WpotStripXy(xpos, ypos, zpos, ip, strip);
7118 }
7119 for (const auto& strip : m_planes[ip].strips2) {
7120 if (strip.ind != isw) continue;
7121 vsum += WpotStripZ(xpos, ypos, ip, strip);
7122 }
7123 for (const auto& pixel : m_planes[ip].pixels) {
7124 if (pixel.ind != isw) continue;
7125 vsum += WpotPixel(xpos, ypos, zpos, ip, pixel);
7126 }
7127 }
7128 return vsum;
7129}
7130
7131void ComponentAnalyticField::WfieldWireA00(const double xpos, const double ypos,
7132 double& ex, double& ey,
7133 const int mx, const int my,
7134 const int isw) const {
7135 //-----------------------------------------------------------------------
7136 // IONA00 - Routine returning the A I,J [MX,MY] * E terms for A cells.
7137 // VARIABLES : R2 : Potential before taking -Log(Sqrt(...))
7138 // EX,EY : x,y-Component of the electric field.
7139 // ETOT : Magnitude of the electric field.
7140 // VOLT : Potential.
7141 // EXHELP ETC : One term in the summing series.
7142 // (XPOS,YPOS): Position where the field is needed.
7143 // (Last changed on 14/ 8/98.)
7144 //-----------------------------------------------------------------------
7145
7146 ex = ey = 0.;
7147 // Loop over all wires.
7148 for (unsigned int i = 0; i < m_nWires; ++i) {
7149 // Define a few reduced variables.
7150 const double xx = xpos - m_w[i].x - mx * m_sx;
7151 const double yy = ypos - m_w[i].y - my * m_sy;
7152 // Calculate the field in case there are no planes.
7153 const double r2 = xx * xx + yy * yy;
7154 if (r2 <= 0.) continue;
7155 const double s2 = 1. / r2;
7156 double exhelp = xx * s2;
7157 double eyhelp = yy * s2;
7158 // Take care of a plane at constant x.
7159 const double xxmirr = m_ynplax ? xpos + m_w[i].x - 2. * m_coplax : 0.;
7160 if (m_ynplax) {
7161 const double r2plan = xxmirr * xxmirr + yy * yy;
7162 if (r2plan <= 0.) continue;
7163 const double s2plan = 1. / r2plan;
7164 exhelp -= xxmirr * s2plan;
7165 eyhelp -= yy * s2plan;
7166 }
7167 // Take care of a plane at constant y.
7168 const double yymirr = m_ynplay ? ypos + m_w[i].y - 2. * m_coplay : 0.;
7169 if (m_ynplay) {
7170 const double r2plan = xx * xx + yymirr * yymirr;
7171 if (r2plan <= 0.) continue;
7172 const double s2plan = 1. / r2plan;
7173 exhelp -= xx * s2plan;
7174 eyhelp -= yymirr * s2plan;
7175 }
7176 // Take care of pairs of planes.
7177 if (m_ynplax && m_ynplay) {
7178 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
7179 if (r2plan <= 0.) continue;
7180 const double s2plan = 1. / r2plan;
7181 exhelp += xxmirr * s2plan;
7182 eyhelp += yymirr * s2plan;
7183 }
7184 // Calculate the electric field.
7185 const double qw = real(m_sigmat[isw][i]);
7186 ex += qw * exhelp;
7187 ey += qw * eyhelp;
7188 }
7189}
7190
7191double ComponentAnalyticField::WpotWireA00(const double xpos, const double ypos,
7192 const int mx, const int my,
7193 const int isw) const {
7194
7195 double volt = 0.;
7196 // Loop over all wires.
7197 for (unsigned int i = 0; i < m_nWires; ++i) {
7198 // Define a few reduced variables.
7199 const double xx = xpos - m_w[i].x - mx * m_sx;
7200 const double yy = ypos - m_w[i].y - my * m_sy;
7201 // Calculate the field in case there are no planes.
7202 double r2 = xx * xx + yy * yy;
7203 if (r2 <= 0.) continue;
7204 // Take care of a plane at constant x.
7205 const double xxmirr = m_ynplax ? xpos + m_w[i].x - 2. * m_coplax : 0.;
7206 if (m_ynplax) {
7207 const double r2plan = xxmirr * xxmirr + yy * yy;
7208 if (r2plan <= 0.) continue;
7209 r2 /= r2plan;
7210 }
7211 // Take care of a plane at constant y.
7212 const double yymirr = m_ynplay ? ypos + m_w[i].y - 2. * m_coplay : 0.;
7213 if (m_ynplay) {
7214 const double r2plan = xx * xx + yymirr * yymirr;
7215 if (r2plan <= 0.) continue;
7216 r2 /= r2plan;
7217 }
7218 // Take care of pairs of planes.
7219 if (m_ynplax && m_ynplay) {
7220 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
7221 if (r2plan <= 0.) continue;
7222 r2 *= r2plan;
7223 }
7224 // Calculate the electric field and the potential.
7225 volt -= real(m_sigmat[isw][i]) * log(r2);
7226 }
7227 return 0.5 * volt;
7228}
7229
7230void ComponentAnalyticField::WfieldWireB2X(const double xpos, const double ypos,
7231 double& ex, double& ey,
7232 const int my, const int isw) const {
7233 //-----------------------------------------------------------------------
7234 // IONB2X - Routine calculating the MY contribution to the signal on
7235 // wire ISW due to a charge at (XPOS,YPOS) for F-B2Y cells.
7236 // VARIABLES : See routine EFCA00 for most of the variables.
7237 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
7238 // ECOMPL : EX + I*EY ; I**2=-1
7239 // (Last changed on 20/ 2/90.)
7240 //-----------------------------------------------------------------------
7241
7242 ex = ey = 0.;
7243 const double tx = HalfPi / m_sx;
7244 // Loop over all wires.
7245 for (unsigned int i = 0; i < m_nWires; ++i) {
7246 const double xx = tx * (xpos - m_w[i].x);
7247 const double yy = tx * (ypos - m_w[i].y - my * m_sy);
7248 const double xxneg = tx * (xpos + m_w[i].x - 2 * m_coplan[0]);
7249 // Calculate the field in case there are no equipotential planes.
7250 std::complex<double> ecompl(0., 0.);
7251 if (fabs(yy) <= 20.) {
7252 const std::complex<double> zz(xx, yy);
7253 const std::complex<double> zzneg(xxneg, yy);
7254 ecompl = -m_b2sin[i] / (sin(zz) * sin(zzneg));
7255 }
7256 // Take care of a plane at constant y.
7257 if (m_ynplay) {
7258 const double yymirr = tx * (ypos + m_w[i].y - 2. * m_coplay);
7259 if (fabs(yymirr) <= 20.) {
7260 const std::complex<double> zzmirr(xx, yymirr);
7261 const std::complex<double> zznmirr(xxneg, yymirr);
7262 ecompl += m_b2sin[i] / (sin(zzmirr) * sin(zznmirr));
7263 }
7264 }
7265 // Calculate the electric field.
7266 const double qw = real(m_sigmat[isw][i]);
7267 ex += qw * real(ecompl);
7268 ey -= qw * imag(ecompl);
7269 }
7270 ex *= tx;
7271 ey *= tx;
7272}
7273
7274double ComponentAnalyticField::WpotWireB2X(const double xpos, const double ypos,
7275 const int my, const int isw) const {
7276 double volt = 0.;
7277 const double tx = HalfPi / m_sx;
7278 // Loop over all wires.
7279 for (unsigned int i = 0; i < m_nWires; ++i) {
7280 const double xx = tx * (xpos - m_w[i].x);
7281 const double yy = tx * (ypos - m_w[i].y - my * m_sy);
7282 const double xxneg = tx * (xpos + m_w[i].x - 2 * m_coplan[0]);
7283 // Calculate the potential in case there are no equipotential planes.
7284 double r2 = 1.;
7285 if (fabs(yy) <= 20.) {
7286 const double sinhy = sinh(yy);
7287 const double sinxx = sin(xx);
7288 const double sinxxneg = sin(xxneg);
7289 r2 = (sinhy * sinhy + sinxx * sinxx) /
7290 (sinhy * sinhy + sinxxneg * sinxxneg);
7291 }
7292 // Take care of a plane at constant y.
7293 if (m_ynplay) {
7294 const double yymirr = tx * (ypos + m_w[i].y - 2. * m_coplay);
7295 if (fabs(yymirr) <= 20.) {
7296 const double sinhy = sinh(yymirr);
7297 const double sinxx = sin(xx);
7298 const double sinxxneg = sin(xxneg);
7299 const double r2plan = (sinhy * sinhy + sinxx * sinxx) /
7300 (sinhy * sinhy + sinxxneg * sinxxneg);
7301 r2 /= r2plan;
7302 }
7303 }
7304 // Calculate the electric field and potential.
7305 volt -= real(m_sigmat[isw][i]) * log(r2);
7306 }
7307 return 0.5 * volt;
7308}
7309
7310void ComponentAnalyticField::WfieldWireB2Y(const double xpos, const double ypos,
7311 double& ex, double& ey,
7312 const int mx, const int isw) const {
7313 //-----------------------------------------------------------------------
7314 // IONB2Y - Routine calculating the MX contribution to the signal on
7315 // wire ISW due to a charge at (XPOS,YPOS) for F-B2X cells.
7316 // VARIABLES : See routine EFCA00 for most of the variables.
7317 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
7318 // ECOMPL : EX + I*EY ; I**2=-1
7319 // (Last changed on 20/ 2/90.)
7320 //-----------------------------------------------------------------------
7321
7322 constexpr std::complex<double> icons(0., 1.);
7323 ex = ey = 0.;
7324 const double ty = HalfPi / m_sy;
7325 // Loop over all wires.
7326 for (unsigned int i = 0; i < m_nWires; ++i) {
7327 const double xx = ty * (xpos - m_w[i].x - mx * m_sx);
7328 const double yy = ty * (ypos - m_w[i].y);
7329 const double yyneg = ty * (ypos + m_w[i].y - 2. * m_coplan[2]);
7330 // Calculate the field in case there are no equipotential planes.
7331 std::complex<double> ecompl(0., 0.);
7332 if (fabs(xx) <= 20.) {
7333 const std::complex<double> zz(xx, yy);
7334 const std::complex<double> zzneg(xx, yyneg);
7335 ecompl = icons * m_b2sin[i] / (sin(icons * zz) * sin(icons * zzneg));
7336 }
7337 // Take care of a plane at constant x.
7338 if (m_ynplax) {
7339 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
7340 if (fabs(xxmirr) <= 20.) {
7341 const std::complex<double> zzmirr(xxmirr, yy);
7342 const std::complex<double> zznmirr(xxmirr, yyneg);
7343 ecompl -=
7344 icons * m_b2sin[i] / (sin(icons * zzmirr) * sin(icons * zznmirr));
7345 }
7346 }
7347 // Calculate the electric field.
7348 const double qw = real(m_sigmat[isw][i]);
7349 ex += qw * real(ecompl);
7350 ey -= qw * imag(ecompl);
7351 }
7352 ex *= ty;
7353 ey *= ty;
7354}
7355
7356double ComponentAnalyticField::WpotWireB2Y(const double xpos, const double ypos,
7357 const int mx, const int isw) const {
7358 double volt = 0.;
7359 const double ty = HalfPi / m_sy;
7360 // Loop over all wires.
7361 for (unsigned int i = 0; i < m_nWires; ++i) {
7362 const double xx = ty * (xpos - m_w[i].x - mx * m_sx);
7363 const double yy = ty * (ypos - m_w[i].y);
7364 const double yyneg = ty * (ypos + m_w[i].y - 2. * m_coplan[2]);
7365 // Calculate the field in case there are no equipotential planes.
7366 double r2 = 1.;
7367 if (fabs(xx) <= 20.) {
7368 const double sinhx = sinh(xx);
7369 const double sinyy = sin(yy);
7370 const double sinyyneg = sin(yyneg);
7371 r2 = (sinhx * sinhx + sinyy * sinyy) /
7372 (sinhx * sinhx + sinyyneg * sinyyneg);
7373 }
7374 // Take care of a plane at constant x.
7375 if (m_ynplax) {
7376 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
7377 if (fabs(xxmirr) <= 20.) {
7378 const double sinhx = sinh(xxmirr);
7379 const double sinyy = sin(yy);
7380 const double sinyyneg = sin(yyneg);
7381 const double r2plan = (sinhx * sinhx + sinyy * sinyy) /
7382 (sinhx * sinhx + sinyyneg * sinyyneg);
7383 r2 /= r2plan;
7384 }
7385 }
7386 volt -= real(m_sigmat[isw][i]) * log(r2);
7387 }
7388 return 0.5 * volt;
7389}
7390
7391void ComponentAnalyticField::WfieldWireC2X(const double xpos, const double ypos,
7392 double& ex, double& ey,
7393 const int isw) const {
7394 //-----------------------------------------------------------------------
7395 // IONC2X - Routine returning the potential and electric field in a
7396 // configuration with 2 x planes and y periodicity.
7397 // VARIABLES : see the writeup
7398 // (Last changed on 12/10/06.)
7399 //-----------------------------------------------------------------------
7400
7401 constexpr std::complex<double> icons(0., 1.);
7402 // Initial values.
7403 std::complex<double> wsum1 = 0.;
7404 std::complex<double> wsum2 = 0.;
7405 double s = 0.;
7406 // Wire loop.
7407 for (unsigned int i = 0; i < m_nWires; ++i) {
7408 const double qw = real(m_sigmat[isw][i]);
7409 const double xx = xpos - m_w[i].x;
7410 const double yy = ypos - m_w[i].y;
7411 // Compute the direct contribution.
7412 auto zeta = m_zmult * std::complex<double>(xx, yy);
7413 if (imag(zeta) > 15.) {
7414 wsum1 -= qw * icons;
7415 } else if (imag(zeta) < -15.) {
7416 wsum1 += qw * icons;
7417 } else {
7418 const auto zterm = Th1(zeta, m_p1, m_p2);
7419 wsum1 += qw * (zterm.second / zterm.first);
7420 }
7421 // Find the plane nearest to the wire.
7422 const double cx =
7423 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
7424 // Constant terms sum
7425 s += qw * (m_w[i].x - cx);
7426 // Mirror contribution.
7427 zeta = m_zmult * std::complex<double>(2. * cx - xpos - m_w[i].x, yy);
7428 if (imag(zeta) > +15.) {
7429 wsum2 -= qw * icons;
7430 } else if (imag(zeta) < -15.) {
7431 wsum2 += qw * icons;
7432 } else {
7433 const auto zterm = Th1(zeta, m_p1, m_p2);
7434 wsum2 += qw * (zterm.second / zterm.first);
7435 }
7436 }
7437 // Convert the two contributions to a real field.
7438 ex = real(m_zmult * (wsum1 + wsum2));
7439 ey = -imag(m_zmult * (wsum1 - wsum2));
7440 // Constant correction terms.
7441 if (m_mode == 0) ex += s * TwoPi / (m_sx * m_sy);
7442}
7443
7444double ComponentAnalyticField::WpotWireC2X(const double xpos, const double ypos,
7445 const int isw) const {
7446 double volt = 0.;
7447 // Wire loop.
7448 for (unsigned int i = 0; i < m_nWires; ++i) {
7449 const double xx = xpos - m_w[i].x;
7450 const double yy = ypos - m_w[i].y;
7451 const double qw = real(m_sigmat[isw][i]);
7452 // Compute the direct contribution.
7453 auto zeta = m_zmult * std::complex<double>(xx, yy);
7454 if (fabs(imag(zeta)) > 15.) {
7455 volt -= qw * (fabs(imag(zeta)) - CLog2);
7456 } else {
7457 const auto zterm = Th1(zeta, m_p1, m_p2);
7458 volt -= qw * log(abs(zterm.first));
7459 }
7460 // Find the plane nearest to the wire.
7461 const double cx =
7462 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
7463 // Mirror contribution.
7464 zeta = m_zmult * std::complex<double>(2. * cx - xpos - m_w[i].x, yy);
7465 if (fabs(imag(zeta)) > 15.) {
7466 volt += qw * (fabs(imag(zeta)) - CLog2);
7467 } else {
7468 const auto zterm = Th1(zeta, m_p1, m_p2);
7469 volt += qw * log(abs(zterm.first));
7470 }
7471 // Correct the voltage, if needed (MODE).
7472 if (m_mode == 0) {
7473 volt -= TwoPi * qw * (xpos - cx) * (m_w[i].x - cx) / (m_sx * m_sy);
7474 }
7475 }
7476 return volt;
7477}
7478
7479void ComponentAnalyticField::WfieldWireC2Y(const double xpos, const double ypos,
7480 double& ex, double& ey,
7481 const int isw) const {
7482 //-----------------------------------------------------------------------
7483 // IONC2Y - Routine returning the potential and electric field in a
7484 // configuration with 2 y planes and x periodicity.
7485 // VARIABLES : see the writeup
7486 // (Last changed on 12/10/06.)
7487 //-----------------------------------------------------------------------
7488
7489 constexpr std::complex<double> icons(0., 1.);
7490 // Initial values.
7491 std::complex<double> wsum1 = 0.;
7492 std::complex<double> wsum2 = 0.;
7493 double s = 0.;
7494 // Wire loop.
7495 for (unsigned int i = 0; i < m_nWires; ++i) {
7496 const double qw = real(m_sigmat[isw][i]);
7497 const double xx = xpos - m_w[i].x;
7498 const double yy = ypos - m_w[i].y;
7499 // Compute the direct contribution.
7500 auto zeta = m_zmult * std::complex<double>(xx, yy);
7501 if (imag(zeta) > +15.) {
7502 wsum1 -= qw * icons;
7503 } else if (imag(zeta) < -15.) {
7504 wsum1 += qw * icons;
7505 } else {
7506 const auto zterm = Th1(zeta, m_p1, m_p2);
7507 wsum1 += qw * (zterm.second / zterm.first);
7508 }
7509 // Find the plane nearest to the wire.
7510 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
7511 // Constant terms sum
7512 s += qw * (m_w[i].y - cy);
7513 // Mirror contribution.
7514 zeta = m_zmult * std::complex<double>(xx, 2. * cy - ypos - m_w[i].y);
7515 if (imag(zeta) > +15.) {
7516 wsum2 -= qw * icons;
7517 } else if (imag(zeta) < -15.) {
7518 wsum2 += qw * icons;
7519 } else {
7520 const auto zterm = Th1(zeta, m_p1, m_p2);
7521 wsum2 += qw * (zterm.second / zterm.first);
7522 }
7523 }
7524 // Convert the two contributions to a real field.
7525 ex = real(m_zmult * (wsum1 - wsum2));
7526 ey = -imag(m_zmult * (wsum1 + wsum2));
7527 // Constant correction terms.
7528 if (m_mode == 1) ey += s * TwoPi / (m_sx * m_sy);
7529}
7530
7531double ComponentAnalyticField::WpotWireC2Y(const double xpos, const double ypos,
7532 const int isw) const {
7533
7534 double volt = 0.;
7535 // Wire loop.
7536 for (unsigned int i = 0; i < m_nWires; ++i) {
7537 const double qw = real(m_sigmat[isw][i]);
7538 const double xx = xpos - m_w[i].x;
7539 const double yy = ypos - m_w[i].y;
7540 // Compute the direct contribution.
7541 auto zeta = m_zmult * std::complex<double>(xx, yy);
7542 if (fabs(imag(zeta)) > 15.) {
7543 volt -= qw * (fabs(imag(zeta)) - CLog2);
7544 } else {
7545 const auto zterm = Th1(zeta, m_p1, m_p2);
7546 volt -= qw * log(abs(zterm.first));
7547 }
7548 // Find the plane nearest to the wire.
7549 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
7550 // Mirror contribution.
7551 zeta = m_zmult * std::complex<double>(xx, 2. * cy - ypos - m_w[i].y);
7552 if (fabs(imag(zeta)) > 15.) {
7553 volt += qw * (fabs(imag(zeta)) - CLog2);
7554 } else {
7555 const auto zterm = Th1(zeta, m_p1, m_p2);
7556 volt += qw * log(abs(zterm.first));
7557 }
7558 // Correct the voltage, if needed (MODE).
7559 if (m_mode == 1) {
7560 volt -= TwoPi * qw * (ypos - cy) * (m_w[i].y - cy) / (m_sx * m_sy);
7561 }
7562 }
7563 return volt;
7564}
7565
7566void ComponentAnalyticField::WfieldWireC30(const double xpos, const double ypos,
7567 double& ex, double& ey,
7568 const int isw) const {
7569 //-----------------------------------------------------------------------
7570 // IONC30 - Routine returning the weighting field field in a
7571 // configuration with 2 y and 2 x planes. This routine is
7572 // basically the same as EFCC30.
7573 // (Last changed on 11/11/97.)
7574 //-----------------------------------------------------------------------
7575
7576 constexpr std::complex<double> icons(0., 1.);
7577 // Initial values.
7578 std::complex<double> wsum1 = 0.;
7579 std::complex<double> wsum2 = 0.;
7580 std::complex<double> wsum3 = 0.;
7581 std::complex<double> wsum4 = 0.;
7582 // Wire loop.
7583 for (unsigned int i = 0; i < m_nWires; ++i) {
7584 const double qw = real(m_sigmat[isw][i]);
7585 const double xx = xpos - m_w[i].x;
7586 const double yy = ypos - m_w[i].y;
7587 // Compute the direct contribution.
7588 auto zeta = m_zmult * std::complex<double>(xx, yy);
7589 if (imag(zeta) > +15.) {
7590 wsum1 -= qw * icons;
7591 } else if (imag(zeta) < -15.) {
7592 wsum1 += qw * icons;
7593 } else {
7594 const auto zterm = Th1(zeta, m_p1, m_p2);
7595 wsum1 += qw * (zterm.second / zterm.first);
7596 }
7597 // Mirror contribution from the x plane.
7598 const double xxmirr = MirrorCoordinate(xpos, m_coplax, m_w[i].x, m_sx);
7599 zeta = m_zmult * std::complex<double>(xxmirr, yy);
7600 if (imag(zeta) > +15.) {
7601 wsum2 -= qw * icons;
7602 } else if (imag(zeta) < -15.) {
7603 wsum2 += qw * icons;
7604 } else {
7605 const auto zterm = Th1(zeta, m_p1, m_p2);
7606 wsum2 += qw * (zterm.second / zterm.first);
7607 }
7608 // Mirror contribution from the y plane.
7609 const double yymirr = MirrorCoordinate(ypos, m_coplay, m_w[i].y, m_sy);
7610 zeta = m_zmult * std::complex<double>(xx, yymirr);
7611 if (imag(zeta) > +15.) {
7612 wsum3 -= qw * icons;
7613 } else if (imag(zeta) < -15.) {
7614 wsum3 += qw * icons;
7615 } else {
7616 const auto zterm = Th1(zeta, m_p1, m_p2);
7617 wsum3 += qw * (zterm.second / zterm.first);
7618 }
7619 // Mirror contribution from both the x and the y plane.
7620 zeta = m_zmult * std::complex<double>(xxmirr, yymirr);
7621 if (imag(zeta) > +15.) {
7622 wsum4 -= qw * icons;
7623 } else if (imag(zeta) < -15.) {
7624 wsum4 += qw * icons;
7625 } else {
7626 const auto zterm = Th1(zeta, m_p1, m_p2);
7627 wsum4 += qw * (zterm.second / zterm.first);
7628 }
7629 }
7630 // Convert the two contributions to a real field.
7631 ex = real(m_zmult * (wsum1 + wsum2 - wsum3 - wsum4));
7632 ey = -imag(m_zmult * (wsum1 - wsum2 + wsum3 - wsum4));
7633}
7634
7635double ComponentAnalyticField::WpotWireC30(const double xpos, const double ypos,
7636 const int isw) const {
7637 double volt = 0.;
7638 // Wire loop.
7639 for (unsigned int i = 0; i < m_nWires; ++i) {
7640 const double qw = real(m_sigmat[isw][i]);
7641 const double xx = xpos - m_w[i].x;
7642 const double yy = ypos - m_w[i].y;
7643 // Compute the direct contribution.
7644 auto zeta = m_zmult * std::complex<double>(xx, yy);
7645 if (fabs(imag(zeta)) > 15.) {
7646 volt -= qw * (fabs(imag(zeta)) - CLog2);
7647 } else {
7648 const auto zterm = Th1(zeta, m_p1, m_p2);
7649 volt -= qw * log(abs(zterm.first));
7650 }
7651 // Mirror contribution from the x plane.
7652 const double xxmirr = MirrorCoordinate(xpos, m_coplax, m_w[i].x, m_sx);
7653 zeta = m_zmult * std::complex<double>(xxmirr, yy);
7654 if (fabs(imag(zeta)) > 15.) {
7655 volt += qw * (fabs(imag(zeta)) - CLog2);
7656 } else {
7657 const auto zterm = Th1(zeta, m_p1, m_p2);
7658 volt += qw * log(abs(zterm.first));
7659 }
7660 // Mirror contribution from the y plane.
7661 const double yymirr = MirrorCoordinate(ypos, m_coplay, m_w[i].y, m_sy);
7662 zeta = m_zmult * std::complex<double>(xx, yymirr);
7663 if (fabs(imag(zeta)) > 15.) {
7664 volt += qw * (fabs(imag(zeta)) - CLog2);
7665 } else {
7666 const auto zterm = Th1(zeta, m_p1, m_p2);
7667 volt += qw * log(abs(zterm.first));
7668 }
7669 // Mirror contribution from both the x and the y plane.
7670 zeta = m_zmult * std::complex<double>(xxmirr, yymirr);
7671 if (fabs(imag(zeta)) > 15.) {
7672 volt -= qw * (fabs(imag(zeta)) - CLog2);
7673 } else {
7674 const auto zterm = Th1(zeta, m_p1, m_p2);
7675 volt -= qw * log(abs(zterm.first));
7676 }
7677 }
7678 return volt;
7679}
7680
7681void ComponentAnalyticField::WfieldWireD10(const double xpos, const double ypos,
7682 double& ex, double& ey,
7683 const int isw) const {
7684 //-----------------------------------------------------------------------
7685 // IOND10 - Subroutine computing the signal on wire ISW due to a charge
7686 // at (XPOS,YPOS). This is effectively routine EFCD10.
7687 // VARIABLES : EX, EY, VOLT:Electric field and potential.
7688 // ETOT, VOLT : Magnitude of electric field, potential.
7689 // (XPOS,YPOS): The position where the field is calculated.
7690 // ZI, ZPOS : Shorthand complex notations.
7691 // (Last changed on 2/ 2/93.)
7692 //-----------------------------------------------------------------------
7693
7694 ex = ey = 0.;
7695 // Set the complex position coordinates.
7696 const std::complex<double> zpos(xpos, ypos);
7697 // Loop over all wires.
7698 for (unsigned int i = 0; i < m_nWires; ++i) {
7699 // Set the complex version of the wire-coordinate for simplicity.
7700 const std::complex<double> zi(m_w[i].x, m_w[i].y);
7701 // Compute the contribution to the electric field.
7702 const auto wi = 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
7703 const double qw = real(m_sigmat[isw][i]);
7704 ex += qw * real(wi);
7705 ey += qw * imag(wi);
7706 }
7707}
7708
7709double ComponentAnalyticField::WpotWireD10(const double xpos, const double ypos,
7710 const int isw) const {
7711 double volt = 0.;
7712 // Set the complex position coordinates.
7713 const std::complex<double> zpos(xpos, ypos);
7714 // Loop over all wires.
7715 for (unsigned int i = 0; i < m_nWires; ++i) {
7716 // Set the complex version of the wire-coordinate for simplicity.
7717 const std::complex<double> zi(m_w[i].x, m_w[i].y);
7718 volt -= real(m_sigmat[isw][i]) *
7719 log(abs(m_cotube * (zpos - zi) / (m_cotube2 - zpos * conj(zi))));
7720 }
7721 return volt;
7722}
7723
7724void ComponentAnalyticField::WfieldWireD30(const double xpos, const double ypos,
7725 double& ex, double& ey,
7726 const int isw) const {
7727 //-----------------------------------------------------------------------
7728 // IOND30 - Subroutine computing the weighting field for a polygonal
7729 // cells without periodicities, type D3.
7730 // VARIABLES : EX, EY :Electric field
7731 // (XPOS,YPOS): The position where the field is calculated.
7732 // ZI, ZPOS : Shorthand complex notations.
7733 // (Last changed on 19/ 6/97.)
7734 //-----------------------------------------------------------------------
7735
7736 ex = ey = 0.;
7737 // Get the mapping of the position.
7738 std::complex<double> wpos, wdpos;
7739 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
7740 // Loop over all wires.
7741 for (unsigned int i = 0; i < m_nWires; ++i) {
7742 // Compute the contribution to the electric field.
7743 const auto whelp = wdpos * (1. - pow(abs(wmap[i]), 2)) /
7744 ((wpos - wmap[i]) * (1. - conj(wmap[i]) * wpos));
7745 const double qw = real(m_sigmat[isw][i]);
7746 ex += qw * real(whelp);
7747 ey -= qw * imag(whelp);
7748 }
7749 ex /= m_cotube;
7750 ey /= m_cotube;
7751}
7752
7753double ComponentAnalyticField::WpotWireD30(const double xpos, const double ypos,
7754 const int isw) const {
7755 double volt = 0.;
7756 // Get the mapping of the position.
7757 std::complex<double> wpos, wdpos;
7758 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
7759 // Loop over all wires.
7760 for (unsigned int i = 0; i < m_nWires; ++i) {
7761 // Compute the contribution to the potential.
7762 volt -= real(m_sigmat[isw][i]) *
7763 log(abs((wpos - wmap[i]) / (1. - wpos * conj(wmap[i]))));
7764 }
7765 return volt;
7766}
7767
7768void ComponentAnalyticField::WfieldPlaneA00(
7769 const double xpos, const double ypos, double& ex, double& ey,
7770 const int mx, const int my, const int ip) const {
7771 //-----------------------------------------------------------------------
7772 // IPLA00 - Routine returning the A I,J [MX,MY] * E terms for A cells.
7773 // VARIABLES : R2 : Potential before taking -Log(Sqrt(...))
7774 // EX,EY : x,y-Component of the electric field.
7775 // EXHELP ETC : One term in the summing series.
7776 // (XPOS,YPOS): Position where the field is needed.
7777 // (Last changed on 9/11/98.)
7778 //-----------------------------------------------------------------------
7779
7780 ex = ey = 0.;
7781 // Loop over all wires.
7782 for (unsigned int i = 0; i < m_nWires; ++i) {
7783 // Define a few reduced variables.
7784 const double xx = xpos - m_w[i].x - mx * m_sx;
7785 const double yy = ypos - m_w[i].y - my * m_sy;
7786 // Calculate the field in case there are no planes.
7787 const double r2 = xx * xx + yy * yy;
7788 if (r2 <= 0.) continue;
7789 const double s2 = 1. / r2;
7790 double exhelp = xx * s2;
7791 double eyhelp = yy * s2;
7792 // Take care of a plane at constant x.
7793 const double xxmirr = m_ynplax ? xpos + m_w[i].x - 2 * m_coplax : 0.;
7794 if (m_ynplax) {
7795 const double r2plan = xxmirr * xxmirr + yy * yy;
7796 if (r2plan <= 0.) continue;
7797 const double s2plan = 1. / r2plan;
7798 exhelp -= xxmirr * s2plan;
7799 eyhelp -= yy * s2plan;
7800 }
7801 // Take care of a plane at constant y.
7802 const double yymirr = m_ynplay ? ypos + m_w[i].y - 2 * m_coplay : 0.;
7803 if (m_ynplay) {
7804 const double r2plan = xx * xx + yymirr * yymirr;
7805 if (r2plan <= 0.) continue;
7806 const double s2plan = 1. / r2plan;
7807 exhelp -= xx * s2plan;
7808 eyhelp -= yymirr * s2plan;
7809 }
7810 // Take care of pairs of planes.
7811 if (m_ynplax && m_ynplay) {
7812 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
7813 if (r2plan <= 0.) continue;
7814 const double s2plan = 1. / r2plan;
7815 exhelp += xxmirr * s2plan;
7816 eyhelp += yymirr * s2plan;
7817 }
7818 ex += m_qplane[ip][i] * exhelp;
7819 ey += m_qplane[ip][i] * eyhelp;
7820 }
7821}
7822
7823double ComponentAnalyticField::WpotPlaneA00(
7824 const double xpos, const double ypos,
7825 const int mx, const int my, const int ip) const {
7826
7827 double volt = 0.;
7828 // Loop over all wires.
7829 for (unsigned int i = 0; i < m_nWires; ++i) {
7830 // Define a few reduced variables.
7831 const double xx = xpos - m_w[i].x - mx * m_sx;
7832 const double yy = ypos - m_w[i].y - my * m_sy;
7833 // Calculate the direct contribution.
7834 double r2 = xx * xx + yy * yy;
7835 if (r2 <= 0.) continue;
7836 // Take care of a plane at constant x.
7837 const double xxmirr = m_ynplax ? xpos + m_w[i].x - 2 * m_coplax : 0.;
7838 if (m_ynplax) {
7839 const double r2plan = xxmirr * xxmirr + yy * yy;
7840 if (r2plan <= 0.) continue;
7841 r2 /= r2plan;
7842 }
7843 // Take care of a plane at constant y.
7844 const double yymirr = m_ynplay ? ypos + m_w[i].y - 2 * m_coplay : 0.;
7845 if (m_ynplay) {
7846 const double r2plan = xx * xx + yymirr * yymirr;
7847 if (r2plan <= 0.) continue;
7848 r2 /= r2plan;
7849 }
7850 // Take care of pairs of planes.
7851 if (m_ynplax && m_ynplay) {
7852 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
7853 if (r2plan <= 0.) continue;
7854 r2 *= r2plan;
7855 }
7856 // Calculate the potential.
7857 volt -= m_qplane[ip][i] * log(r2);
7858 }
7859 return 0.5 * volt;
7860}
7861
7862void ComponentAnalyticField::WfieldPlaneB2X(const double xpos,
7863 const double ypos,
7864 double& ex, double& ey,
7865 const int my,
7866 const int ip) const {
7867 //-----------------------------------------------------------------------
7868 // IPLB2X - Routine calculating the MY contribution to the signal on
7869 // wire IPLANE due to a charge at (XPOS,YPOS) for F-B2Y cells.
7870 // VARIABLES : See routine EFCA00 for most of the variables.
7871 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
7872 // ECOMPL : EX + I*EY ; I**2=-1
7873 // (Last changed on 12/11/98.)
7874 //-----------------------------------------------------------------------
7875
7876 ex = ey = 0.;
7877 const double tx = HalfPi / m_sx;
7878 // Loop over all wires.
7879 for (unsigned int i = 0; i < m_nWires; ++i) {
7880 const double xx = tx * (xpos - m_w[i].x);
7881 const double yy = tx * (ypos - m_w[i].y - my * m_sy);
7882 const double xxneg = tx * (xpos + m_w[i].x - 2 * m_coplan[0]);
7883 // Calculate the field in case there are no equipotential planes.
7884 std::complex<double> ecompl(0., 0.);
7885 if (fabs(yy) <= 20.) {
7886 const std::complex<double> zz(xx, yy);
7887 const std::complex<double> zzneg(xxneg, yy);
7888 ecompl = -m_b2sin[i] / (sin(zz) * sin(zzneg));
7889 }
7890 // Take care of a plane at constant y.
7891 if (m_ynplay) {
7892 const double yymirr = tx * (ypos + m_w[i].y - 2 * m_coplay);
7893 if (fabs(yymirr) <= 20.) {
7894 const std::complex<double> zzmirr(yy, yymirr);
7895 const std::complex<double> zznmirr(xxneg, yymirr);
7896 ecompl += m_b2sin[i] / (sin(zzmirr) * sin(zznmirr));
7897 }
7898 }
7899 // Calculate the electric field.
7900 ex += m_qplane[ip][i] * real(ecompl);
7901 ey -= m_qplane[ip][i] * imag(ecompl);
7902 }
7903 ex *= tx;
7904 ey *= tx;
7905}
7906
7907double ComponentAnalyticField::WpotPlaneB2X(const double xpos,
7908 const double ypos,
7909 const int my, const int ip) const {
7910
7911 double volt = 0.;
7912 const double tx = HalfPi / m_sx;
7913 // Loop over all wires.
7914 for (unsigned int i = 0; i < m_nWires; ++i) {
7915 const double xx = tx * (xpos - m_w[i].x);
7916 const double yy = tx * (ypos - m_w[i].y - my * m_sy);
7917 const double xxneg = tx * (xpos + m_w[i].x - 2 * m_coplan[0]);
7918 // Calculate the direct contribution.
7919 double r2 = 1.;
7920 if (fabs(yy) <= 20.) {
7921 const double sinhy = sinh(yy);
7922 const double sinxx = sin(xx);
7923 const double sinxxneg = sin(xxneg);
7924 r2 = (sinhy * sinhy + sinxx * sinxx) /
7925 (sinhy * sinhy + sinxxneg * sinxxneg);
7926 }
7927 // Take care of a plane at constant y.
7928 if (m_ynplay) {
7929 const double yymirr = tx * (ypos + m_w[i].y - 2 * m_coplay);
7930 if (fabs(yymirr) <= 20.) {
7931 const double sinhy = sinh(yymirr);
7932 const double sinxx = sin(xx);
7933 const double sinxxneg = sin(xxneg);
7934 const double r2plan = (sinhy * sinhy + sinxx * sinxx) /
7935 (sinhy * sinhy + sinxxneg * sinxxneg);
7936 r2 /= r2plan;
7937 }
7938 }
7939 volt -= m_qplane[ip][i] * log(r2);
7940 }
7941 return 0.5 * volt;
7942}
7943
7944void ComponentAnalyticField::WfieldPlaneB2Y(const double xpos,
7945 const double ypos,
7946 double& ex, double& ey,
7947 const int mx,
7948 const int ip) const {
7949 //-----------------------------------------------------------------------
7950 // IPLB2Y - Routine calculating the MX contribution to the signal on
7951 // wire IPLANE due to a charge at (XPOS,YPOS) for F-B2X cells.
7952 // VARIABLES : See routine EFCA00 for most of the variables.
7953 // Z,ZZMIRR : X + I*Y , XXMIRR + I*YYMIRR ; I**2=-1
7954 // ECOMPL : EX + I*EY ; I**2=-1
7955 // (Last changed on 12/11/98.)
7956 //-----------------------------------------------------------------------
7957
7958 constexpr std::complex<double> icons(0., 1.);
7959 ex = ey = 0.;
7960 const double ty = HalfPi / m_sy;
7961 // Loop over all wires.
7962 for (unsigned int i = 0; i < m_nWires; ++i) {
7963 const double xx = ty * (xpos - m_w[i].x - mx * m_sx);
7964 const double yy = ty * (ypos - m_w[i].y);
7965 const double yyneg = ty * (ypos + m_w[i].y - 2 * m_coplan[2]);
7966 // Calculate the field in case there are no equipotential planes.
7967 std::complex<double> ecompl(0., 0.);
7968 if (fabs(xx) <= 20.) {
7969 const std::complex<double> zz(xx, yy);
7970 const std::complex<double> zzneg(xx, yyneg);
7971 ecompl = icons * m_b2sin[i] / (sin(icons * zz) * sin(icons * zzneg));
7972 }
7973 // Take care of a plane at constant y.
7974 if (m_ynplax) {
7975 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
7976 if (fabs(xxmirr) <= 20.) {
7977 const std::complex<double> zzmirr(xxmirr, yy);
7978 const std::complex<double> zznmirr(xxmirr, yyneg);
7979 ecompl -= m_b2sin[i] / (sin(icons * zzmirr) * sin(icons * zznmirr));
7980 }
7981 }
7982 ex += m_qplane[ip][i] * real(ecompl);
7983 ey -= m_qplane[ip][i] * imag(ecompl);
7984 }
7985 ex *= ty;
7986 ey *= ty;
7987}
7988
7989double ComponentAnalyticField::WpotPlaneB2Y(const double xpos,
7990 const double ypos,
7991 const int mx, const int ip) const {
7992 double volt = 0.;
7993 const double ty = HalfPi / m_sy;
7994 // Loop over all wires.
7995 for (unsigned int i = 0; i < m_nWires; ++i) {
7996 const double xx = ty * (xpos - m_w[i].x - mx * m_sx);
7997 const double yy = ty * (ypos - m_w[i].y);
7998 const double yyneg = ty * (ypos + m_w[i].y - 2 * m_coplan[2]);
7999 // Calculate the direct contribution.
8000 double r2 = 1.;
8001 if (fabs(xx) <= 20.) {
8002 const double sinhx = sinh(xx);
8003 const double sinyy = sin(yy);
8004 const double sinyyneg = sin(yyneg);
8005 r2 = (sinhx * sinhx + sinyy * sinyy) /
8006 (sinhx * sinhx + sinyyneg * sinyyneg);
8007 }
8008 // Take care of a plane at constant y.
8009 if (m_ynplax) {
8010 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
8011 if (fabs(xxmirr) <= 20.) {
8012 const double sinhx = sinh(xxmirr);
8013 const double sinyy = sin(yy);
8014 const double sinyyneg = sin(yyneg);
8015 const double r2plan = (sinhx * sinhx + sinyy * sinyy) /
8016 (sinhx * sinhx + sinyyneg * sinyyneg);
8017 r2 /= r2plan;
8018 }
8019 }
8020 volt -= m_qplane[ip][i] * log(r2);
8021 }
8022 return 0.5 * volt;
8023}
8024
8025void ComponentAnalyticField::WfieldPlaneC2X(const double xpos,
8026 const double ypos,
8027 double& ex, double& ey,
8028 const int ip) const {
8029 //-----------------------------------------------------------------------
8030 // IPLC2X - Routine returning the potential and electric field in a
8031 // configuration with 2 x planes and y periodicity.
8032 // VARIABLES : see the writeup
8033 // (Last changed on 12/10/06.)
8034 //-----------------------------------------------------------------------
8035
8036 constexpr std::complex<double> icons(0., 1.);
8037 // Initial values.
8038 std::complex<double> wsum1 = 0.;
8039 std::complex<double> wsum2 = 0.;
8040 double s = 0.;
8041 // Wire loop.
8042 for (unsigned int i = 0; i < m_nWires; ++i) {
8043 const double xx = xpos - m_w[i].x;
8044 const double yy = ypos - m_w[i].y;
8045 // Compute the direct contribution.
8046 auto zeta = m_zmult * std::complex<double>(xx, yy);
8047 if (imag(zeta) > +15.) {
8048 wsum1 -= m_qplane[ip][i] * icons;
8049 } else if (imag(zeta) < -15.) {
8050 wsum1 += m_qplane[ip][i] * icons;
8051 } else {
8052 const auto zterm = Th1(zeta, m_p1, m_p2);
8053 wsum1 += m_qplane[ip][i] * (zterm.second / zterm.first);
8054 }
8055 // Find the plane nearest to the wire.
8056 const double cx =
8057 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
8058 // Constant terms sum
8059 s += m_qplane[ip][i] * (m_w[i].x - cx);
8060 // Mirror contribution.
8061 zeta = m_zmult * std::complex<double>(2. * cx - xpos - m_w[i].x, yy);
8062 if (imag(zeta) > 15.) {
8063 wsum2 -= m_qplane[ip][i] * icons;
8064 } else if (imag(zeta) < -15.) {
8065 wsum2 += m_qplane[ip][i] * icons;
8066 } else {
8067 const auto zterm = Th1(zeta, m_p1, m_p2);
8068 wsum2 += m_qplane[ip][i] * (zterm.second / zterm.first);
8069 }
8070 }
8071 // Convert the two contributions to a real field.
8072 ex = real(m_zmult * (wsum1 + wsum2));
8073 ey = -imag(m_zmult * (wsum1 - wsum2));
8074 // Constant correction terms.
8075 if (m_mode == 0) ex += s * TwoPi / (m_sx * m_sy);
8076}
8077
8078double ComponentAnalyticField::WpotPlaneC2X(const double xpos,
8079 const double ypos,
8080 const int ip) const {
8081 double volt = 0.;
8082 const double c0 = m_mode == 0 ? TwoPi / (m_sx * m_sy) : 0.;
8083 // Wire loop.
8084 for (unsigned int i = 0; i < m_nWires; ++i) {
8085 const double xx = xpos - m_w[i].x;
8086 const double yy = ypos - m_w[i].y;
8087 // Compute the direct contribution.
8088 auto zeta = m_zmult * std::complex<double>(xx, yy);
8089 if (fabs(imag(zeta)) > 15.) {
8090 volt -= m_qplane[ip][i] * (fabs(imag(zeta)) - CLog2);
8091 } else {
8092 const auto zterm = Th1(zeta, m_p1, m_p2);
8093 volt -= m_qplane[ip][i] * log(abs(zterm.first));
8094 }
8095 // Find the plane nearest to the wire.
8096 const double cx =
8097 m_coplax - m_sx * int(round((m_coplax - m_w[i].x) / m_sx));
8098 // Mirror contribution.
8099 zeta = m_zmult * std::complex<double>(2. * cx - xpos - m_w[i].x, yy);
8100 if (fabs(imag(zeta)) > 15.) {
8101 volt += m_qplane[ip][i] * (fabs(imag(zeta)) - CLog2);
8102 } else {
8103 const auto zterm = Th1(zeta, m_p1, m_p2);
8104 volt += m_qplane[ip][i] * log(abs(zterm.first));
8105 }
8106 if (m_mode == 0) {
8107 volt -= c0 * m_qplane[ip][i] * (xpos - cx) * (m_w[i].x - cx);
8108 }
8109 }
8110 return volt;
8111}
8112
8113void ComponentAnalyticField::WfieldPlaneC2Y(const double xpos,
8114 const double ypos,
8115 double& ex, double& ey,
8116 const int ip) const {
8117 //-----------------------------------------------------------------------
8118 // IPLC2Y - Routine returning the potential and electric field in a
8119 // configuration with 2 y planes and x periodicity.
8120 // VARIABLES : see the writeup
8121 // (Last changed on 12/10/06.)
8122 //-----------------------------------------------------------------------
8123
8124 constexpr std::complex<double> icons(0., 1.);
8125 // Initial values.
8126 std::complex<double> wsum1 = 0.;
8127 std::complex<double> wsum2 = 0.;
8128 double s = 0.;
8129 // Wire loop.
8130 for (unsigned int i = 0; i < m_nWires; ++i) {
8131 const double xx = xpos - m_w[i].x;
8132 const double yy = ypos - m_w[i].y;
8133 // Compute the direct contribution.
8134 auto zeta = m_zmult * std::complex<double>(xx, yy);
8135 if (imag(zeta) > +15.) {
8136 wsum1 -= m_qplane[ip][i] * icons;
8137 } else if (imag(zeta) < -15.) {
8138 wsum1 += m_qplane[ip][i] * icons;
8139 } else {
8140 const auto zterm = Th1(zeta, m_p1, m_p2);
8141 wsum1 += m_qplane[ip][i] * (zterm.second / zterm.first);
8142 }
8143 // Find the plane nearest to the wire.
8144 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
8145 // Constant terms sum
8146 s += m_qplane[ip][i] * (m_w[i].y - cy);
8147 // Mirror contribution.
8148 zeta = m_zmult * std::complex<double>(xx, 2. * cy - ypos - m_w[i].y);
8149 if (imag(zeta) > 15.) {
8150 wsum2 -= m_qplane[ip][i] * icons;
8151 } else if (imag(zeta) < -15.) {
8152 wsum2 += m_qplane[ip][i] * icons;
8153 } else {
8154 const auto zterm = Th1(zeta, m_p1, m_p2);
8155 wsum2 += m_qplane[ip][i] * (zterm.second / zterm.first);
8156 }
8157 }
8158 // Convert the two contributions to a real field.
8159 ex = real(m_zmult * (wsum1 - wsum2));
8160 ey = -imag(m_zmult * (wsum1 + wsum2));
8161 // Constant correction terms.
8162 if (m_mode == 1) ey += s * TwoPi / (m_sx * m_sy);
8163}
8164
8165double ComponentAnalyticField::WpotPlaneC2Y(const double xpos,
8166 const double ypos,
8167 const int ip) const {
8168 double volt = 0.;
8169 const double c1 = m_mode == 1 ? TwoPi / (m_sx * m_sy) : 0.;
8170 // Wire loop.
8171 for (unsigned int i = 0; i < m_nWires; ++i) {
8172 const double xx = xpos - m_w[i].x;
8173 const double yy = ypos - m_w[i].y;
8174 // Compute the direct contribution.
8175 auto zeta = m_zmult * std::complex<double>(xx, yy);
8176 if (fabs(imag(zeta)) > 15.) {
8177 volt -= m_qplane[ip][i] * (fabs(imag(zeta)) - CLog2);
8178 } else {
8179 const auto zterm = Th1(zeta, m_p1, m_p2);
8180 volt -= m_qplane[ip][i] * log(abs(zterm.first));
8181 }
8182 // Find the plane nearest to the wire.
8183 double cy = m_coplay - m_sy * int(round((m_coplay - m_w[i].y) / m_sy));
8184 // Mirror contribution.
8185 zeta = m_zmult * std::complex<double>(xx, 2. * cy - ypos - m_w[i].y);
8186 if (fabs(imag(zeta)) > 15.) {
8187 volt += m_qplane[ip][i] * (fabs(imag(zeta)) - CLog2);
8188 } else {
8189 const auto zterm = Th1(zeta, m_p1, m_p2);
8190 volt += m_qplane[ip][i] * log(abs(zterm.first));
8191 }
8192 // Correct the voltage, if needed (MODE).
8193 if (m_mode == 1) {
8194 volt -= c1 * m_qplane[ip][i] * (ypos - cy) * (m_w[i].y - cy);
8195 }
8196 }
8197 return volt;
8198}
8199
8200void ComponentAnalyticField::WfieldPlaneC30(const double xpos,
8201 const double ypos,
8202 double& ex, double& ey,
8203 const int ip) const {
8204 //-----------------------------------------------------------------------
8205 // IPLC30 - Routine returning the weighting field field in a
8206 // configuration with 2 y and 2 x planes. This routine is
8207 // basically the same as EFCC30.
8208 // (Last changed on 9/11/98.)
8209 //-----------------------------------------------------------------------
8210
8211 constexpr std::complex<double> icons(0., 1.);
8212 // Initial values.
8213 std::complex<double> wsum1 = 0.;
8214 std::complex<double> wsum2 = 0.;
8215 std::complex<double> wsum3 = 0.;
8216 std::complex<double> wsum4 = 0.;
8217 // Wire loop.
8218 for (unsigned int i = 0; i < m_nWires; ++i) {
8219 const double xx = xpos - m_w[i].x;
8220 const double yy = ypos - m_w[i].y;
8221 // Compute the direct contribution.
8222 auto zeta = m_zmult * std::complex<double>(xx, yy);
8223 if (imag(zeta) > +15.) {
8224 wsum1 -= m_qplane[ip][i] * icons;
8225 } else if (imag(zeta) < -15.) {
8226 wsum1 += m_qplane[ip][i] * icons;
8227 } else {
8228 const auto zterm = Th1(zeta, m_p1, m_p2);
8229 wsum1 += m_qplane[ip][i] * zterm.second / zterm.first;
8230 }
8231 // Mirror contribution from the x plane.
8232 const double xxmirr = MirrorCoordinate(xpos, m_coplax, m_w[i].x, m_sx);
8233 zeta = m_zmult * std::complex<double>(xxmirr, yy);
8234 if (imag(zeta) > 15.) {
8235 wsum2 -= m_qplane[ip][i] * icons;
8236 } else if (imag(zeta) < -15.) {
8237 wsum2 += m_qplane[ip][i] * icons;
8238 } else {
8239 const auto zterm = Th1(zeta, m_p1, m_p2);
8240 wsum2 += m_qplane[ip][i] * zterm.second / zterm.first;
8241 }
8242 // Mirror contribution from the y plane.
8243 const double yymirr = MirrorCoordinate(ypos, m_coplay, m_w[i].y, m_sy);
8244 zeta = m_zmult * std::complex<double>(xx, yymirr);
8245 if (imag(zeta) > 15.) {
8246 wsum3 -= m_qplane[ip][i] * icons;
8247 } else if (imag(zeta) < -15.) {
8248 wsum3 += m_qplane[ip][i] * icons;
8249 } else {
8250 const auto zterm = Th1(zeta, m_p1, m_p2);
8251 wsum3 += m_qplane[ip][i] * zterm.second / zterm.first;
8252 }
8253 // Mirror contribution from both the x and the y plane.
8254 zeta = m_zmult * std::complex<double>(xxmirr, yymirr);
8255 if (imag(zeta) > 15.) {
8256 wsum4 -= m_qplane[ip][i] * icons;
8257 } else if (imag(zeta) < -15.) {
8258 wsum4 += m_qplane[ip][i] * icons;
8259 } else {
8260 const auto zterm = Th1(zeta, m_p1, m_p2);
8261 wsum4 += m_qplane[ip][i] * zterm.second / zterm.first;
8262 }
8263 }
8264 ex = real(m_zmult * (wsum1 + wsum2 - wsum3 - wsum4));
8265 ey = -imag(m_zmult * (wsum1 - wsum2 + wsum3 - wsum4));
8266}
8267
8268double ComponentAnalyticField::WpotPlaneC30(const double xpos,
8269 const double ypos,
8270 const int ip) const {
8271 double volt = 0.;
8272 // Wire loop.
8273 for (unsigned int i = 0; i < m_nWires; ++i) {
8274 const double xx = xpos - m_w[i].x;
8275 const double yy = ypos - m_w[i].y;
8276 // Compute the direct contribution.
8277 auto zeta = m_zmult * std::complex<double>(xx, yy);
8278 if (fabs(imag(zeta)) > 15.) {
8279 volt -= m_qplane[ip][i] * (fabs(imag(zeta)) - CLog2);
8280 } else {
8281 const auto zterm = Th1(zeta, m_p1, m_p2);
8282 volt -= m_qplane[ip][i] * log(abs(zterm.first));
8283 }
8284 // Mirror contribution from the x plane.
8285 const double xxmirr = MirrorCoordinate(xpos, m_coplax, m_w[i].x, m_sx);
8286 zeta = m_zmult * std::complex<double>(xxmirr, yy);
8287 if (fabs(imag(zeta)) > 15.) {
8288 volt += m_qplane[ip][i] * (fabs(imag(zeta)) - CLog2);
8289 } else {
8290 const auto zterm = Th1(zeta, m_p1, m_p2);
8291 volt += m_qplane[ip][i] * log(abs(zterm.first));
8292 }
8293 // Mirror contribution from the y plane.
8294 const double yymirr = MirrorCoordinate(ypos, m_coplay, m_w[i].y, m_sy);
8295 zeta = m_zmult * std::complex<double>(xx, yymirr);
8296 if (fabs(imag(zeta)) > 15.) {
8297 volt += m_qplane[ip][i] * (fabs(imag(zeta)) - CLog2);
8298 } else {
8299 const auto zterm = Th1(zeta, m_p1, m_p2);
8300 volt += m_qplane[ip][i] * log(abs(zterm.first));
8301 }
8302 // Mirror contribution from both the x and the y plane.
8303 zeta = m_zmult * std::complex<double>(xxmirr, yymirr);
8304 if (fabs(imag(zeta)) > 15.) {
8305 volt -= m_qplane[ip][i] * (fabs(imag(zeta)) - CLog2);
8306 } else {
8307 const auto zterm = Th1(zeta, m_p1, m_p2);
8308 volt -= m_qplane[ip][i] * log(abs(zterm.first));
8309 }
8310 }
8311 return volt;
8312}
8313
8314void ComponentAnalyticField::WfieldPlaneD10(const double xpos,
8315 const double ypos,
8316 double& ex, double& ey,
8317 const int ip) const {
8318 //-----------------------------------------------------------------------
8319 // IPLD10 - Subroutine computing the signal on wire IPLANE due to a
8320 // charge at (XPOS,YPOS). This is effectively routine EFCD10.
8321 // VARIABLES : EX, EY : Electric field.
8322 // (XPOS,YPOS): The position where the field is calculated.
8323 // ZI, ZPOS : Shorthand complex notations.
8324 // (Last changed on 9/11/98.)
8325 //-----------------------------------------------------------------------
8326
8327 ex = ey = 0.;
8328 // Set the complex position coordinates.
8329 const std::complex<double> zpos(xpos, ypos);
8330 // Loop over all wires.
8331 for (unsigned int i = 0; i < m_nWires; ++i) {
8332 // Set the complex version of the wire-coordinate for simplicity.
8333 const std::complex<double> zi(m_w[i].x, m_w[i].y);
8334 // Compute the contribution to the electric field.
8335 const auto wi = 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
8336 ex += m_qplane[ip][i] * real(wi);
8337 ey += m_qplane[ip][i] * imag(wi);
8338 }
8339}
8340
8341double ComponentAnalyticField::WpotPlaneD10(const double xpos,
8342 const double ypos,
8343 const int ip) const {
8344 double volt = 0.;
8345 // Set the complex position coordinates.
8346 const std::complex<double> zpos(xpos, ypos);
8347 // Loop over all wires.
8348 for (unsigned int i = 0; i < m_nWires; ++i) {
8349 // Set the complex version of the wire-coordinate for simplicity.
8350 const std::complex<double> zi(m_w[i].x, m_w[i].y);
8351 volt -= m_qplane[ip][i] *
8352 log(abs(m_cotube * (zpos - zi) / (m_cotube2 - zpos * conj(zi))));
8353 }
8354 return volt;
8355}
8356
8357void ComponentAnalyticField::WfieldPlaneD30(const double xpos,
8358 const double ypos,
8359 double& ex, double& ey,
8360 const int ip) const {
8361 //-----------------------------------------------------------------------
8362 // IPLD30 - Subroutine computing the weighting field for a polygonal
8363 // cells without periodicities, type D3.
8364 // VARIABLES : EX, EY : Electric field
8365 // (XPOS,YPOS): The position where the field is calculated.
8366 // ZI, ZPOS : Shorthand complex notations.
8367 // (Last changed on 9/11/98.)
8368 //-----------------------------------------------------------------------
8369 ex = ey = 0.;
8370 // Get the mapping of the position.
8371 std::complex<double> wpos, wdpos;
8372 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
8373 // Loop over all wires.
8374 for (unsigned int i = 0; i < m_nWires; ++i) {
8375 // Compute the contribution to the electric field.
8376 const auto whelp = wdpos * (1. - pow(abs(wmap[i]), 2)) /
8377 ((wpos - wmap[i]) * (1. - conj(wmap[i]) * wpos));
8378 ex += m_qplane[ip][i] * real(whelp);
8379 ey -= m_qplane[ip][i] * imag(whelp);
8380 }
8381 ex /= m_cotube;
8382 ey /= m_cotube;
8383}
8384
8385double ComponentAnalyticField::WpotPlaneD30(const double xpos,
8386 const double ypos,
8387 const int ip) const {
8388 double volt = 0.;
8389 // Get the mapping of the position.
8390 std::complex<double> wpos, wdpos;
8391 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
8392 // Loop over all wires.
8393 for (unsigned int i = 0; i < m_nWires; ++i) {
8394 volt -= m_qplane[ip][i] *
8395 log(abs((wpos - wmap[i]) / (1. - wpos * conj(wmap[i]))));
8396 }
8397 return volt;
8398}
8399
8400void ComponentAnalyticField::WfieldStripZ(
8401 const double xpos, const double ypos, double& ex, double& ey,
8402 const int ip, const Strip& strip) const {
8403 //-----------------------------------------------------------------------
8404 // IONEST - Weighting field for strips.
8405 // (Last changed on 6/12/00.)
8406 //-----------------------------------------------------------------------
8407
8408 // Initialise the weighting field.
8409 ex = ey = 0.;
8410
8411 // Transform to normalised coordinates.
8412 double xw = 0., yw = 0.;
8413 switch (ip) {
8414 case 0:
8415 xw = -ypos + 0.5 * (strip.smin + strip.smax);
8416 yw = xpos - m_coplan[ip];
8417 break;
8418 case 1:
8419 xw = ypos - 0.5 * (strip.smin + strip.smax);
8420 yw = m_coplan[ip] - xpos;
8421 break;
8422 case 2:
8423 xw = xpos - 0.5 * (strip.smin + strip.smax);
8424 yw = ypos - m_coplan[ip];
8425 break;
8426 case 3:
8427 xw = -xpos + 0.5 * (strip.smin + strip.smax);
8428 yw = m_coplan[ip] - ypos;
8429 break;
8430 default:
8431 return;
8432 }
8433
8434 // Make sure we are in the fiducial part of the weighting map.
8435 if (yw <= 0. || yw > strip.gap) return;
8436
8437 // Define shorthand notations.
8438 const double invg = 1. / strip.gap;
8439 const double s = sin(Pi * yw * invg);
8440 const double c = cos(Pi * yw * invg);
8441 // Strip halfwidth.
8442 const double w = 0.5 * fabs(strip.smax - strip.smin);
8443 const double e1 = exp(Pi * (w - xw) * invg);
8444 const double e2 = exp(-Pi * (w + xw) * invg);
8445 // Check for singularities.
8446 if (c == e1 || c == e2) return;
8447 // Evaluate the field.
8448 const double s2 = s * s;
8449 const double d1 = c - e1;
8450 const double d2 = c - e2;
8451 const double t1 = 1. / (s2 + d1 * d1);
8452 const double t2 = 1. / (s2 + d2 * d2);
8453 const double fx = s * (e1 * t1 - e2 * t2) * invg;
8454 const double fy = ((1. - c * e2) * t2 - (1. - c * e1) * t1) * invg;
8455
8456 // Rotate the field back to the original coordinates.
8457 switch (ip) {
8458 case 0:
8459 ex = fy;
8460 ey = -fx;
8461 break;
8462 case 1:
8463 ex = -fy;
8464 ey = fx;
8465 break;
8466 case 2:
8467 ex = fx;
8468 ey = fy;
8469 break;
8470 case 3:
8471 ex = -fx;
8472 ey = -fy;
8473 break;
8474 }
8475}
8476
8477double ComponentAnalyticField::WpotStripZ(
8478 const double xpos, const double ypos,
8479 const int ip, const Strip& strip) const {
8480
8481 // Transform to normalised coordinates.
8482 double xw = 0., yw = 0.;
8483 switch (ip) {
8484 case 0:
8485 xw = -ypos + 0.5 * (strip.smin + strip.smax);
8486 yw = xpos - m_coplan[ip];
8487 break;
8488 case 1:
8489 xw = ypos - 0.5 * (strip.smin + strip.smax);
8490 yw = m_coplan[ip] - xpos;
8491 break;
8492 case 2:
8493 xw = xpos - 0.5 * (strip.smin + strip.smax);
8494 yw = ypos - m_coplan[ip];
8495 break;
8496 case 3:
8497 xw = -xpos + 0.5 * (strip.smin + strip.smax);
8498 yw = m_coplan[ip] - ypos;
8499 break;
8500 default:
8501 return 0.;
8502 }
8503
8504 // Make sure we are in the fiducial part of the weighting map.
8505 if (yw <= 0. || yw > strip.gap) return 0.;
8506
8507 // Define shorthand notations.
8508 const double a = Pi / strip.gap;
8509 const double c = cos(a * yw);
8510 // Strip halfwidth.
8511 const double w = 0.5 * fabs(strip.smax - strip.smin);
8512 const double e1 = exp(a * (w - xw));
8513 const double e2 = exp(-a * (w + xw));
8514 // Check for singularities.
8515 if (c == e1 || c == e2) return 0.;
8516 const double invs = 1. / sin(a * yw);
8517 constexpr double invPi = 1. / Pi;
8518 return (atan((c - e2) * invs) - atan((c - e1) * invs)) * invPi;
8519}
8520
8521void ComponentAnalyticField::WfieldStripXy(const double xpos, const double ypos,
8522 const double zpos, double& ex,
8523 double& ey, double& ez,
8524 const int ip,
8525 const Strip& strip) const {
8526 //-----------------------------------------------------------------------
8527 // IONEST - Weighting field for strips.
8528 // (Last changed on 6/12/00.)
8529 //-----------------------------------------------------------------------
8530
8531 // Initialise the weighting field.
8532 ex = ey = ez = 0.;
8533
8534 // Transform to normalised coordinates.
8535 double xw = 0., yw = 0.;
8536 switch (ip) {
8537 case 0:
8538 xw = -zpos + 0.5 * (strip.smin + strip.smax);
8539 yw = xpos - m_coplan[ip];
8540 break;
8541 case 1:
8542 xw = zpos - 0.5 * (strip.smin + strip.smax);
8543 yw = m_coplan[ip] - xpos;
8544 break;
8545 case 2:
8546 xw = zpos - 0.5 * (strip.smin + strip.smax);
8547 yw = ypos - m_coplan[ip];
8548 break;
8549 case 3:
8550 xw = -zpos + 0.5 * (strip.smin + strip.smax);
8551 yw = m_coplan[ip] - ypos;
8552 break;
8553 default:
8554 return;
8555 }
8556
8557 // Make sure we are in the fiducial part of the weighting map.
8558 if (yw <= 0. || yw > strip.gap) return;
8559
8560 // Define shorthand notations.
8561 const double invg = 1. / strip.gap;
8562 const double s = sin(Pi * yw * invg);
8563 const double c = cos(Pi * yw * invg);
8564 // Strip halfwidth.
8565 const double w = 0.5 * fabs(strip.smax - strip.smin);
8566 const double e1 = exp(Pi * (w - xw) * invg);
8567 const double e2 = exp(-Pi * (w + xw) * invg);
8568 // Check for singularities.
8569 if (c == e1 || c == e2) return;
8570 // Evaluate the field.
8571 const double s2 = s * s;
8572 const double d1 = c - e1;
8573 const double d2 = c - e2;
8574 const double t1 = 1. / (s2 + d1 * d1);
8575 const double t2 = 1. / (s2 + d2 * d2);
8576 const double fx = s * (e1 * t1 - e2 * t2) * invg;
8577 const double fy = ((1. - c * e2) * t2 - (1. - c * e1) * t1) * invg;
8578
8579 // Rotate the field back to the original coordinates.
8580 switch (ip) {
8581 case 0:
8582 ex = fy;
8583 ey = 0.;
8584 ez = -fx;
8585 break;
8586 case 1:
8587 ex = -fy;
8588 ey = 0.;
8589 ez = fx;
8590 break;
8591 case 2:
8592 ex = 0.;
8593 ey = fy;
8594 ez = fx;
8595 break;
8596 case 3:
8597 ex = 0.;
8598 ey = -fy;
8599 ez = -fx;
8600 break;
8601 }
8602}
8603
8604double ComponentAnalyticField::WpotStripXy(const double xpos, const double ypos,
8605 const double zpos,
8606 const int ip, const Strip& strip) const {
8607 // Transform to normalised coordinates.
8608 double xw = 0., yw = 0.;
8609 switch (ip) {
8610 case 0:
8611 xw = -zpos + 0.5 * (strip.smin + strip.smax);
8612 yw = xpos - m_coplan[ip];
8613 break;
8614 case 1:
8615 xw = zpos - 0.5 * (strip.smin + strip.smax);
8616 yw = m_coplan[ip] - xpos;
8617 break;
8618 case 2:
8619 xw = zpos - 0.5 * (strip.smin + strip.smax);
8620 yw = ypos - m_coplan[ip];
8621 break;
8622 case 3:
8623 xw = -zpos + 0.5 * (strip.smin + strip.smax);
8624 yw = m_coplan[ip] - ypos;
8625 break;
8626 default:
8627 return 0.;
8628 }
8629
8630 // Make sure we are in the fiducial part of the weighting map.
8631 if (yw <= 0. || yw > strip.gap) return 0.;
8632
8633 // Define shorthand notations.
8634 const double a = Pi / strip.gap;
8635 const double c = cos(a * yw);
8636 // Strip halfwidth.
8637 const double w = 0.5 * fabs(strip.smax - strip.smin);
8638 const double e1 = exp(a * (w - xw));
8639 const double e2 = exp(-a * (w + xw));
8640 // Check for singularities.
8641 if (c == e1 || c == e2) return 0.;
8642 const double invs = 1. / sin(a * yw);
8643 constexpr double invPi = 1. / Pi;
8644 return (atan((c - e2) * invs) - atan((c - e1) * invs)) * invPi;
8645}
8646
8647void ComponentAnalyticField::WfieldPixel(const double xpos, const double ypos,
8648 const double zpos,
8649 double& ex, double& ey, double& ez,
8650 const int ip,
8651 const Pixel& pixel) const {
8652 //-----------------------------------------------------------------------
8653 // Weighting field for pixels.
8654 //-----------------------------------------------------------------------
8655 // W. Riegler, G. Aglieri Rinella,
8656 // Point charge potential and weighting field of a pixel or pad
8657 // in a plane condenser,
8658 // Nucl. Instr. Meth. A 767, 2014, 267 - 270
8659 // http://dx.doi.org/10.1016/j.nima.2014.08.044
8660
8661 // Initialise the weighting field.
8662 ex = ey = ez = 0.;
8663
8664 // Transform to standard coordinates.
8665 double x = 0., y = 0., z = 0.;
8666
8667 // Pixel centre and widths.
8668 const double ps = 0.5 * (pixel.smin + pixel.smax);
8669 const double pz = 0.5 * (pixel.zmin + pixel.zmax);
8670 const double wx = pixel.smax - pixel.smin;
8671 const double wy = pixel.zmax - pixel.zmin;
8672 switch (ip) {
8673 case 0:
8674 x = ypos - ps;
8675 y = zpos - pz;
8676 z = xpos - m_coplan[ip];
8677 break;
8678 case 1:
8679 x = ypos - ps;
8680 y = -zpos + pz;
8681 z = -xpos + m_coplan[ip];
8682 break;
8683 case 2:
8684 x = xpos - ps;
8685 y = -zpos + pz;
8686 z = ypos - m_coplan[ip];
8687 break;
8688 case 3:
8689 x = xpos - ps;
8690 y = zpos - pz;
8691 z = -ypos + m_coplan[ip];
8692 break;
8693 default:
8694 return;
8695 }
8696 // If needed, rotate into place.
8697 const bool rot = fabs(pixel.sphi) > 1.e-9;
8698 if (rot) {
8699 const double xx = x;
8700 const double yy = y;
8701 x = pixel.cphi * xx + pixel.sphi * yy;
8702 y = -pixel.sphi * xx + pixel.cphi * yy;
8703 }
8704 // if (z < 0.) std::cerr << " z = " << z << std::endl;
8705 // Make sure we are in the fiducial part of the weighting map.
8706 // Commenting out this lines either breaks the simulation or the plot!
8707 // TODO!
8708 // if (z <= 0. || z > d) return;
8709
8710 // Define shorthand notations and common terms.
8711 const double x1 = x - 0.5 * wx;
8712 const double x2 = x + 0.5 * wx;
8713 const double y1 = y - 0.5 * wy;
8714 const double y2 = y + 0.5 * wy;
8715 const double x1s = x1 * x1;
8716 const double x2s = x2 * x2;
8717 const double y1s = y1 * y1;
8718 const double y2s = y2 * y2;
8719
8720 // Calculate number of terms needed to have sufficiently small error.
8721 const double maxError = 1.e-5;
8722 const double d = pixel.gap;
8723 const double d3 = d * d * d;
8724 const unsigned int nz = std::ceil(sqrt(wx * wy / (8 * Pi * d3 * maxError)));
8725 const unsigned int nx = std::ceil(sqrt(wy * z / (4 * Pi * d3 * maxError)));
8726 const unsigned int ny = std::ceil(sqrt(wx * z / (4 * Pi * d3 * maxError)));
8727 const unsigned int nn = std::max(ny, std::max(nx, nz));
8728 for (unsigned int i = 1; i <= nn; ++i) {
8729 const double u1 = 2 * i * d - z;
8730 const double u2 = 2 * i * d + z;
8731 const double u1s = u1 * u1;
8732 const double u2s = u2 * u2;
8733 const double u1x1y1 = sqrt(x1s + y1s + u1s);
8734 const double u1x1y2 = sqrt(x1s + y2s + u1s);
8735 const double u1x2y1 = sqrt(x2s + y1s + u1s);
8736 const double u1x2y2 = sqrt(x2s + y2s + u1s);
8737 const double u2x1y1 = sqrt(x1s + y1s + u2s);
8738 const double u2x1y2 = sqrt(x1s + y2s + u2s);
8739 const double u2x2y1 = sqrt(x2s + y1s + u2s);
8740 const double u2x2y2 = sqrt(x2s + y2s + u2s);
8741
8742 if (i <= nx) {
8743 //-df/dx(x,y,2nd-z)
8744 ex -= u1 * y1 / ((u1s + x2s) * u1x2y1) -
8745 u1 * y1 / ((u1s + x1s) * u1x1y1) +
8746 u1 * y2 / ((u1s + x1s) * u1x1y2) - u1 * y2 / ((u1s + x2s) * u1x2y2);
8747
8748 //-df/dx(x,y,2nd+z)
8749 ex += u2 * y1 / ((u2s + x2s) * u2x2y1) -
8750 u2 * y1 / ((u2s + x1s) * u2x1y1) +
8751 u2 * y2 / ((u2s + x1s) * u2x1y2) - u2 * y2 / ((u2s + x2s) * u2x2y2);
8752 }
8753 if (i <= ny) {
8754 //-df/dy(x,y,2nd-z)
8755 ey -= u1 * x1 / ((u1s + y2s) * u1x1y2) -
8756 u1 * x1 / ((u1s + y1s) * u1x1y1) +
8757 u1 * x2 / ((u1s + y1s) * u1x2y1) - u1 * x2 / ((u1s + y2s) * u1x2y2);
8758
8759 //-df/dy(x,y,2nd+z)
8760 ey += u2 * x1 / ((u2s + y2s) * u2x1y2) -
8761 u2 * x1 / ((u2s + y1s) * u2x1y1) +
8762 u2 * x2 / ((u2s + y1s) * u2x2y1) - u2 * x2 / ((u2s + y2s) * u2x2y2);
8763 }
8764 if (i <= nz) {
8765 //-df/dz(x,y,2nd-z)
8766 ez += x1 * y1 * (x1s + y1s + 2 * u1s) /
8767 ((x1s + u1s) * (y1s + u1s) * u1x1y1) +
8768 x2 * y2 * (x2s + y2s + 2 * u1s) /
8769 ((x2s + u1s) * (y2s + u1s) * u1x2y2) -
8770 x1 * y2 * (x1s + y2s + 2 * u1s) /
8771 ((x1s + u1s) * (y2s + u1s) * u1x1y2) -
8772 x2 * y1 * (x2s + y1s + 2 * u1s) /
8773 ((x2s + u1s) * (y1s + u1s) * u1x2y1);
8774
8775 //-df/dz(x,y,2nd+z)
8776 ez += x1 * y1 * (x1s + y1s + 2 * u2s) /
8777 ((x1s + u2s) * (y1s + u2s) * u2x1y1) +
8778 x2 * y2 * (x2s + y2s + 2 * u2s) /
8779 ((x2s + u2s) * (y2s + u2s) * u2x2y2) -
8780 x1 * y2 * (x1s + y2s + 2 * u2s) /
8781 ((x1s + u2s) * (y2s + u2s) * u2x1y2) -
8782 x2 * y1 * (x2s + y1s + 2 * u2s) /
8783 ((x2s + u2s) * (y1s + u2s) * u2x2y1);
8784 }
8785 }
8786
8787 const double zs = z * z;
8788 const double x1y1 = sqrt(x1s + y1s + zs);
8789 const double x1y2 = sqrt(x1s + y2s + zs);
8790 const double x2y1 = sqrt(x2s + y1s + zs);
8791 const double x2y2 = sqrt(x2s + y2s + zs);
8792 //-df/dx(x,y,z)
8793 ex += z * y1 / ((zs + x2s) * x2y1) - z * y1 / ((zs + x1s) * x1y1) +
8794 z * y2 / ((zs + x1s) * x1y2) - z * y2 / ((zs + x2s) * x2y2);
8795
8796 //-df/y(x,y,z)
8797 ey += z * x1 / ((zs + y2s) * x1y2) - z * x1 / ((zs + y1s) * x1y1) +
8798 z * x2 / ((zs + y1s) * x2y1) - z * x2 / ((zs + y2s) * x2y2);
8799
8800 //-df/dz(x,y,z)
8801 ez += x1 * y1 * (x1s + y1s + 2 * zs) / ((x1s + zs) * (y1s + zs) * x1y1) +
8802 x2 * y2 * (x2s + y2s + 2 * zs) / ((x2s + zs) * (y2s + zs) * x2y2) -
8803 x1 * y2 * (x1s + y2s + 2 * zs) / ((x1s + zs) * (y2s + zs) * x1y2) -
8804 x2 * y1 * (x2s + y1s + 2 * zs) / ((x2s + zs) * (y1s + zs) * x2y1);
8805
8806 constexpr double invTwoPi = 1. / TwoPi;
8807 ex *= invTwoPi;
8808 ey *= invTwoPi;
8809 ez *= invTwoPi;
8810
8811 // Rotate the field back to the original coordinates.
8812 const double fx = rot ? pixel.cphi * ex - pixel.sphi * ey : ex;
8813 const double fy = rot ? pixel.sphi * ex + pixel.cphi * ey : ey;
8814 const double fz = ez;
8815 switch (ip) {
8816 case 0:
8817 ex = fz;
8818 ey = fx;
8819 ez = fy;
8820 break;
8821 case 1:
8822 ex = -fz;
8823 ey = fx;
8824 ez = -fy;
8825 break;
8826 case 2:
8827 ex = fx;
8828 ey = fz;
8829 ez = -fy;
8830 break;
8831 case 3:
8832 ex = fx;
8833 ey = -fz;
8834 ez = fy;
8835 break;
8836 }
8837}
8838
8839double ComponentAnalyticField::WpotPixel(const double xpos, const double ypos,
8840 const double zpos,
8841 const int ip, const Pixel& pixel) const {
8842 // Transform to standard coordinates.
8843 double x = 0., y = 0., z = 0.;
8844
8845 // Pixel centre and widths.
8846 const double ps = 0.5 * (pixel.smin + pixel.smax);
8847 const double pz = 0.5 * (pixel.zmin + pixel.zmax);
8848 const double wx = pixel.smax - pixel.smin;
8849 const double wy = pixel.zmax - pixel.zmin;
8850 switch (ip) {
8851 case 0:
8852 x = ypos - ps;
8853 y = zpos - pz;
8854 z = xpos - m_coplan[ip];
8855 break;
8856 case 1:
8857 x = ypos - ps;
8858 y = -zpos + pz;
8859 z = -xpos + m_coplan[ip];
8860 break;
8861 case 2:
8862 x = xpos - ps;
8863 y = -zpos + pz;
8864 z = ypos - m_coplan[ip];
8865 break;
8866 case 3:
8867 x = xpos - ps;
8868 y = zpos - pz;
8869 z = -ypos + m_coplan[ip];
8870 break;
8871 default:
8872 return 0.;
8873 }
8874 // If needed, rotate into place.
8875 if (fabs(pixel.sphi) > 1.e-9) {
8876 const double xx = x;
8877 const double yy = y;
8878 x = pixel.cphi * xx + pixel.sphi * yy;
8879 y = -pixel.sphi * xx + pixel.cphi * yy;
8880 }
8881
8882 // Define shorthand notations and common terms.
8883 const double x1 = x - 0.5 * wx;
8884 const double x2 = x + 0.5 * wx;
8885 const double y1 = y - 0.5 * wy;
8886 const double y2 = y + 0.5 * wy;
8887 const double x1s = x1 * x1;
8888 const double x2s = x2 * x2;
8889 const double y1s = y1 * y1;
8890 const double y2s = y2 * y2;
8891
8892 // Calculate number of terms needed to have sufficiently small error.
8893 const double maxError = 1.e-5;
8894 const double d = pixel.gap;
8895 const double d3 = d * d * d;
8896 const unsigned int nn = std::ceil(sqrt(wx * wy * z / (8 * Pi * d3 * maxError)));
8897 double volt = 0.;
8898 for (unsigned int i = 1; i <= nn; ++i) {
8899 const double u1 = 2 * i * d - z;
8900 const double u2 = 2 * i * d + z;
8901 const double u1s = u1 * u1;
8902 const double u2s = u2 * u2;
8903 const double u1x1y1 = sqrt(x1s + y1s + u1s);
8904 const double u1x1y2 = sqrt(x1s + y2s + u1s);
8905 const double u1x2y1 = sqrt(x2s + y1s + u1s);
8906 const double u1x2y2 = sqrt(x2s + y2s + u1s);
8907 const double u2x1y1 = sqrt(x1s + y1s + u2s);
8908 const double u2x1y2 = sqrt(x1s + y2s + u2s);
8909 const double u2x2y1 = sqrt(x2s + y1s + u2s);
8910 const double u2x2y2 = sqrt(x2s + y2s + u2s);
8911
8912 volt -= atan(x1 * y1 / (u1 * u1x1y1)) + atan(x2 * y2 / (u1 * u1x2y2)) -
8913 atan(x1 * y2 / (u1 * u1x1y2)) - atan(x2 * y1 / (u1 * u1x2y1));
8914 volt += atan(x1 * y1 / (u2 * u2x1y1)) + atan(x2 * y2 / (u2 * u2x2y2)) -
8915 atan(x1 * y2 / (u2 * u2x1y2)) - atan(x2 * y1 / (u2 * u2x2y1));
8916 }
8917
8918 const double zs = z * z;
8919 const double x1y1 = sqrt(x1s + y1s + zs);
8920 const double x1y2 = sqrt(x1s + y2s + zs);
8921 const double x2y1 = sqrt(x2s + y1s + zs);
8922 const double x2y2 = sqrt(x2s + y2s + zs);
8923
8924 volt += atan(x1 * y1 / (z * x1y1)) + atan(x2 * y2 / (z * x2y2)) -
8925 atan(x1 * y2 / (z * x1y2)) - atan(x2 * y1 / (z * x2y1));
8926 constexpr double invTwoPi = 1. / TwoPi;
8927 return volt * invTwoPi;
8928}
8929
8930void ComponentAnalyticField::FieldAtWireA00(
8931 const double xpos, const double ypos, double& ex, double& ey,
8932 const std::vector<bool>& cnalso) const {
8933 //-----------------------------------------------------------------------
8934 // FFCA00 - Subroutine performing the actual field calculations in case
8935 // only one charge and not more than 1 mirror-charge in either
8936 // x or y is present.
8937 // The potential used is 1/2*pi*eps0 log(r).
8938 // (Last changed on 27/ 1/96.)
8939 //-----------------------------------------------------------------------
8940
8941 ex = ey = 0.;
8942 // Loop over all wires.
8943 for (unsigned int i = 0; i < m_nWires; ++i) {
8944 const auto& wire = m_w[i];
8945 // Calculate the field in case there are no planes.
8946 double exhelp = 0.;
8947 double eyhelp = 0.;
8948 const double xx = xpos - wire.x;
8949 const double yy = ypos - wire.y;
8950 if (cnalso[i]) {
8951 const double r2 = xx * xx + yy * yy;
8952 exhelp = xx / r2;
8953 eyhelp = yy / r2;
8954 }
8955 // Take care of a plane at constant x.
8956 double xxmirr = 0.;
8957 if (m_ynplax) {
8958 xxmirr = wire.x + xpos - 2 * m_coplax;
8959 const double r2plan = xxmirr * xxmirr + yy * yy;
8960 exhelp -= xxmirr / r2plan;
8961 eyhelp -= yy / r2plan;
8962 }
8963 // Take care of a plane at constant y.
8964 double yymirr = 0.;
8965 if (m_ynplay) {
8966 yymirr = wire.y + ypos - 2 * m_coplay;
8967 const double r2plan = xx * xx + yymirr * yymirr;
8968 exhelp -= xx / r2plan;
8969 eyhelp -= yymirr / r2plan;
8970 }
8971 // Take care of pairs of planes.
8972 if (m_ynplax && m_ynplay) {
8973 const double r2plan = xxmirr * xxmirr + yymirr * yymirr;
8974 exhelp += xxmirr / r2plan;
8975 eyhelp += yymirr / r2plan;
8976 }
8977 ex += wire.e * exhelp;
8978 ey += wire.e * eyhelp;
8979 }
8980}
8981
8982void ComponentAnalyticField::FieldAtWireB1X(
8983 const double xpos, const double ypos, double& ex, double& ey,
8984 const std::vector<bool>& cnalso) const {
8985 //-----------------------------------------------------------------------
8986 // FFCB1X - Routine calculating the potential for a row of positive
8987 // charges. The potential used is Re(Log(sin pi/s (z-z0))).
8988 //-----------------------------------------------------------------------
8989
8990 constexpr std::complex<double> icons(0., 1.);
8991 std::complex<double> ecompl;
8992 ex = ey = 0.;
8993 const double tx = Pi / m_sx;
8994 if (m_ynplay) {
8995 // With a y plane.
8996 for (unsigned int i = 0; i < m_nWires; ++i) {
8997 const auto& wire = m_w[i];
8998 const double xx = tx * (xpos - wire.x);
8999 const double yy = tx * (ypos - wire.y);
9000 if (!cnalso[i]) {
9001 ecompl = 0.;
9002 } else if (yy > 20.) {
9003 ecompl = -icons;
9004 } else if (yy < -20.) {
9005 ecompl = icons;
9006 } else {
9007 const std::complex<double> zz(xx, yy);
9008 const auto expzz = exp(2. * icons * zz);
9009 ecompl = icons * (expzz + 1.) / (expzz - 1.);
9010 }
9011 const double yymirr = tx * (ypos + wire.y - 2. * m_coplay);
9012 if (yymirr > 20.) {
9013 ecompl += icons;
9014 } else if (yymirr < -20.) {
9015 ecompl += -icons;
9016 } else {
9017 const std::complex<double> zzmirr(xx, yymirr);
9018 const auto expzzmirr = exp(2. * icons * zzmirr);
9019 ecompl += -icons * (expzzmirr + 1.) / (expzzmirr - 1.);
9020 }
9021 // Update the field.
9022 ex += wire.e * real(ecompl);
9023 ey -= wire.e * imag(ecompl);
9024 }
9025 } else {
9026 // Without a y plane.
9027 for (unsigned int i = 0; i < m_nWires; ++i) {
9028 if (!cnalso[i]) continue;
9029 const auto& wire = m_w[i];
9030 const double xx = tx * (xpos - wire.x);
9031 const double yy = tx * (ypos - wire.y);
9032 if (yy > 20.) {
9033 ecompl = -icons;
9034 } else if (yy < -20.) {
9035 ecompl = icons;
9036 } else {
9037 const std::complex<double> zz(xx, yy);
9038 const auto expzz = exp(2. * icons * zz);
9039 ecompl = icons * (expzz + 1.) / (expzz - 1.);
9040 }
9041 ex += wire.e * real(ecompl);
9042 ey -= wire.e * imag(ecompl);
9043 }
9044 }
9045 ex *= tx;
9046 ey *= tx;
9047}
9048
9049void ComponentAnalyticField::FieldAtWireB1Y(
9050 const double xpos, const double ypos, double& ex, double& ey,
9051 const std::vector<bool>& cnalso) const {
9052 //-----------------------------------------------------------------------
9053 // FFCB1Y - Routine calculating the potential for a row of positive
9054 // charges. The potential used is Re(Log(sinh pi/sy(z-z0)).
9055 //-----------------------------------------------------------------------
9056
9057 std::complex<double> ecompl;
9058 ex = ey = 0.;
9059 const double ty = Pi / m_sy;
9060 if (m_ynplax) {
9061 // With an x plane.
9062 for (unsigned int i = 0; i < m_nWires; ++i) {
9063 const auto& wire = m_w[i];
9064 const double xx = ty * (xpos - wire.x);
9065 const double yy = ty * (ypos - wire.y);
9066 if (!cnalso[i]) {
9067 ecompl = 0.;
9068 } else if (xx > 20.) {
9069 ecompl = 1.;
9070 } else if (xx < -20.) {
9071 ecompl = -1.;
9072 } else {
9073 const std::complex<double> zz(xx, yy);
9074 const auto expzz = exp(2. * zz);
9075 ecompl = (expzz + 1.) / (expzz - 1.);
9076 }
9077 const double xxmirr = ty * (xpos + wire.x - 2. * m_coplax);
9078 if (xxmirr > 20.) {
9079 ecompl -= 1.;
9080 } else if (xxmirr < -20.) {
9081 ecompl += 1.;
9082 } else {
9083 const std::complex<double> zzmirr(xxmirr, yy);
9084 const auto expzzmirr = exp(2. * zzmirr);
9085 ecompl -= (expzzmirr + 1.) / (expzzmirr - 1.);
9086 }
9087 ex += wire.e * real(ecompl);
9088 ey -= wire.e * imag(ecompl);
9089 }
9090 } else {
9091 // Without an x plane.
9092 for (unsigned int i = 0; i < m_nWires; ++i) {
9093 if (!cnalso[i]) continue;
9094 const auto& wire = m_w[i];
9095 const double xx = ty * (xpos - wire.x);
9096 const double yy = ty * (ypos - wire.y);
9097 if (xx > 20.) {
9098 ecompl = 1.;
9099 } else if (xx < -20.) {
9100 ecompl = -1.;
9101 } else {
9102 const std::complex<double> zz(xx, yy);
9103 const auto expzz = exp(2. * zz);
9104 ecompl = (expzz + 1.) / (expzz - 1.);
9105 }
9106 ex += wire.e * real(ecompl);
9107 ey -= wire.e * imag(ecompl);
9108 }
9109 }
9110 ex *= ty;
9111 ey *= ty;
9112}
9113
9114void ComponentAnalyticField::FieldAtWireB2X(
9115 const double xpos, const double ypos, double& ex, double& ey,
9116 const std::vector<bool>& cnalso) const {
9117 //-----------------------------------------------------------------------
9118 // FFCB2X - Routine calculating the potential for a row of alternating
9119 // + - charges. The potential used is re log(sin pi/sx (z-z0))
9120 //-----------------------------------------------------------------------
9121 constexpr std::complex<double> icons(0., 1.);
9122 ex = ey = 0.;
9123 const double tx = HalfPi / m_sx;
9124 // Loop over all wires.
9125 for (unsigned int i = 0; i < m_nWires; ++i) {
9126 const double xx = tx * (xpos - m_w[i].x);
9127 const double yy = tx * (ypos - m_w[i].y);
9128 const double xxneg = tx * (xpos - m_w[i].x - 2 * m_coplax);
9129 // Calculate the field in case there are no equipotential planes.
9130 std::complex<double> ecompl(0., 0.);
9131 if (cnalso[i] && fabs(yy) <= 20.) {
9132 const std::complex<double> zz(xx, yy);
9133 const std::complex<double> zzneg(xxneg, yy);
9134 ecompl = -m_b2sin[i] / (sin(zz) * sin(zzneg));
9135 } else if (fabs(yy) <= 20.) {
9136 const std::complex<double> zzneg(xxneg, yy);
9137 const auto expzzneg = exp(2. * icons * zzneg);
9138 ecompl = -icons * (expzzneg + 1.) / (expzzneg - 1.);
9139 }
9140 // Take care of planes at constant y.
9141 if (m_ynplay) {
9142 const double yymirr = tx * (ypos + m_w[i].y - 2 * m_coplay);
9143 if (fabs(yymirr) <= 20.) {
9144 const std::complex<double> zzmirr(xx, yymirr);
9145 const std::complex<double> zznmirr(xxneg, yymirr);
9146 ecompl += m_b2sin[i] / (sin(zzmirr) * sin(zznmirr));
9147 }
9148 }
9149 ex += m_w[i].e * real(ecompl);
9150 ey -= m_w[i].e * imag(ecompl);
9151 }
9152 ex *= tx;
9153 ey *= tx;
9154}
9155
9156void ComponentAnalyticField::FieldAtWireB2Y(
9157 const double xpos, const double ypos, double& ex, double& ey,
9158 const std::vector<bool>& cnalso) const {
9159 //-----------------------------------------------------------------------
9160 // FFCB2Y - Routine calculating the potential for a row of alternating
9161 // + - charges. The potential used is re log(sin pi/sx (z-z0))
9162 //-----------------------------------------------------------------------
9163 constexpr std::complex<double> icons(0., 1.);
9164 ex = ey = 0.;
9165 const double ty = HalfPi / m_sy;
9166 // Loop over all wires.
9167 for (unsigned int i = 0; i < m_nWires; ++i) {
9168 const double xx = ty * (xpos - m_w[i].x);
9169 const double yy = ty * (ypos - m_w[i].y);
9170 const double yyneg = ty * (ypos + m_w[i].y - 2 * m_coplay);
9171 // Calculate the field in case there are no equipotential planes.
9172 std::complex<double> ecompl(0., 0.);
9173 if (cnalso[i] && fabs(xx) <= 20.) {
9174 const std::complex<double> zz(xx, yy);
9175 const std::complex<double> zzneg(xx, yyneg);
9176 ecompl = icons * m_b2sin[i] / (sin(icons * zz) * sin(icons * zzneg));
9177 } else if (fabs(xx) <= 20.) {
9178 const std::complex<double> zzneg(xx, yyneg);
9179 const auto expzzneg = exp(2. * zzneg);
9180 ecompl = -(expzzneg + 1.) / (expzzneg - 1.);
9181 }
9182 // Take care of a plane at constant x.
9183 if (m_ynplax) {
9184 const double xxmirr = ty * (xpos + m_w[i].x - 2 * m_coplax);
9185 if (fabs(xxmirr) <= 20.) {
9186 const std::complex<double> zzmirr(xxmirr, yy);
9187 const std::complex<double> zznmirr(xxmirr, yyneg);
9188 ecompl -=
9189 icons * m_b2sin[i] / (sin(icons * zzmirr) * sin(icons * zznmirr));
9190 }
9191 }
9192 ex += m_w[i].e * real(ecompl);
9193 ey -= m_w[i].e * imag(ecompl);
9194 }
9195 ex *= ty;
9196 ey *= ty;
9197}
9198
9199void ComponentAnalyticField::FieldAtWireC10(
9200 const double xpos, const double ypos, double& ex, double& ey,
9201 const std::vector<bool>& cnalso) const {
9202 //-----------------------------------------------------------------------
9203 // FFCC10 - Routine returning the potential and electric field. It
9204 // calls the routines PH2 and E2SUM written by G.A.Erskine.
9205 //-----------------------------------------------------------------------
9206 constexpr std::complex<double> icons(0., 1.);
9207 std::complex<double> wsum(0., 0.);
9208 // Loop over the wires.
9209 for (unsigned int j = 0; j < m_nWires; ++j) {
9210 if (!cnalso[j]) continue;
9211 const auto& wire = m_w[j];
9212 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
9213 if (imag(zeta) > 15.) {
9214 wsum -= wire.e * icons;
9215 } else if (imag(zeta) < -15.) {
9216 wsum += wire.e * icons;
9217 } else {
9218 const auto zterm = Th1(zeta, m_p1, m_p2);
9219 wsum += wire.e * (zterm.second / zterm.first);
9220 }
9221 }
9222 ex = -real(-m_zmult * wsum);
9223 ey = imag(-m_zmult * wsum);
9224 if (m_mode == 0) ex -= m_c1;
9225 if (m_mode == 1) ey -= m_c1;
9226}
9227
9228void ComponentAnalyticField::FieldAtWireC2X(
9229 const double xpos, const double ypos, double& ex, double& ey,
9230 const std::vector<bool>& cnalso) const {
9231 //-----------------------------------------------------------------------
9232 // FFCC2X - Routine returning the potential and electric field in a
9233 // configuration with 2 x planes and y periodicity.
9234 //-----------------------------------------------------------------------
9235 constexpr std::complex<double> icons(0., 1.);
9236 // Initial values.
9237 std::complex<double> wsum1 = 0.;
9238 std::complex<double> wsum2 = 0.;
9239 for (unsigned int i = 0; i < m_nWires; ++i) {
9240 const auto& wire = m_w[i];
9241 if (cnalso[i]) {
9242 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
9243 if (imag(zeta) > 15.) {
9244 wsum1 -= wire.e * icons;
9245 } else if (imag(zeta) < -15.) {
9246 wsum1 += wire.e * icons;
9247 } else {
9248 const auto zterm = Th1(zeta, m_p1, m_p2);
9249 wsum1 += wire.e * (zterm.second / zterm.first);
9250 }
9251 }
9252 // Find the plane nearest to the wire.
9253 const double cx = m_coplax - m_sx * int(round((m_coplax - wire.x) / m_sx));
9254 // Mirror contribution.
9255 auto zeta =
9256 m_zmult * std::complex<double>(2. * cx - xpos - wire.x, ypos - wire.y);
9257 if (imag(zeta) > 15.) {
9258 wsum2 -= wire.e * icons;
9259 } else if (imag(zeta) < -15.) {
9260 wsum2 += wire.e * icons;
9261 } else {
9262 const auto zterm = Th1(zeta, m_p1, m_p2);
9263 wsum2 += wire.e * (zterm.second / zterm.first);
9264 }
9265 }
9266 // Convert the two contributions to a real field.
9267 ex = real(m_zmult * (wsum1 + wsum2));
9268 ey = -imag(m_zmult * (wsum1 - wsum2));
9269 // Constant correction terms.
9270 if (m_mode == 0) ex -= m_c1;
9271}
9272
9273void ComponentAnalyticField::FieldAtWireC2Y(
9274 const double xpos, const double ypos, double& ex, double& ey,
9275 const std::vector<bool>& cnalso) const {
9276 //-----------------------------------------------------------------------
9277 // FFCC2Y - Routine returning the potential and electric field in a
9278 // configuration with 2 y planes and x periodicity.
9279 //-----------------------------------------------------------------------
9280 const std::complex<double> icons(0., 1.);
9281 // Initial values.
9282 std::complex<double> wsum1 = 0.;
9283 std::complex<double> wsum2 = 0.;
9284 for (unsigned int i = 0; i < m_nWires; ++i) {
9285 const auto& wire = m_w[i];
9286 if (cnalso[i]) {
9287 // Compute the direct contribution.
9288 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
9289 if (imag(zeta) > 15.) {
9290 wsum1 -= wire.e * icons;
9291 } else if (imag(zeta) < -15.) {
9292 wsum1 += wire.e * icons;
9293 } else {
9294 const auto zterm = Th1(zeta, m_p1, m_p2);
9295 wsum1 += wire.e * (zterm.second / zterm.first);
9296 }
9297 }
9298 // Find the plane nearest to the wire.
9299 const double cy = m_coplay - m_sy * int(round((m_coplay - wire.y) / m_sy));
9300 // Mirror contribution from the y plane.
9301 auto zeta =
9302 m_zmult * std::complex<double>(xpos - wire.x, 2 * cy - ypos - wire.y);
9303 if (imag(zeta) > 15.) {
9304 wsum2 -= wire.e * icons;
9305 } else if (imag(zeta) < -15.) {
9306 wsum2 += wire.e * icons;
9307 } else {
9308 const auto zterm = Th1(zeta, m_p1, m_p2);
9309 wsum2 += wire.e * (zterm.second / zterm.first);
9310 }
9311 }
9312 // Convert the two contributions to a real field.
9313 ex = real(m_zmult * (wsum1 - wsum2));
9314 ey = -imag(m_zmult * (wsum1 + wsum2));
9315 // Constant correction terms.
9316 if (m_mode == 1) ey -= m_c1;
9317}
9318
9319void ComponentAnalyticField::FieldAtWireC30(
9320 const double xpos, const double ypos, double& ex, double& ey,
9321 const std::vector<bool>& cnalso) const {
9322 //-----------------------------------------------------------------------
9323 // FFCC30 - Routine returning the potential and electric field in a
9324 // configuration with 2 y and 2 x planes.
9325 //-----------------------------------------------------------------------
9326 constexpr std::complex<double> icons(0., 1.);
9327 // Initial values.
9328 std::complex<double> wsum1 = 0.;
9329 std::complex<double> wsum2 = 0.;
9330 std::complex<double> wsum3 = 0.;
9331 std::complex<double> wsum4 = 0.;
9332 for (unsigned int i = 0; i < m_nWires; ++i) {
9333 const auto& wire = m_w[i];
9334 if (cnalso[i]) {
9335 auto zeta = m_zmult * std::complex<double>(xpos - wire.x, ypos - wire.y);
9336 if (imag(zeta) > 15.) {
9337 wsum1 -= wire.e * icons;
9338 } else if (imag(zeta) < -15.) {
9339 wsum1 += wire.e * icons;
9340 } else {
9341 const auto zterm = Th1(zeta, m_p1, m_p2);
9342 wsum1 += wire.e * (zterm.second / zterm.first);
9343 }
9344 }
9345 // Find the plane nearest to the wire.
9346 const double cx = m_coplax - m_sx * int(round((m_coplax - wire.x) / m_sx));
9347 // Mirror contribution from the x plane.
9348 auto zeta =
9349 m_zmult * std::complex<double>(2. * cx - xpos - wire.x, ypos - wire.y);
9350 if (imag(zeta) > 15.) {
9351 wsum2 -= wire.e * icons;
9352 } else if (imag(zeta) < -15.) {
9353 wsum2 += wire.e * icons;
9354 } else {
9355 const auto zterm = Th1(zeta, m_p1, m_p2);
9356 wsum2 += wire.e * (zterm.second / zterm.first);
9357 }
9358 // Find the plane nearest to the wire.
9359 const double cy = m_coplay - m_sy * int(round((m_coplay - wire.y) / m_sy));
9360 // Mirror contribution from the x plane.
9361 zeta =
9362 m_zmult * std::complex<double>(xpos - wire.x, 2. * cy - ypos - wire.y);
9363 if (imag(zeta) > 15.) {
9364 wsum3 -= wire.e * icons;
9365 } else if (imag(zeta) < -15.) {
9366 wsum3 += wire.e * icons;
9367 } else {
9368 const auto zterm = Th1(zeta, m_p1, m_p2);
9369 wsum3 += wire.e * (zterm.second / zterm.first);
9370 }
9371 // Mirror contribution from both the x and the y plane.
9372 zeta = m_zmult * std::complex<double>(2. * cx - xpos - wire.x,
9373 2. * cy - ypos - wire.y);
9374 if (imag(zeta) > 15.) {
9375 wsum4 -= wire.e * icons;
9376 } else if (imag(zeta) < -15.) {
9377 wsum4 += wire.e * icons;
9378 } else {
9379 const auto zterm = Th1(zeta, m_p1, m_p2);
9380 wsum4 += wire.e * (zterm.second / zterm.first);
9381 }
9382 }
9383 // Convert the two contributions to a real field.
9384 ex = real(m_zmult * (wsum1 + wsum2 - wsum3 - wsum4));
9385 ey = -imag(m_zmult * (wsum1 - wsum2 + wsum3 - wsum4));
9386}
9387
9388void ComponentAnalyticField::FieldAtWireD10(
9389 const double xpos, const double ypos, double& ex, double& ey,
9390 const std::vector<bool>& cnalso) const {
9391 //-----------------------------------------------------------------------
9392 // FFCD10 - Subroutine performing the actual field calculations for a
9393 // cell which has a one circular plane and some wires.
9394 //-----------------------------------------------------------------------
9395 ex = ey = 0.;
9396 // Set the complex position coordinates.
9397 const std::complex<double> zpos(xpos, ypos);
9398 // Loop over all wires.
9399 for (unsigned int i = 0; i < m_nWires; ++i) {
9400 const auto& wire = m_w[i];
9401 // Set the complex version of the wire-coordinate for simplicity.
9402 const std::complex<double> zi(wire.x, wire.y);
9403 // First the case that the wire has to be taken fully.
9404 if (cnalso[i]) {
9405 const std::complex<double> wi =
9406 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
9407 ex += wire.e * real(wi);
9408 ey += wire.e * imag(wi);
9409 } else {
9410 const std::complex<double> wi = zi / (m_cotube2 - conj(zpos) * zi);
9411 ex += wire.e * real(wi);
9412 ey += wire.e * imag(wi);
9413 }
9414 }
9415}
9416
9417void ComponentAnalyticField::FieldAtWireD20(
9418 const double xpos, const double ypos, double& ex, double& ey,
9419 const std::vector<bool>& cnalso) const {
9420 //-----------------------------------------------------------------------
9421 // FFCD20 - Subroutine performing the actual field calculations for a
9422 // cell which has a tube and phi periodicity.
9423 //-----------------------------------------------------------------------
9424 ex = ey = 0.;
9425 // Set the complex position coordinates.
9426 const std::complex<double> zpos(xpos, ypos);
9427 for (unsigned int i = 0; i < m_nWires; ++i) {
9428 const auto& wire = m_w[i];
9429 // Set the complex version of the wire-coordinate for simplicity.
9430 const std::complex<double> zi(wire.x, wire.y);
9431 if (cnalso[i]) {
9432 // Case of the wire which is not in the centre.
9433 if (std::abs(zi) > wire.r) {
9434 const std::complex<double> wi =
9435 double(m_mtube) * pow(conj(zpos), m_mtube - 1) *
9436 (1. / conj(pow(zpos, m_mtube) - pow(zi, m_mtube)) +
9437 pow(zi, m_mtube) /
9438 (pow(m_cotube, 2 * m_mtube) - pow(conj(zpos) * zi, m_mtube)));
9439 ex += wire.e * real(wi);
9440 ey += wire.e * imag(wi);
9441 } else {
9442 const std::complex<double> wi =
9443 1. / conj(zpos - zi) + zi / (m_cotube2 - conj(zpos) * zi);
9444 ex += wire.e * real(wi);
9445 ey += wire.e * imag(wi);
9446 }
9447 } else {
9448 if (abs(zi) > wire.r) {
9449 const std::complex<double> wi =
9450 double(m_mtube) * pow(conj(zpos), m_mtube - 1) *
9451 (pow(zi, m_mtube) /
9452 (pow(m_cotube, 2 * m_mtube) - pow(conj(zpos) * zi, m_mtube)));
9453 ex += wire.e * real(wi);
9454 ey += wire.e * imag(wi);
9455 } else {
9456 const std::complex<double> wi = zi / (m_cotube2 - conj(zpos) * zi);
9457 ex += wire.e * real(wi);
9458 ey += wire.e * imag(wi);
9459 }
9460 }
9461 }
9462}
9463
9464void ComponentAnalyticField::FieldAtWireD30(
9465 const double xpos, const double ypos, double& ex, double& ey,
9466 const std::vector<bool>& cnalso) const {
9467 //-----------------------------------------------------------------------
9468 // FFCD30 - Subroutine performing the actual field calculations for a
9469 // cell which has a polygon as tube and some wires.
9470 //-----------------------------------------------------------------------
9471 ex = ey = 0.;
9472 // Get the mapping of the position.
9473 std::complex<double> wpos, wdpos;
9474 ConformalMap(std::complex<double>(xpos, ypos) / m_cotube, wpos, wdpos);
9475 // Loop over all wires.
9476 for (unsigned int i = 0; i < m_nWires; ++i) {
9477 if (cnalso[i]) {
9478 // Full contribution.
9479 const std::complex<double> whelp =
9480 wdpos * (1. - pow(abs(wmap[i]), 2)) /
9481 ((wpos - wmap[i]) * (1. - conj(wmap[i]) * wpos));
9482 ex += m_w[i].e * real(whelp);
9483 ey -= m_w[i].e * imag(whelp);
9484 } else {
9485 // Mirror charges only.
9486 const std::complex<double> whelp =
9487 wdpos * conj(wmap[i]) / (1. - conj(wmap[i]) * wpos);
9488 ex += m_w[i].e * real(whelp);
9489 ey -= m_w[i].e * imag(whelp);
9490 }
9491 }
9492 ex /= m_cotube;
9493 ey /= m_cotube;
9494}
9495
9496bool ComponentAnalyticField::SagDetailed(
9497 const Wire& wire, const std::vector<double>& xMap,
9498 const std::vector<double>& yMap,
9499 const std::vector<std::vector<double> >& fxMap,
9500 const std::vector<std::vector<double> >& fyMap, std::vector<double>& csag,
9501 std::vector<double>& xsag, std::vector<double>& ysag) const {
9502 //-----------------------------------------------------------------------
9503 // OPTSAG - Computes the wire sag due to electrostatic and gravitational
9504 // forces, using a Runge-Kutta-Nystrom multiple shoot method,
9505 // where the intermediate conditions are imposed through a
9506 // Broyden rank-1 zero search.
9507 //-----------------------------------------------------------------------
9508
9509 csag.clear();
9510 xsag.clear();
9511 ysag.clear();
9512 const unsigned int np = m_nSteps * (m_nShots + 1);
9513 // Compute the step width.
9514 const double h = wire.u / np;
9515 // Compute expected maximum sag, constant-force approximation.
9516 std::array<double, 2> xst = {0., 0.};
9517 std::array<double, 2> dxst = {0., 0.};
9518 double fxmean = 0.;
9519 double fymean = 0.;
9520 // Loop over the whole wire.
9521 for (unsigned int i = 0; i <= np; ++i) {
9522 const double z = i * h;
9523 std::array<double, 2> force = {0., 0.};
9524 if (!GetForceRatio(wire, z, xst, dxst, force, xMap, yMap, fxMap, fyMap)) {
9525 std::cerr << m_className << "::SagDetailed:\n"
9526 << " Wire at nominal position outside scanning area.\n";
9527 return false;
9528 }
9529 fxmean += force[0];
9530 fymean += force[1];
9531 }
9532 const double u2 = wire.u * wire.u;
9533 // Compute expected sag.
9534 const double s = u2 / (8. * (1. + np));
9535 double sagx0 = -fxmean * s;
9536 double sagy0 = -fymean * s;
9537 if (m_debug) {
9538 std::cout << m_className << "::SagDetailed: Parabolic sag.\n";
9539 std::printf(" dx = %12.5e, dy = %12.5e [cm]\n", sagx0, sagy0);
9540 }
9541 // Starting position: parabolic sag.
9542 std::vector<double> xx(4 * m_nShots + 2);
9543 // Derivative first point.
9544 xx[0] = 4 * sagx0 / wire.u;
9545 xx[1] = 4 * sagy0 / wire.u;
9546 // Intermediate points, both position and derivative.
9547 for (unsigned int i = 1; i <= m_nShots; ++i) {
9548 // Position along the wire.
9549 const double z = -0.5 * wire.u + i * m_nSteps * h;
9550 const unsigned int k = 4 * i - 2;
9551 // Deflection.
9552 const double f = 1. - 4 * z * z / u2;
9553 xx[k] = sagx0 * f;
9554 xx[k + 1] = sagy0 * f;
9555 // Derivative.
9556 const double fp = -8 * z / u2;
9557 xx[k + 2] = sagx0 * fp;
9558 xx[k + 3] = sagy0 * fp;
9559 }
9560 // Search for solution.
9561 if (!FindZeroes(wire, h, xx, xMap, yMap, fxMap, fyMap)) {
9562 std::cerr << m_className << "::SagDetailed:\n"
9563 << " Failed to solve the differential equation for the sag.\n";
9564 return false;
9565 }
9566 // And return the detailed solution, first the starting point.
9567 csag.assign(np + 1, 0.);
9568 xsag.assign(np + 1, 0.);
9569 ysag.assign(np + 1, 0.);
9570 csag[0] = -0.5 * wire.u;
9571 double coor = -0.5 * wire.u;
9572 for (unsigned int i = 0; i <= m_nShots; ++i) {
9573 // Set the starting value and starting derivative.
9574 if (i == 0) {
9575 xst[0] = 0;
9576 xst[1] = 0;
9577 dxst[0] = xx[0];
9578 dxst[1] = xx[1];
9579 } else {
9580 xst[0] = xx[4 * i - 2];
9581 xst[1] = xx[4 * i - 1];
9582 dxst[0] = xx[4 * i];
9583 dxst[1] = xx[4 * i + 1];
9584 }
9585 // Store the intermediate values.
9586 for (unsigned int j = 1; j <= m_nSteps; ++j) {
9587 StepRKN(wire, h, coor, xst, dxst, xMap, yMap, fxMap, fyMap);
9588 csag[i * m_nSteps + j] = coor;
9589 xsag[i * m_nSteps + j] = xst[0];
9590 ysag[i * m_nSteps + j] = xst[1];
9591 }
9592 }
9593 // Seems to have worked.
9594 return true;
9595}
9596
9597bool ComponentAnalyticField::GetForceRatio(
9598 const Wire& wire, const double /*coor*/, const std::array<double, 2>& bend,
9599 const std::array<double, 2>& /*dbend*/, std::array<double, 2>& f,
9600 const std::vector<double>& xMap, const std::vector<double>& yMap,
9601 const std::vector<std::vector<double> >& fxMap,
9602 const std::vector<std::vector<double> >& fyMap) const {
9603 //-----------------------------------------------------------------------
9604 // OPTSTP - Returns the electrostatic and gravitational force divided
9605 // by the stretching force acting on a wire at position COOR,
9606 // with deflection BEND and bending derivative DBEND.
9607 //-----------------------------------------------------------------------
9608
9609 // TODO: COOR and DBEND don't seem to be used?
9610
9611 // Initialise the forces.
9612 f.fill(0.);
9613
9614 const double xw = wire.x + bend[0];
9615 const double yw = wire.y + bend[1];
9616 if (m_useElectrostaticForce) {
9617 // Electrostatic force.
9618 if (xMap.empty() || yMap.empty() || fxMap.empty() || fyMap.empty()) {
9619 return false;
9620 }
9621 // In case extrapolation is not permitted, check range.
9622 if (!m_extrapolateForces) {
9623 if ((xMap.front() - xw) * (xw - xMap.back()) < 0 ||
9624 (yMap.front() - yw) * (yw - yMap.back()) < 0) {
9625 return false;
9626 }
9627 }
9628 // Interpolation order.
9629 constexpr int order = 2;
9630 // Electrostatic force: interpolate the table, first along the y-lines.
9631 const unsigned int nX = xMap.size();
9632 const unsigned int nY = yMap.size();
9633 std::vector<double> xaux(nX, 0.);
9634 std::vector<double> yaux(nX, 0.);
9635 for (unsigned int i = 0; i < nX; ++i) {
9636 xaux[i] = Numerics::Divdif(fxMap[i], yMap, nY, yw, order);
9637 yaux[i] = Numerics::Divdif(fyMap[i], yMap, nY, yw, order);
9638 }
9639 // Then along the x-lines.
9640 f[0] += Numerics::Divdif(xaux, xMap, nX, xw, order);
9641 f[1] += Numerics::Divdif(yaux, xMap, nX, xw, order);
9642 }
9643 // Add the gravity term.
9644 if (m_useGravitationalForce) {
9645 // Mass per unit length [kg / cm].
9646 const double m = 1.e-3 * wire.density * Pi * wire.r * wire.r;
9647 f[0] -= m_down[0] * m * GravitationalAcceleration;
9648 f[1] -= m_down[1] * m * GravitationalAcceleration;
9649 }
9650 // Divide by the stretching force.
9651 const double s = 1000. / (GravitationalAcceleration * wire.tension);
9652 f[0] *= s;
9653 f[1] *= s;
9654 return true;
9655}
9656
9657// Subroutine subprograms RRKNYS and DRKNYS advance the solution of the system
9658// of n >= 1 second-order differential equations
9659// by a single step of length h in the independent variable x.
9660bool ComponentAnalyticField::StepRKN(
9661 const Wire& wire, const double h, double& x, std::array<double, 2>& y,
9662 std::array<double, 2>& yp, const std::vector<double>& xMap,
9663 const std::vector<double>& yMap,
9664 const std::vector<std::vector<double> >& fxMap,
9665 const std::vector<std::vector<double> >& fyMap) const {
9666 constexpr double r2 = 1. / 2.;
9667 constexpr double r6 = 1. / 6.;
9668 constexpr double r8 = 1. / 8.;
9669 if (h == 0) return true;
9670 const double h2 = r2 * h;
9671 const double h6 = r6 * h;
9672 const double hh2 = h * h2;
9673 const double hh6 = h * h6;
9674 const double hh8 = r8 * h * h;
9675
9676 constexpr unsigned int n = 2;
9677 std::array<std::array<double, n>, 6> w;
9678 if (!GetForceRatio(wire, x, y, yp, w[0], xMap, yMap, fxMap, fyMap)) {
9679 return false;
9680 }
9681 for (unsigned int j = 0; j < n; ++j) {
9682 w[3][j] = y[j] + h2 * yp[j];
9683 w[4][j] = w[3][j] + hh8 * w[0][j];
9684 w[5][j] = yp[j] + h2 * w[0][j];
9685 }
9686 const double xh2 = x + h2;
9687 if (!GetForceRatio(wire, xh2, w[4], w[5], w[1], xMap, yMap, fxMap, fyMap)) {
9688 return false;
9689 }
9690 for (unsigned int j = 0; j < n; ++j) {
9691 w[5][j] = yp[j] + h2 * w[1][j];
9692 w[0][j] = w[0][j] + w[1][j];
9693 w[1][j] = w[0][j] + w[1][j];
9694 }
9695 if (!GetForceRatio(wire, xh2, w[4], w[5], w[2], xMap, yMap, fxMap, fyMap)) {
9696 return false;
9697 }
9698 for (unsigned int j = 0; j < n; ++j) {
9699 w[3][j] = w[3][j] + h2 * yp[j];
9700 w[4][j] = w[3][j] + hh2 * w[2][j];
9701 w[5][j] = yp[j] + h * w[2][j];
9702 w[0][j] = w[0][j] + w[2][j];
9703 w[1][j] = w[1][j] + 2 * w[2][j];
9704 }
9705 const double xh = x + h;
9706 if (!GetForceRatio(wire, xh, w[4], w[5], w[2], xMap, yMap, fxMap, fyMap)) {
9707 return false;
9708 }
9709 for (unsigned int j = 0; j < n; ++j) {
9710 y[j] = w[3][j] + hh6 * w[0][j];
9711 yp[j] += h6 * (w[1][j] + w[2][j]);
9712 }
9713 x = xh;
9714 return true;
9715}
9716
9717bool ComponentAnalyticField::FindZeroes(
9718 const Wire& wire, const double h, std::vector<double>& x,
9719 const std::vector<double>& xMap, const std::vector<double>& yMap,
9720 const std::vector<std::vector<double> >& fxMap,
9721 const std::vector<std::vector<double> >& fyMap) const {
9722 //-----------------------------------------------------------------------
9723 // OPTZRO - Tries to find zeroes of a set of functions F. Uses the
9724 // Broyden rank-1 update variant of an n-dimensional Newton-
9725 // Raphson zero search in most steps, except every 5th step
9726 // and whenever the step length update becomes less than 0.5,
9727 // when a new derivative is computed.
9728 //-----------------------------------------------------------------------
9729
9730 if (x.empty()) {
9731 std::cerr << m_className << "::FindZeroes: Empty vector.\n";
9732 return false;
9733 }
9734 const unsigned int n = x.size();
9735 // Initial deviation.
9736 std::vector<double> fold(n, 0.);
9737 if (!Trace(wire, h, x, fold, xMap, yMap, fxMap, fyMap)) {
9738 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n "
9739 << "Initial position outside scanning area.\n";
9740 return false;
9741 }
9742 double fnorml =
9743 std::inner_product(fold.begin(), fold.end(), fold.begin(), 0.);
9744 // Debugging output for initial situation.
9745 constexpr unsigned int nbsmax = 10;
9746 constexpr unsigned int nitmax = 20;
9747 constexpr double eps = 1.e-4;
9748 constexpr double epsx = 1.e-4;
9749 constexpr double epsf = 1.e-4;
9750 if (m_debug) {
9751 std::cout << m_className << "::FindZeroes: Start of zero search.\n"
9752 << " Number of parameters: " << n << "\n"
9753 << " Maximum bisections: " << nbsmax << "\n"
9754 << " Maximum iterations: " << nitmax << "\n"
9755 << " Epsilon differentiation: " << eps << "\n"
9756 << " Required location change: " << epsx << "\n"
9757 << " Required function norm: " << epsf << "\n"
9758 << " Initial function norm: " << sqrt(fnorml) << "\n"
9759 << " Parameter Value Function\n";
9760 for (unsigned int i = 0; i < n; ++i) {
9761 std::printf(" %9d %12.5e %12.5e\n", i, x[i], fold[i]);
9762 }
9763 }
9764 // Derivative matrix.
9765 std::vector<std::vector<double> > b(n, std::vector<double>(n, 0.));
9766 std::vector<std::vector<double> > bb(n, std::vector<double>(n, 0.));
9767 // Flag whether the matrix needs to be recomputed.
9768 bool updateMatrix = true;
9769 // Count function calls.
9770 unsigned int nCalls = 0;
9771 bool converged = false;
9772 for (unsigned int iter = 0; iter < nitmax; ++iter) {
9773 // If needed, (re-)compute the derivative matrix.
9774 if (updateMatrix) {
9775 std::vector<double> f1(n, 0.);
9776 std::vector<double> f2(n, 0.);
9777 for (unsigned int i = 0; i < n; ++i) {
9778 const double epsdif = eps * (1. + std::abs(x[i]));
9779 x[i] += 0.5 * epsdif;
9780 if (!Trace(wire, h, x, f1, xMap, yMap, fxMap, fyMap)) {
9781 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n "
9782 << "Differential matrix requires a point "
9783 << "outside scanning area.\n";
9784 return false;
9785 }
9786 x[i] -= epsdif;
9787 if (!Trace(wire, h, x, f2, xMap, yMap, fxMap, fyMap)) {
9788 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n "
9789 << "Differential matrix requires a point "
9790 << "outside scanning area.\n";
9791 return false;
9792 }
9793 x[i] += 0.5 * epsdif;
9794 for (unsigned int j = 0; j < n; ++j) {
9795 b[j][i] = (f1[j] - f2[j]) / epsdif;
9796 }
9797 }
9798 nCalls += 2 * n;
9799 updateMatrix = false;
9800 }
9801 if (m_debug) {
9802 std::cout << " Start of iteration " << iter << "\n";
9803 for (unsigned int i = 0; i < m_nShots; ++i) {
9804 const unsigned int k = 4 * i + 2;
9805 std::printf(" x = %12.5e, y = %12.5e\n", x[k], x[k + 1]);
9806 }
9807 }
9808 // Find the correction vector to 0th order.
9809 std::vector<double> dx = fold;
9810 bb = b;
9811 if (Numerics::CERNLIB::deqn(n, bb, dx) != 0) {
9812 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n"
9813 << " Solving the update equation failed.\n";
9814 break;
9815 }
9816 if (m_debug) {
9817 for (unsigned int i = 0; i < m_nShots; ++i) {
9818 const unsigned int k = 4 * i + 2;
9819 std::printf(" dx = %12.5e, dy = %12.5e\n", dx[k], dx[k + 1]);
9820 }
9821 }
9822 // Scale the correction vector to improve FNORM, AUX3: f.
9823 double scale = 1.;
9824 double fnorm = 2 * fnorml;
9825 std::vector<double> xnew(n, 0.);
9826 std::vector<double> fnew(n, 0.);
9827 for (unsigned int kbs = 0; kbs < nbsmax; ++kbs) {
9828 for (unsigned int i = 0; i < n; ++i) {
9829 xnew[i] = x[i] - scale * dx[i];
9830 }
9831 if (!Trace(wire, h, xnew, fnew, xMap, yMap, fxMap, fyMap)) {
9832 std::cerr
9833 << m_className << "::FindZeroes: Zero search stopped.\n "
9834 << "Step update leads to a point outside the scanning area.\n";
9835 return false;
9836 }
9837 ++nCalls;
9838 fnorm = std::inner_product(fnew.begin(), fnew.end(), fnew.begin(), 0.);
9839 if (fnorm <= fnorml) {
9840 if (m_debug) std::cout << " Scaling factor: " << scale << "\n";
9841 break;
9842 }
9843 scale *= 0.5;
9844 }
9845 if (fnorm > fnorml) {
9846 std::cerr << m_className << "::FindZeroes: Zero search stopped.\n "
9847 << "Bisection search for scaling factor did not converge.\n";
9848 break;
9849 }
9850 // Update the estimate.
9851 std::vector<double> df(n, 0.);
9852 for (unsigned int i = 0; i < n; ++i) {
9853 dx[i] = xnew[i] - x[i];
9854 x[i] = xnew[i];
9855 df[i] = fnew[i] - fold[i];
9856 fold[i] = fnew[i];
9857 }
9858 double xnorm = std::inner_product(x.begin(), x.end(), x.begin(), 0.);
9859 double dxnorm = std::inner_product(dx.begin(), dx.end(), dx.begin(), 0.);
9860 double dfnorm = std::inner_product(df.begin(), df.end(), df.begin(), 0.);
9861 // Debugging output to show current status.
9862 if (m_debug) {
9863 std::cout << " After this iteration...\n";
9864 std::printf(" Norm and change of position: %12.5e %12.5e\n",
9865 sqrt(xnorm), sqrt(dxnorm));
9866 std::printf(" Norm and change of function: %12.5e %12.5e\n",
9867 sqrt(fnorm), sqrt(dfnorm));
9868 }
9869 // See whether convergence has been achieved.
9870 if (sqrt(dxnorm) < epsx * sqrt(xnorm)) {
9871 if (m_debug) {
9872 std::cout << " Positional convergence criterion is satisfied.\n";
9873 }
9874 converged = true;
9875 break;
9876 } else if (sqrt(fnorm) < epsf) {
9877 if (m_debug) {
9878 std::cout << " Function value convergence criterion is satisfied.\n";
9879 }
9880 converged = true;
9881 break;
9882 }
9883 // Update the difference.
9884 fnorml = fnorm;
9885 if (scale > 0.4 && iter != 5 * (iter / 5)) {
9886 // If the scaling factor is small, then update (rank-1 Broyden).
9887 if (m_debug) std::cout << " Performing a Broyden rank-1 update.\n";
9888 // Compute the "df - B dx" term.
9889 std::vector<double> corr(n, 0.);
9890 for (unsigned int i = 0; i < n; ++i) {
9891 corr[i] = df[i];
9892 for (unsigned int j = 0; j < n; ++j) {
9893 corr[i] -= b[i][j] * dx[j];
9894 }
9895 }
9896 // Update the matrix.
9897 for (unsigned int i = 0; i < n; ++i) {
9898 for (unsigned int j = 0; j < n; ++j) {
9899 b[i][j] += corr[i] * dx[j] / dxnorm;
9900 }
9901 }
9902 } else {
9903 // Otherwise, recompute the differential.
9904 if (m_debug) std::cout << " Recomputing the covariance matrix.\n";
9905 updateMatrix = true;
9906 }
9907 }
9908 if (!converged) {
9909 std::cerr << m_className << "::FindZeroes: Search did not converge.\n";
9910 }
9911 if (m_debug) {
9912 std::vector<double> f(n, 0.);
9913 Trace(wire, h, x, f, xMap, yMap, fxMap, fyMap);
9914 ++nCalls;
9915 std::cout << " Final values:\n"
9916 << " Parameter Value Function\n";
9917 for (unsigned int i = 0; i < n; ++i) {
9918 std::printf(" %9d %12.5e %12.5e\n", i, x[i], f[i]);
9919 }
9920 std::cout << " Total number of function calls: " << nCalls << "\n";
9921 }
9922 return converged;
9923}
9924
9925bool ComponentAnalyticField::Trace(
9926 const Wire& wire, const double h, const std::vector<double>& xx,
9927 std::vector<double>& delta, const std::vector<double>& xMap,
9928 const std::vector<double>& yMap,
9929 const std::vector<std::vector<double> >& fxMap,
9930 const std::vector<std::vector<double> >& fyMap) const {
9931 //-----------------------------------------------------------------------
9932 // OPTSHT - Auxiliary routine for the wire sag routines which computes
9933 // for a given set of positions and derivatives the next set
9934 // which is used by OPTZRO to match the sections. Uses a
9935 // 2nd order Runge-Kutta-Nystrom integration routine (D203).
9936 //-----------------------------------------------------------------------
9937
9938 delta.assign(xx.size(), 0.);
9939 // For the starting set in XX, compute the next round.
9940 double z = -0.5 * wire.u;
9941 std::array<double, 2> xst = {0., 0.};
9942 std::array<double, 2> dxst = {xx[0], xx[1]};
9943 for (unsigned int i = 0; i <= m_nShots; ++i) {
9944 const unsigned int k = 4 * i;
9945 // Set the starting value and starting derivative.
9946 if (i > 0) {
9947 xst = {xx[k - 2], xx[k - 1]};
9948 dxst = {xx[k], xx[k + 1]};
9949 }
9950 // Compute the end value and end derivative.
9951 for (unsigned int j = 0; j < m_nSteps; ++j) {
9952 if (!StepRKN(wire, h, z, xst, dxst, xMap, yMap, fxMap, fyMap)) {
9953 return false;
9954 }
9955 }
9956 // Store the differences as function value.
9957 if (i < m_nShots) {
9958 delta[k] = xst[0] - xx[k + 2];
9959 delta[k + 1] = xst[1] - xx[k + 3];
9960 delta[k + 2] = dxst[0] - xx[k + 4];
9961 delta[k + 3] = dxst[1] - xx[k + 5];
9962 } else {
9963 delta[k] = xst[0];
9964 delta[k + 1] = xst[1];
9965 }
9966 }
9967 return true;
9968}
9969
9970bool ComponentAnalyticField::SetupDipoleTerms() {
9971
9972 //-----------------------------------------------------------------------
9973 // SETDIP - Subroutine computing coefficients for the dipole terms in
9974 // cells without periodicities.
9975 //-----------------------------------------------------------------------
9976
9977 // Parameters.
9978 constexpr double epsp = 1.e-3;
9979 constexpr double epsa = 1.e-3;
9980
9981 const unsigned int nWires = m_w.size();
9982 // Initial dipole moments.
9983 std::vector<double> phi2(nWires, 0.);
9984 m_cosph2.assign(nWires, 1.);
9985 m_sinph2.assign(nWires, 0.);
9986 m_amp2.assign(nWires, 0.);
9987
9988 // Iterate until the dipole terms have converged.
9989 std::vector<double> phit2(nWires, 0.);
9990 std::vector<double> ampt2(nWires, 0.);
9991 constexpr unsigned int nMaxIter = 10;
9992 for (unsigned int iter = 0; iter < nMaxIter; ++iter) {
9993 if (m_debug) {
9994 std::cout << "Iteration " << iter << "/" << nMaxIter << "\n"
9995 << " Wire correction angle [deg] amplitude\n";
9996 }
9997 // Loop over the wires.
9998 for (unsigned int iw = 0; iw < nWires; ++iw) {
9999 const double xw = m_w[iw].x;
10000 const double yw = m_w[iw].y;
10001 const double rw = m_w[iw].r;
10002 // Set the radius of the wire to 0.
10003 m_w[iw].r = 0.;
10004 // Loop around the wire.
10005 constexpr unsigned int nAngles = 20;
10006 std::vector<double> angle(nAngles, 0.);
10007 std::vector<double> volt(nAngles, 0.);
10008 constexpr double rmult = 1.;
10009 const double r = rw * rmult;
10010 int status = 0;
10011 for (unsigned int i = 0; i < nAngles; ++i) {
10012 angle[i] = TwoPi * (i + 1.) / nAngles;
10013 const double x = xw + r * cos(angle[i]);
10014 const double y = yw + r * sin(angle[i]);
10015 double ex = 0., ey = 0., ez = 0.;
10016 status = Field(x, y, 0., ex, ey, ez, volt[i], true);
10017 if (status != 0) {
10018 std::cerr << "Unexpected status code; computation stopped.\n";
10019 break;
10020 }
10021 volt[i] -= m_w[iw].v;
10022 }
10023 // Restore wire radius.
10024 m_w[iw].r = rw;
10025 if (status != 0) continue;
10026 // Determine the dipole term.
10027 double ampdip = 0., phidip = 0.;
10028 FitDipoleMoment(angle, volt, ampdip, phidip, false);
10029 // Store the parameters, removing the radial dependence.
10030 phit2[iw] = phidip;
10031 ampt2[iw] = ampdip * r;
10032 if (m_debug) {
10033 std::printf(" %3d %10.3f %12.5e\n",
10034 iw, RadToDegree * phit2[iw], ampt2[iw]);
10035 }
10036 }
10037 // Transfer to the arrays where the dipole moments have impact
10038 bool reiter = false;
10039 if (m_debug) std::cout << " Wire new angle [deg] amplitude\n";
10040 for (unsigned int iw = 0; iw < nWires; ++iw) {
10041 // See whether we need further refinements.
10042 bool converged = true;
10043 if (std::abs(phi2[iw]) > epsp * (1. + std::abs(phi2[iw])) ||
10044 std::abs(m_amp2[iw]) > epsa * (1. + std::abs(m_amp2[iw]))) {
10045 reiter = true;
10046 converged = false;
10047 }
10048 // Add the new term to the existing one.
10049 const double s0 = m_sinph2[iw] * m_amp2[iw] + sin(phit2[iw]) * ampt2[iw];
10050 const double c0 = m_cosph2[iw] * m_amp2[iw] + cos(phit2[iw]) * ampt2[iw];
10051 phi2[iw] = atan2(s0, c0);
10052 m_cosph2[iw] = cos(phi2[iw]);
10053 m_sinph2[iw] = sin(phi2[iw]);
10054 const double s1 = m_sinph2[iw] * m_amp2[iw] + sin(phit2[iw]) * ampt2[iw];
10055 const double c1 = m_cosph2[iw] * m_amp2[iw] + cos(phit2[iw]) * ampt2[iw];
10056 m_amp2[iw] = sqrt(s1 * s1 + c1 * c1);
10057 if (m_debug) {
10058 std::printf(" %3d %10.3f %12.5e %s\n",
10059 iw, RadToDegree * phi2[iw], m_amp2[iw],
10060 converged ? "CONVERGED" : "");
10061 }
10062 }
10063 // Next iteration?
10064 if (!reiter) return true;
10065 }
10066 // Maximum number of iterations exceeded.
10067 std::cerr << m_className << "::SetupDipoleTerms:\n"
10068 << " Maximum number of dipole iterations exceeded "
10069 << "without convergence; abandoned.\n";
10070 return false;
10071}
10072
10073void ComponentAnalyticField::DipoleFieldA00(
10074 const double xpos, const double ypos,
10075 double& ex, double& ey, double& volt, const bool opt) const {
10076
10077 //-----------------------------------------------------------------------
10078 // EMCA00 - Subroutine computing dipole terms in a field generated by
10079 // wires without periodicities.
10080 //-----------------------------------------------------------------------
10081
10082 // Initialise the potential and the electric field.
10083 ex = 0.;
10084 ey = 0.;
10085 volt = 0.;
10086 // Loop over all wires.
10087 double v = 0.;
10088 for (unsigned int i = 0; i < m_nWires; ++i) {
10089 const auto& wire = m_w[i];
10090 const double dx = xpos - wire.x;
10091 const double dy = ypos - wire.y;
10092 const double dxm = xpos + wire.x - 2. * m_coplax;
10093 const double dym = ypos + wire.y - 2. * m_coplay;
10094 // Calculate the field in case there are no planes.
10095 const double a = dx * dx - dy * dy;
10096 const double b = 2 * dx * dy;
10097 const double d2 = dx * dx + dy * dy;
10098 const double d4 = d2 * d2;
10099 double fx = (a * m_cosph2[i] + b * m_sinph2[i]) / d4;
10100 double fy = (b * m_cosph2[i] - a * m_sinph2[i]) / d4;
10101 if (opt) v = (dx * m_cosph2[i] + dy * m_sinph2[i]) / d2;
10102 // Take care of a plane at constant x.
10103 if (m_ynplax) {
10104 const double am = dxm * dxm - dy * dy;
10105 const double bm = 2 * dxm * dy;
10106 const double d2m = dxm * dxm + dy * dy;
10107 const double d4m = d2m * d2m;
10108 fx -= (am * m_cosph2[i] + bm * m_sinph2[i]) / d4m;
10109 fy -= (bm * m_cosph2[i] - am * m_sinph2[i]) / d4m;
10110 if (opt) v -= (dxm * m_cosph2[i] + dy * m_sinph2[i]) / d2m;
10111 }
10112 // Take care of a plane at constant y.
10113 if (m_ynplay) {
10114 const double am = dx * dx - dym * dym;
10115 const double bm = 2 * dx * dym;
10116 const double d2m = dx * dx + dym * dym;
10117 const double d4m = d2m * d2m;
10118 fx -= (am * m_cosph2[i] + bm * m_sinph2[i]) / d4m;
10119 fy -= (bm * m_cosph2[i] - am * m_sinph2[i]) / d4m;
10120 if (opt) v -= (dx * m_cosph2[i] + dym * m_sinph2[i]) / d2m;
10121 }
10122 // Take care of pairs of planes.
10123 if (m_ynplax && m_ynplay) {
10124 const double am = dxm * dxm - dym * dym;
10125 const double bm = 2 * dxm * dym;
10126 const double d2m = dxm * dxm + dym * dym;
10127 const double d4m = d2m * d2m;
10128 fx += (am * m_cosph2[i] + bm * m_sinph2[i]) / d4m;
10129 fy += (bm * m_cosph2[i] - am * m_sinph2[i]) / d4m;
10130 if (opt) v += (dxm * m_cosph2[i] + dym * m_sinph2[i]) / d2m;
10131 }
10132 // Normalise.
10133 volt -= m_amp2[i] * v;
10134 ex -= m_amp2[i] * fx;
10135 ey -= m_amp2[i] * fy;
10136 }
10137}
10138
10139void ComponentAnalyticField::DipoleFieldB1X(
10140 const double xpos, const double ypos,
10141 double& ex, double& ey, double& volt, const bool opt) const {
10142
10143 //-----------------------------------------------------------------------
10144 // EMCB1X - Subroutine computing dipole terms in a field generated by
10145 // a row of wires along the x-axis.
10146 //-----------------------------------------------------------------------
10147
10148 // Initialise the potential and the electric field.
10149 ex = 0.;
10150 ey = 0.;
10151 volt = 0.;
10152 // Shorthand.
10153 const double tx = Pi / m_sx;
10154 const double tx2 = tx * tx;
10155 // Loop over all wires.
10156 double v = 0.;
10157 for (unsigned int i = 0; i < m_nWires; ++i) {
10158 const auto& wire = m_w[i];
10159 // Calculate the field in case there are no planes.
10160 const double dx = tx * (xpos - wire.x);
10161 const double dy = tx * (ypos - wire.y);
10162 const double a = 1 - cos(2 * dx) * cosh(2 * dy);
10163 const double b = sin(2 * dx) * sinh(2 * dy);
10164 const double sx = sin(dx);
10165 const double shy = sinh(dy);
10166 const double d2 = sx * sx + shy * shy;
10167 const double d4 = d2 * d2;
10168 double fx = ( m_cosph2[i] * a + m_sinph2[i] * b) / d4;
10169 double fy = (-m_sinph2[i] * a + m_cosph2[i] * b) / d4;
10170 if (opt) {
10171 v = (m_cosph2[i] * sin(2 * dx) + m_sinph2[i] * sinh(2 * dy)) / d2;
10172 }
10173 // Take care of a plane at constant y.
10174 if (m_ynplay) {
10175 const double dym = tx * (ypos + wire.y - 2. * m_coplay);
10176 const double am = 1 - cos(2 * dx) * cosh(2 * dym);
10177 const double bm = sin(2 * dx) * sinh(2 * dym);
10178 const double shym = sinh(dym);
10179 const double d2m = sx * sx + shym * shym;
10180 const double d4m = d2m * d2m;
10181 fx -= (m_cosph2[i] * am - m_sinph2[i] * bm) / d4m;
10182 fy -= (m_sinph2[i] * am + m_cosph2[i] * bm) / d4m;
10183 if (opt) {
10184 v -= (m_cosph2[i] * sin(2 * dx) - m_sinph2[i] * sinh(2 * dym)) / d2m;
10185 }
10186 }
10187 // Calculate the electric field and the potential.
10188 ex -= m_amp2[i] * 0.5 * tx2 * fx;
10189 ey -= m_amp2[i] * 0.5 * tx2 * fy;
10190 if (opt) volt -= 0.5 * tx * m_amp2[i] * v;
10191 }
10192}
10193
10194void ComponentAnalyticField::DipoleFieldB1Y(
10195 const double xpos, const double ypos,
10196 double& ex, double& ey, double& volt, const bool opt) const {
10197
10198 //-----------------------------------------------------------------------
10199 // EMCB1Y - Subroutine computing dipole terms in a field generated by
10200 // a row of wires along the y-axis.
10201 //-----------------------------------------------------------------------
10202
10203 // Initialise the potential and the electric field.
10204 ex = 0.;
10205 ey = 0.;
10206 volt = 0.;
10207 // Shorthand.
10208 const double ty = Pi / m_sy;
10209 const double ty2 = ty * ty;
10210 // Loop over all wires.
10211 double v = 0.;
10212 for (unsigned int i = 0; i < m_nWires; ++i) {
10213 const auto& wire = m_w[i];
10214 // Calculate the field in case there are no planes.
10215 const double dx = ty * (xpos - wire.x);
10216 const double dy = ty * (ypos - wire.y);
10217 const double a = 1 - cosh(2 * dx) * cos(2 * dy);
10218 const double b = sinh(2 * dx) * sin(2 * dy);
10219 const double shx = sinh(dx);
10220 const double sy = sin(dy);
10221 const double d2 = shx * shx + sy * sy;
10222 const double d4 = d2 * d2;
10223 double fx = (-m_cosph2[i] * a + m_sinph2[i] * b) / d4;
10224 double fy = ( m_sinph2[i] * a + m_cosph2[i] * b) / d4;
10225 if (opt) {
10226 v = (m_cosph2[i] * sinh(2 * dx) + m_sinph2[i] * sin(2 * dy)) / d2;
10227 }
10228 // Take care of a plane at constant x.
10229 if (m_ynplax) {
10230 const double dxm = ty * (xpos + wire.x - 2. * m_coplax);
10231 const double am = 1 - cosh(2 * dxm) * cos(2 * dy);
10232 const double bm = sinh(2 * dxm) * sin(2 * dy);
10233 const double shxm = sinh(dxm);
10234 const double d2m = shxm * shxm + sy * sy;
10235 const double d4m = d2m * d2m;
10236 fx -= (m_cosph2[i] * am + m_sinph2[i] * bm) / d4m;
10237 fy -= (m_sinph2[i] * am - m_cosph2[i] * bm) / d4m;
10238 if (opt) {
10239 v -= (-m_cosph2[i] * sinh(2 * dxm) + m_sinph2[i] * sin(2 * dy)) / d2m;
10240 }
10241 }
10242 // Calculate the electric field and the potential.
10243 ex -= m_amp2[i] * 0.5 * ty2 * fx;
10244 ey -= m_amp2[i] * 0.5 * ty2 * fy;
10245 if (opt) volt -= 0.5 * ty * m_amp2[i] * v;
10246 }
10247}
10248
10249void ComponentAnalyticField::DipoleFieldB2X(
10250 const double xpos, const double ypos,
10251 double& ex, double& ey, double& volt, const bool opt) const {
10252
10253 //-----------------------------------------------------------------------
10254 // EMCB2X - Routine calculating the dipole terms for a charge between
10255 // parallel conducting planes.
10256 //-----------------------------------------------------------------------
10257
10258 // Initialise the potential and the electric field.
10259 ex = 0.;
10260 ey = 0.;
10261 volt = 0.;
10262 // Shorthand.
10263 const double tx = HalfPi / m_sx;
10264 const double tx2 = tx * tx;
10265 // Loop over all wires.
10266 double v = 0.;
10267 for (unsigned int i = 0; i < m_nWires; ++i) {
10268 const auto& wire = m_w[i];
10269 const double dx = tx * (xpos - wire.x);
10270 const double dy = tx * (ypos - wire.y);
10271 // Calculate the field in case there are no equipotential planes.
10272 const double a = 1 - cos(2 * dx) * cosh(2 * dy);
10273 const double b = sin(2 * dx) * sinh(2 * dy);
10274 const double sx = sin(dx);
10275 const double shy = sinh(dy);
10276 const double d2 = sx * sx + shy * shy;
10277 const double d4 = d2 * d2;
10278 double fx = ( m_cosph2[i] * a + m_sinph2[i] * b) / d4;
10279 double fy = (-m_sinph2[i] * a + m_cosph2[i] * b) / d4;
10280 const double dxn = tx * (xpos + wire.x - 2. * m_coplax);
10281 const double an = 1 - cos(2 * dxn) * cosh(2 * dy);
10282 const double bn = sin(2 * dxn) * sinh(2 * dy);
10283 const double sxn = sin(dxn);
10284 const double d2n = sxn * sxn + shy * shy;
10285 const double d4n = d2n * d2n;
10286 fx += (m_cosph2[i] * an - m_sinph2[i] * bn) / d4n;
10287 fy += (m_sinph2[i] * an + m_cosph2[i] * bn) / d4n;
10288 if (opt) {
10289 v = -sin(dx + dxn) * (-2 * m_cosph2[i] * (
10290 (1 + shy * shy) * sx * sxn + cos(dx) * cos(dxn) * shy * shy) +
10291 m_sinph2[i] * m_b2sin[i] * sinh(2 * dy)) / (d2 * d2n);
10292 }
10293 // Take care of planes at constant y.
10294 if (m_ynplay) {
10295 const double dym = tx * (ypos + wire.y - 2. * m_coplay);
10296 const double am = 1 - cos(2 * dx) * cosh(2 * dym);
10297 const double bm = sin(2 * dx) * sinh(2 * dym);
10298 const double shym = sinh(dym);
10299 const double d2m = sx * sx + shym * shym;
10300 const double d4m = d2m * d2m;
10301 fx -= (m_cosph2[i] * am - m_sinph2[i] * bm) / d4m;
10302 fy -= (m_sinph2[i] * am + m_cosph2[i] * bm) / d4m;
10303 const double amn = 1 - cos(2 * dxn) * cosh(2 * dym);
10304 const double bmn = sin(2 * dxn) * sinh(2 * dym);
10305 const double d2mn = sxn * sxn + shym * shym;
10306 const double d4mn = d2mn * d2mn;
10307 fx -= ( m_cosph2[i] * amn + m_sinph2[i] * bmn) / d4mn;
10308 fy -= (-m_sinph2[i] * amn + m_cosph2[i] * bmn) / d4mn;
10309 if (opt) {
10310 v += sin(dx + dxn) * (-2 * m_cosph2[i] * (
10311 (1 + shym * shym) * sx * sxn + cos(dx) * cos(dxn) * shym * shym) -
10312 m_sinph2[i] * m_b2sin[i] * sinh(2 * dym)) / (d2m * d2mn);
10313 }
10314 }
10315 // Calculate the electric field and the potential.
10316 ex -= m_amp2[i] * 0.5 * tx2 * fx;
10317 ey -= m_amp2[i] * 0.5 * tx2 * fy;
10318 if (opt) volt -= 0.5 * tx * m_amp2[i] * v;
10319 }
10320}
10321
10322void ComponentAnalyticField::DipoleFieldB2Y(
10323 const double xpos, const double ypos,
10324 double& ex, double& ey, double& volt, const bool opt) const {
10325
10326 //-----------------------------------------------------------------------
10327 // EMCB2Y - Routine calculating the dipole terms for a charge between
10328 // parallel conducting planes.
10329 //-----------------------------------------------------------------------
10330
10331 // Initialise the potential and the electric field.
10332 ex = 0.;
10333 ey = 0.;
10334 volt = 0.;
10335 // Shorthand.
10336 const double ty = HalfPi / m_sy;
10337 const double ty2 = ty * ty;
10338 // Loop over all wires.
10339 double v = 0.;
10340 for (unsigned int i = 0; i < m_nWires; ++i) {
10341 const auto& wire = m_w[i];
10342 const double dx = ty * (xpos - wire.x);
10343 const double dy = ty * (ypos - wire.y);
10344 // Calculate the field in case there are no equipotential planes.
10345 const double a = 1 - cosh(2 * dx) * cos(2 * dy);
10346 const double b = sinh(2 * dx) * sin(2 * dy);
10347 const double shx = sinh(dx);
10348 const double sy = sin(dy);
10349 const double d2 = shx * shx + sy * sy;
10350 const double d4 = d2 * d2;
10351 double fx = (-m_cosph2[i] * a + m_sinph2[i] * b) / d4;
10352 double fy = ( m_sinph2[i] * a + m_cosph2[i] * b) / d4;
10353 const double dyn = ty * (ypos + wire.y - 2. * m_coplay);
10354 const double an = 1 - cosh(2 * dx) * sin(2 * dyn);
10355 const double bn = sinh(2 * dx) * sin(2 * dyn);
10356 const double syn = sin(dyn);
10357 const double d2n = shx * shx + syn * syn;
10358 const double d4n = d2n * d2n;
10359 fx += (m_cosph2[i] * an + m_sinph2[i] * bn) / d4n;
10360 fy += (m_sinph2[i] * an - m_cosph2[i] * bn) / d4n;
10361 if (opt) {
10362 // TODO: check!
10363 v = sin(dy + dyn) * (
10364 m_sinph2[i] * (-cos(dy + dyn) + cos(dy - dyn) * cosh(2 * dx)) -
10365 m_cosph2[i] * sinh(2 * dx) * (cos(dyn) * sy + cos(dy) * syn)) /
10366 (d2 * d2n);
10367 }
10368 // Take care of planes at constant x.
10369 if (m_ynplax) {
10370 const double dxm = ty * (xpos + wire.x - 2. * m_coplax);
10371 const double am = 1 - cosh(2 * dxm) * cos(2 * dy);
10372 const double bm = sinh(2 * dxm) * sin(2 * dy);
10373 const double shxm = sinh(dxm);
10374 const double d2m = shxm * shxm + sy * sy;
10375 const double d4m = d2m * d2m;
10376 fx -= (m_cosph2[i] * am + m_sinph2[i] * bm) / d4m;
10377 fy -= (m_sinph2[i] * am - m_cosph2[i] * bm) / d4m;
10378 const double amn = 1 - cosh(2 * dxm) * cos(2 * dyn);
10379 const double bmn = sinh(2 * dxm) * sin(2 * dyn);
10380 const double d2mn = shxm * shxm + syn * syn;
10381 const double d4mn = d2mn * d2mn;
10382 fx -= (-m_cosph2[i] * amn + m_sinph2[i] * bmn) / d4mn;
10383 fy -= ( m_sinph2[i] * amn + m_cosph2[i] * bmn) / d4mn;
10384 if (opt) {
10385 // TODO: check!
10386 v -= sin(dy + dyn) * (
10387 m_sinph2[i] * (-cos(dy + dyn) + cos(dy - dyn) * cosh(2 * dxm)) +
10388 m_cosph2[i] * sinh(2 * dxm) * (cos(dyn) * sy - cos(dy) * syn)) /
10389 (d2m * d2mn);
10390
10391 }
10392 }
10393 // Calculate the electric field and the potential.
10394 ex -= m_amp2[i] * 0.5 * ty2 * fx;
10395 ey -= m_amp2[i] * 0.5 * ty2 * fy;
10396 if (opt) volt -= 0.5 * ty * m_amp2[i] * v;
10397 }
10398}
10399
10401 const unsigned int nPoles, const bool print, const bool plot,
10402 const double rmult, const double eps,
10403 const unsigned int nMaxIter) {
10404
10405 //-----------------------------------------------------------------------
10406 // EFMWIR - Computes the dipole moment of a given wire.
10407 //-----------------------------------------------------------------------
10408 if (!m_cellset && !Prepare()) return false;
10409 // Check input parameters.
10410 if (iw >= m_nWires) {
10411 std::cerr << m_className << "::MultipoleMoments:\n"
10412 << " Wire index out of range.\n";
10413 return false;
10414 }
10415 if (eps <= 0.) {
10416 std::cerr << m_className << "::MultipoleMoments:\n"
10417 << " Epsilon must be positive.\n";
10418 return false;
10419 }
10420 if (nPoles == 0) {
10421 std::cerr << m_className << "::MultipoleMoments:\n"
10422 << " Multipole order out of range.\n";
10423 return false;
10424 }
10425 if (rmult <= 0.) {
10426 std::cerr << m_className << "::MultipoleMoments:\n"
10427 << " Radius multiplication factor out of range.\n";
10428 return false;
10429 }
10430
10431 const double xw = m_w[iw].x;
10432 const double yw = m_w[iw].y;
10433 // Set the radius of the wire to 0.
10434 const double rw = m_w[iw].r;
10435 m_w[iw].r = 0.;
10436
10437 // Loop around the wire.
10438 constexpr unsigned int nPoints = 20000;
10439 std::vector<double> angle(nPoints, 0.);
10440 std::vector<double> volt(nPoints, 0.);
10441 std::vector<double> weight(nPoints, 1.);
10442 for (unsigned int i = 0; i < nPoints; ++i) {
10443 // Set angle around wire.
10444 angle[i] = TwoPi * (i + 1.) / nPoints;
10445 // Compute E field, make sure the point is in a free region.
10446 const double x = xw + rmult * rw * cos(angle[i]);
10447 const double y = yw + rmult * rw * sin(angle[i]);
10448 double ex = 0., ey = 0., ez = 0., v = 0.;
10449 if (Field(x, y, 0., ex, ey, ez, v, true) != 0) {
10450 std::cerr << m_className << "::MultipoleMoments:\n"
10451 << " Unexpected location code. Computation stopped.\n";
10452 m_w[iw].r = rw;
10453 return false;
10454 }
10455 // Assign the result to the fitting array.
10456 volt[i] = v;
10457 }
10458 // Restore the wire diameter.
10459 m_w[iw].r = rw;
10460
10461 // Determine the maximum, minimum and average.
10462 double vmin = *std::min_element(volt.cbegin(), volt.cend());
10463 double vmax = *std::max_element(volt.cbegin(), volt.cend());
10464 double vave = std::accumulate(volt.cbegin(), volt.cend(), 0.) / nPoints;
10465 // Subtract the wire potential to centre the data more or less.
10466 for (unsigned int i = 0; i < nPoints; ++i) volt[i] -= vave;
10467 vmax -= vave;
10468 vmin -= vave;
10469
10470 // Perform the fit.
10471 const double vm = 0.5 * fabs(vmin) + fabs(vmax);
10472 double chi2 = 1.e-6 * nPoints * vm * vm;
10473 const double dist = 1.e-3 * (1. + vm);
10474 const unsigned int nPar = 2 * nPoles + 1;
10475 std::vector<double> pars(nPar, 0.);
10476 std::vector<double> epar(nPar, 0.);
10477 pars[0] = 0.5 * (vmax + vmin);
10478 for (unsigned int i = 1; i <= nPoles; ++i) {
10479 pars[2 * i - 1] = 0.5 * (vmax - vmin);
10480 pars[2 * i] = 0.;
10481 }
10482
10483 auto f = [nPoles](const double x, const std::vector<double>& par) {
10484 // EFMFUN
10485 // Sum the series, initial value is the monopole term.
10486 double sum = par[0];
10487 for (unsigned int k = 1; k <= nPoles; ++k) {
10488 // Obtain the Legendre polynomial of this order and add to the series.
10489 const float cphi = cos(x - par[2 * k]);
10490 sum += par[2 * k - 1] * sqrt(k + 0.5) * Numerics::Legendre(k, cphi);
10491 }
10492 return sum;
10493 };
10494
10495 if (!Numerics::LeastSquaresFit(f, pars, epar, angle, volt, weight,
10496 nMaxIter, dist, chi2, eps, m_debug, print)) {
10497 std::cerr << m_className << "::MultipoleMoments:\n"
10498 << " Fitting the multipoles failed; computation stopped.\n";
10499 }
10500 // Plot the result of the fit.
10501 if (plot) {
10502 const std::string name = ViewBase::FindUnusedCanvasName("cMultipole");
10503 TCanvas* cfit = new TCanvas(name.c_str(), "Multipoles");
10504 cfit->SetGridx();
10505 cfit->SetGridy();
10506 cfit->DrawFrame(0., vmin, TwoPi, vmax,
10507 ";Angle around the wire [rad]; Potential - average [V]");
10508 TGraph graph;
10509 graph.SetLineWidth(2);
10510 graph.SetLineColor(kBlack);
10511 graph.DrawGraph(angle.size(), angle.data(), volt.data(), "lsame");
10512 // Sum of contributions.
10513 constexpr unsigned int nP = 1000;
10514 std::array<double, nP> xp;
10515 std::array<double, nP> yp;
10516 for (unsigned int i = 0; i < nP; ++i) {
10517 xp[i] = TwoPi * (i + 1.) / nP;
10518 yp[i] = f(xp[i], pars);
10519 }
10520 graph.SetLineColor(kViolet + 3);
10521 graph.DrawGraph(nP, xp.data(), yp.data(), "lsame");
10522 // Individual contributions.
10523 std::vector<double> parres = pars;
10524 for (unsigned int i = 1; i <= nPoles; ++i) parres[2 * i - 1] = 0.;
10525 for (unsigned int j = 1; j <= nPoles; ++j) {
10526 parres[2 * j - 1] = pars[2 * j - 1];
10527 for (unsigned int i = 0; i < nP; ++i) {
10528 yp[i] = f(xp[i], parres);
10529 }
10530 parres[2 * j - 1] = 0.;
10531 graph.SetLineColor(kAzure + j);
10532 graph.DrawGraph(nP, xp.data(), yp.data(), "lsame");
10533 }
10534 gPad->Update();
10535 }
10536
10537 // Print the results.
10538 std::cout << m_className << "::MultipoleMoments:\n"
10539 << " Multipole moments for wire " << iw << ":\n"
10540 << " Moment Value Angle [degree]\n";
10541 std::printf(" %6u %15.8f Arbitrary\n", 0, vave);
10542 for (unsigned int i = 1; i <= nPoles; ++i) {
10543 // Remove radial term from the multipole moment.
10544 const double val = pow(rmult * rw, i) * pars[2 * i - 1];
10545 const double phi = RadToDegree * fmod(pars[2 * i], Pi);
10546 std::printf(" %6u %15.8f %15.8f\n", i, val, phi);
10547 }
10548 return true;
10549}
10550
10551} // 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 AddPixelOnPlaneX(const double x, const double ymin, const double ymax, const double zmin, const double zmax, const std::string &label, const double gap=-1., const double rot=0.)
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.)
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.
bool IsInTrapRadius(const double q0, const double x0, const double y0, const double z0, double &xw, double &yx, double &rw) override
bool GetElementaryCell(double &x0, double &y0, double &z0, double &x1, double &y1, double &z1) override
Get the coordinates of the elementary cell.
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 AddPixelOnPlaneY(const double y, const double xmin, const double xmax, const double zmin, const double zmax, const std::string &label, const double gap=-1., const double rot=0.)
Add a pixel on an existing plane at constant y.
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.
Definition: Component.hh:13
std::array< bool, 3 > m_rotationSymmetric
Rotation symmetry around x-axis, y-axis, z-axis.
Definition: Component.hh:350
std::array< bool, 3 > m_mirrorPeriodic
Mirror periodicity in x, y, z.
Definition: Component.hh:346
bool m_debug
Switch on/off debugging messages.
Definition: Component.hh:341
std::array< bool, 3 > m_periodic
Simple periodicity in x, y, z.
Definition: Component.hh:344
std::string m_className
Class name.
Definition: Component.hh:329
Geometry * m_geometry
Pointer to the geometry.
Definition: Component.hh:332
std::array< bool, 3 > m_axiallyPeriodic
Axial periodicity in x, y, z.
Definition: Component.hh:348
virtual bool GetBoundingBox(double &xmin, double &ymin, double &zmin, double &xmax, double &ymax, double &zmax)=0
Get the bounding box (envelope of the geometry).
virtual Medium * GetMedium(const double x, const double y, const double z, const bool tesselated=false) const =0
Retrieve the medium at a given point.
Abstract base class for media.
Definition: Medium.hh:13
static std::string FindUnusedCanvasName(const std::string &s)
Find an unused canvas name.
Definition: ViewBase.cc:310
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:1839
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