Garfield++ 3.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
ComponentNeBem2d.cc
Go to the documentation of this file.
1#include <cmath>
2#include <fstream>
3#include <iostream>
4#include <cstdio>
5#include <algorithm>
6#include <numeric>
7
11#include "Garfield/Polygon.hh"
12#include "Garfield/Random.hh"
13
14namespace {
15
16double Mag2(const std::array<double, 2>& x) {
17 return x[0] * x[0] + x[1] * x[1];
18}
19
20/// Determine whether a point (u, v) lies on a straight line
21/// (x1, y1) to (x2, y2).
22bool OnLine(const double x1, const double y1, const double x2, const double y2,
23 const double u, const double v) {
24 // Set tolerances.
25 double epsx = 1.e-10 * std::max({fabs(x1), fabs(x2), fabs(u)});
26 double epsy = 1.e-10 * std::max({fabs(y1), fabs(y2), fabs(v)});
27 epsx = std::max(1.e-10, epsx);
28 epsy = std::max(1.e-10, epsy);
29
30 if ((fabs(x1 - u) <= epsx && fabs(y1 - v) <= epsy) ||
31 (fabs(x2 - u) <= epsx && fabs(y2 - v) <= epsy)) {
32 // Point to be examined coincides with start or end.
33 return true;
34 } else if (fabs(x1 - x2) <= epsx && fabs(y1 - y2) <= epsy) {
35 // The line (x1, y1) to (x2, y2) is in fact a point.
36 return false;
37 }
38 double xc = 0., yc = 0.;
39 if (fabs(u - x1) + fabs(v - y1) < fabs(u - x2) + fabs(v - y2)) {
40 // (u, v) is nearer to (x1, y1).
41 const double dx = (x2 - x1);
42 const double dy = (y2 - y1);
43 const double xl = ((u - x1) * dx + (v - y1) * dy) / (dx * dx + dy * dy);
44 if (xl < 0.) {
45 xc = x1;
46 yc = y1;
47 } else if (xl > 1.) {
48 xc = x2;
49 yc = y2;
50 } else {
51 xc = x1 + xl * dx;
52 yc = y1 + xl * dy;
53 }
54 } else {
55 // (u, v) is nearer to (x2, y2).
56 const double dx = (x1 - x2);
57 const double dy = (y1 - y2);
58 const double xl = ((u - x2) * dx + (v - y2) * dy) / (dx * dx + dy * dy);
59 if (xl < 0.) {
60 xc = x2;
61 yc = y2;
62 } else if (xl > 1.) {
63 xc = x1;
64 yc = y1;
65 } else {
66 xc = x2 + xl * dx;
67 yc = y2 + xl * dy;
68 }
69 }
70 // See whether the point is on the line.
71 if (fabs(u - xc) < epsx && fabs(v - yc) < epsy) {
72 return true;
73 }
74 return false;
75}
76
77/// Determine whether the 2 straight lines (x1, y1) to (x2, y2)
78/// and (u1, v1) to (u2, v2) cross at an intermediate point for both lines.
79bool Crossing(const double x1, const double y1, const double x2,
80 const double y2, const double u1, const double v1,
81 const double u2, const double v2, double& xc, double& yc) {
82 // Initial values.
83 xc = 0.;
84 yc = 0.;
85 /// Matrix to compute the crossing point.
86 std::array<std::array<double, 2>, 2> a;
87 a[0][0] = y2 - y1;
88 a[0][1] = v2 - v1;
89 a[1][0] = x1 - x2;
90 a[1][1] = u1 - u2;
91 const double det = a[0][0] * a[1][1] - a[1][0] * a[0][1];
92 // Set tolerances.
93 const double epsx = std::max(1.e-10, 1.e-10 * std::max({fabs(x1), fabs(x2),
94 fabs(u1), fabs(u2)}));
95 const double epsy = std::max(1.e-10, 1.e-10 * std::max({fabs(y1), fabs(y2),
96 fabs(v1), fabs(v2)}));
97 // Check if the lines are parallel.
98 if (fabs(det) < epsx * epsy) return false;
99
100 // Crossing, non-trivial lines: solve crossing equations.
101 const double aux = a[1][1];
102 a[1][1] = a[0][0] / det;
103 a[0][0] = aux / det;
104 a[1][0] = -a[1][0] / det;
105 a[0][1] = -a[0][1] / det;
106 // Compute crossing point.
107 xc = a[0][0] * (x1 * y2 - x2 * y1) + a[1][0] * (u1 * v2 - u2 * v1);
108 yc = a[0][1] * (x1 * y2 - x2 * y1) + a[1][1] * (u1 * v2 - u2 * v1);
109 // See whether the crossing point is on both lines.
110 if (OnLine(x1, y1, x2, y2, xc, yc) && OnLine(u1, v1, u2, v2, xc, yc)) {
111 // Intersecting lines.
112 return true;
113 }
114 // Crossing point not on both lines.
115 return false;
116}
117
118/// Determine whether two polygons are overlapping.
119bool Intersecting(const std::vector<double>& xp1,
120 const std::vector<double>& yp1,
121 const std::vector<double>& xp2,
122 const std::vector<double>& yp2) {
123
124 const double xmin1 = *std::min_element(std::begin(xp1), std::end(xp1));
125 const double ymin1 = *std::min_element(std::begin(yp1), std::end(yp1));
126 const double xmax1 = *std::max_element(std::begin(xp1), std::end(xp1));
127 const double ymax1 = *std::max_element(std::begin(yp1), std::end(yp1));
128
129 const double xmin2 = *std::min_element(std::begin(xp2), std::end(xp2));
130 const double ymin2 = *std::min_element(std::begin(yp2), std::end(yp2));
131 const double xmax2 = *std::max_element(std::begin(xp2), std::end(xp2));
132 const double ymax2 = *std::max_element(std::begin(yp2), std::end(yp2));
133
134 const double epsx = 1.e-6 * std::max({std::abs(xmax1), std::abs(xmin1),
135 std::abs(xmax2), std::abs(xmin2)});
136 const double epsy = 1.e-6 * std::max({std::abs(ymax1), std::abs(ymin1),
137 std::abs(ymax2), std::abs(ymin2)});
138 // Check if the bounding boxes overlap.
139 if (xmax1 + epsx < xmin2 || xmax2 + epsx < xmin1) return false;
140 if (ymax1 + epsy < ymin2 || ymax2 + epsy < ymin1) return false;
141
142 const unsigned int n1 = xp1.size();
143 const unsigned int n2 = xp2.size();
144 for (unsigned int i = 0; i < n1; ++i) {
145 const double x0 = xp1[i];
146 const double y0 = yp1[i];
147 const unsigned int ii = i < n1 - 1 ? i + 1 : 0;
148 const double x1 = xp1[ii];
149 const double y1 = yp1[ii];
150 for (unsigned int j = 0; j < n2; ++j) {
151 const unsigned int jj = j < n2 - 1 ? j + 1 : 0;
152 const double u0 = xp2[j];
153 const double v0 = yp2[j];
154 const double u1 = xp2[jj];
155 const double v1 = yp2[jj];
156 double xc = 0., yc = 0.;
157 if (!Crossing(x0, y0, x1, y1, u0, v0, u1, v1, xc, yc)) continue;
158 if ((OnLine(x0, y0, x1, y1, u0, v0) || OnLine(x0, y0, x1, y1, u1, v1)) &&
159 (OnLine(u0, v0, u1, v1, x0, y0) || OnLine(u0, v0, u1, v1, x1, y1))) {
160 continue;
161 }
162 return true;
163 }
164 }
165 return false;
166}
167
168/// Determine whether polygon 1 is fully enclosed by polygon 2.
169bool Enclosed(const std::vector<double>& xp1,
170 const std::vector<double>& yp1,
171 const std::vector<double>& xp2,
172 const std::vector<double>& yp2) {
173
174 const double xmin1 = *std::min_element(std::begin(xp1), std::end(xp1));
175 const double ymin1 = *std::min_element(std::begin(yp1), std::end(yp1));
176 const double xmax1 = *std::max_element(std::begin(xp1), std::end(xp1));
177 const double ymax1 = *std::max_element(std::begin(yp1), std::end(yp1));
178
179 const double xmin2 = *std::min_element(std::begin(xp2), std::end(xp2));
180 const double ymin2 = *std::min_element(std::begin(yp2), std::end(yp2));
181 const double xmax2 = *std::max_element(std::begin(xp2), std::end(xp2));
182 const double ymax2 = *std::max_element(std::begin(yp2), std::end(yp2));
183
184 const double epsx = 1.e-6 * std::max({std::abs(xmax1), std::abs(xmin1),
185 std::abs(xmax2), std::abs(xmin2)});
186 const double epsy = 1.e-6 * std::max({std::abs(ymax1), std::abs(ymin1),
187 std::abs(ymax2), std::abs(ymin2)});
188 // Check the bounding boxes.
189 if (xmax1 + epsx < xmin2 || xmax2 + epsx < xmin1) return false;
190 if (ymax1 + epsy < ymin2 || ymax2 + epsy < ymin1) return false;
191 if (xmin1 + epsx < xmin2 || xmax1 > xmax2 + epsx) return false;
192 if (ymin1 + epsy < ymin2 || ymax1 > ymax2 + epsy) return false;
193
194 const unsigned int n1 = xp1.size();
195 for (unsigned int i = 0; i < n1; ++i) {
196 bool inside = false, edge = false;
197 Garfield::Polygon::Inside(xp2, yp2, xp1[i], yp1[i], inside, edge);
198 if (!inside) return false;
199 }
200 return true;
201}
202
203}
204
205namespace Garfield {
206
207const double ComponentNeBem2d::InvEpsilon0 = 1. / VacuumPermittivity;
208const double ComponentNeBem2d::InvTwoPiEpsilon0 = 1. / TwoPiEpsilon0;
209
211 m_className = "ComponentNeBem2d";
212}
213
214void ComponentNeBem2d::ElectricField(const double x, const double y,
215 const double z, double& ex, double& ey,
216 double& ez, double& v, Medium*& m,
217 int& status) {
218 ex = ey = ez = v = 0.;
219 status = 0;
220 // Check if the requested point is inside the z-range.
221 if (m_useRangeZ && (z < m_zmin || z > m_zmax)) {
222 status = -6;
223 return;
224 }
225
226 // Check if the requested point is inside a medium.
227 m = GetMedium(x, y, z);
228 if (!m) {
229 status = -6;
230 return;
231 }
232 // Inside a conductor?
233 if (m->IsConductor()) {
234 status = -5;
235 // Find the potential.
236 for (const auto& region : m_regions) {
237 bool inside = false, edge = false;
238 Garfield::Polygon::Inside(region.xv, region.yv, x, y, inside, edge);
239 if (inside || edge) {
240 v = region.bc.second;
241 break;
242 }
243 }
244 return;
245 }
246
247 if (!m_ready) {
248 if (!Initialise()) {
249 std::cerr << m_className << "::ElectricField: Initialisation failed.\n";
250 status = -11;
251 return;
252 }
253 }
254
255 // See whether we are inside a wire.
256 const unsigned int nWires = m_wires.size();
257 for (unsigned int i = 0; i < nWires; ++i) {
258 const double dx = x - m_wires[i].x;
259 const double dy = y - m_wires[i].y;
260 if (dx * dx + dy * dy < m_wires[i].r * m_wires[i].r) {
261 v = m_wires[i].v;
262 status = i + 1;
263 return;
264 }
265 }
266
267 // Sum up the contributions from all straight-line elements.
268 for (const auto& element : m_elements) {
269 const double cphi = element.cphi;
270 const double sphi = element.sphi;
271 // Transform to local coordinates.
272 double xL = 0., yL = 0.;
273 ToLocal(x - element.x, y - element.y, cphi, sphi, xL, yL);
274 // Compute the potential.
275 v += LinePotential(element.a, xL, yL) * element.q;
276 // Compute the field in local coordinates.
277 double fx = 0., fy = 0.;
278 LineFlux(element.a, xL, yL, fx, fy);
279 // Rotate to the global frame.
280 ToGlobal(fx, fy, cphi, sphi, fx, fy);
281 ex += fx * element.q;
282 ey += fy * element.q;
283 }
284
285 // Add the contributions from the wires.
286 for (const auto& wire : m_wires) {
287 // Compute the potential.
288 v += WirePotential(wire.r, x - wire.x, y - wire.y) * wire.q;
289 // Compute the field.
290 double fx = 0., fy = 0.;
291 WireFlux(wire.x, x - wire.x, y - wire.y, fx, fy);
292 ex += fx * wire.q;
293 ey += fy * wire.q;
294 }
295
296}
297
298void ComponentNeBem2d::ElectricField(const double x, const double y,
299 const double z, double& ex, double& ey,
300 double& ez, Medium*& m, int& status) {
301 double v = 0.;
302 ElectricField(x, y, z, ex, ey, ez, v, m, status);
303}
304
305bool ComponentNeBem2d::GetVoltageRange(double& vmin, double& vmax) {
306 bool gotValue = false;
307 for (const auto& region : m_regions) {
308 if (region.bc.first != Voltage) continue;
309 if (!gotValue) {
310 vmin = vmax = region.bc.second;
311 gotValue = true;
312 } else {
313 vmin = std::min(vmin, region.bc.second);
314 vmax = std::max(vmax, region.bc.second);
315 }
316 }
317
318 for (const auto& segment : m_segments) {
319 if (segment.bc.first != Voltage) continue;
320 if (!gotValue) {
321 vmin = vmax = segment.bc.second;
322 gotValue = true;
323 } else {
324 vmin = std::min(vmin, segment.bc.second);
325 vmax = std::max(vmax, segment.bc.second);
326 }
327 }
328
329 for (const auto& wire : m_wires) {
330 if (!gotValue) {
331 vmin = vmax = wire.v;
332 gotValue = true;
333 } else {
334 vmin = std::min(vmin, wire.v);
335 vmax = std::max(vmax, wire.v);
336 }
337 }
338 return gotValue;
339}
340
341Medium* ComponentNeBem2d::GetMedium(const double x, const double y,
342 const double z) {
343
344 if (m_geometry) return m_geometry->GetMedium(x, y, z);
345 for (const auto& region : m_regions) {
346 bool inside = false, edge = false;
347 Garfield::Polygon::Inside(region.xv, region.yv, x, y, inside, edge);
348 if (inside || edge) return region.medium;
349 }
350 return m_medium;
351}
352
354 double& xmin, double& ymin, double& zmin,
355 double& xmax, double& ymax, double& zmax) {
356
357 if (m_geometry) {
358 return m_geometry->GetBoundingBox(xmin, ymin, zmin, xmax, ymax, zmax);
359 }
360 zmin = m_zmin;
361 zmax = m_zmax;
362 bool gotValue = false;
363 for (const auto& region : m_regions) {
364 const auto& xv = region.xv;
365 const auto& yv = region.yv;
366 if (!gotValue) {
367 xmin = *std::min_element(std::begin(xv), std::end(xv));
368 ymin = *std::min_element(std::begin(yv), std::end(yv));
369 xmax = *std::max_element(std::begin(xv), std::end(xv));
370 ymax = *std::max_element(std::begin(yv), std::end(yv));
371 gotValue = true;
372 } else {
373 xmin = std::min(*std::min_element(std::begin(xv), std::end(xv)), xmin);
374 ymin = std::min(*std::min_element(std::begin(yv), std::end(yv)), ymin);
375 xmax = std::max(*std::max_element(std::begin(xv), std::end(xv)), xmax);
376 ymax = std::max(*std::max_element(std::begin(yv), std::end(yv)), ymax);
377 }
378 }
379 for (const auto& seg : m_segments) {
380 if (!gotValue) {
381 xmin = std::min(seg.x0[0], seg.x1[0]);
382 xmax = std::max(seg.x0[0], seg.x1[0]);
383 ymin = std::min(seg.x0[1], seg.x1[1]);
384 ymax = std::max(seg.x0[1], seg.x1[1]);
385 gotValue = true;
386 } else {
387 xmin = std::min({xmin, seg.x0[0], seg.x1[0]});
388 xmax = std::max({xmax, seg.x0[0], seg.x1[0]});
389 ymin = std::min({ymin, seg.x0[1], seg.x1[1]});
390 ymax = std::max({ymax, seg.x0[1], seg.x1[1]});
391 }
392 }
393 for (const auto& wire : m_wires) {
394 if (!gotValue) {
395 xmin = xmax = wire.x;
396 ymin = ymax = wire.y;
397 } else {
398 xmin = std::min(xmin, wire.x);
399 xmax = std::max(xmax, wire.x);
400 ymin = std::min(ymin, wire.y);
401 ymax = std::max(ymax, wire.y);
402 }
403 }
404 return gotValue;
405}
406
407bool ComponentNeBem2d::IsWireCrossed(const double x0, const double y0,
408 const double z0, const double x1,
409 const double y1, const double z1,
410 double& xc, double& yc, double& zc,
411 const bool centre, double& rc) {
412 xc = x0;
413 yc = y0;
414 zc = z0;
415
416 if (m_wires.empty()) return false;
417
418 const double dx = x1 - x0;
419 const double dy = y1 - y0;
420 const double d2 = dx * dx + dy * dy;
421 // Make sure the step length is non-zero.
422 if (d2 < Small) return false;
423 const double invd2 = 1. / d2;
424
425 // Both coordinates are assumed to be located inside
426 // the drift area and inside a drift medium.
427 // This should have been checked before this call.
428
429 double dMin2 = 0.;
430 for (const auto& wire : m_wires) {
431 const double xw = wire.x;
432 const double yw = wire.y;
433 // Calculate the smallest distance between track and wire.
434 const double xIn0 = dx * (xw - x0) + dy * (yw - y0);
435 // Check if the minimum is located before (x0, y0).
436 if (xIn0 < 0.) continue;
437 const double xIn1 = -(dx * (xw - x1) + dy * (yw - y1));
438 // Check if the minimum is located behind (x1, y1).
439 if (xIn1 < 0.) continue;
440 // Minimum is located between (x0, y0) and (x1, y1).
441 const double xw0 = xw - x0;
442 const double xw1 = xw - x1;
443 const double yw0 = yw - y0;
444 const double yw1 = yw - y1;
445 const double dw02 = xw0 * xw0 + yw0 * yw0;
446 const double dw12 = xw1 * xw1 + yw1 * yw1;
447 if (xIn1 * xIn1 * dw02 > xIn0 * xIn0 * dw12) {
448 dMin2 = dw02 - xIn0 * xIn0 * invd2;
449 } else {
450 dMin2 = dw12 - xIn1 * xIn1 * invd2;
451 }
452 const double r2 = wire.r * wire.r;
453 if (dMin2 < r2) {
454 // Wire has been crossed.
455 if (centre) {
456 xc = xw;
457 yc = yw;
458 } else {
459 // Find the point of intersection.
460 const double p = -xIn0 * invd2;
461 const double q = (dw02 - r2) * invd2;
462 const double s = sqrt(p * p - q);
463 const double t = std::min(-p + s, -p - s);
464 xc = x0 + t * dx;
465 yc = y0 + t * dy;
466 zc = z0 + t * (z1 - z0);
467 }
468 rc = wire.r;
469 return true;
470 }
471 }
472 return false;
473}
474
475bool ComponentNeBem2d::IsInTrapRadius(const double q, const double x,
476 const double y, const double /*z*/,
477 double& xw, double& yw, double& rw) {
478
479 for (const auto& wire : m_wires) {
480 // Skip wires with the wrong charge.
481 if (q * wire.q > 0.) continue;
482 const double dx = wire.x - x;
483 const double dy = wire.y - y;
484 const double rt = wire.r * wire.ntrap;
485 if (dx * dx + dy * dy < rt * rt) {
486 xw = wire.x;
487 yw = wire.y;
488 rw = wire.r;
489 return true;
490 }
491 }
492 return false;
493}
494
495void ComponentNeBem2d::SetRangeZ(const double zmin, const double zmax) {
496
497 if (fabs(zmax - zmin) <= 0.) {
498 std::cerr << m_className << "::SetRangeZ: Zero range is not permitted.\n";
499 return;
500 }
501 m_zmin = std::min(zmin, zmax);
502 m_zmax = std::max(zmin, zmax);
503 m_useRangeZ = true;
504}
505
506bool ComponentNeBem2d::AddSegment(const double x0, const double y0,
507 const double x1, const double y1,
508 const double v, const int ndiv) {
509 const double dx = x1 - x0;
510 const double dy = y1 - y0;
511 if (dx * dx + dy * dy < Small) {
512 std::cerr << m_className << "::AddSegment: Length must be > 0.\n";
513 return false;
514 }
515
516 Segment segment;
517 segment.x0 = {x0, y0};
518 segment.x1 = {x1, y1};
519 segment.bc = std::make_pair(Voltage, v);
520 segment.region1 = -1;
521 segment.region2 = -1;
522 segment.ndiv = ndiv;
523 m_segments.push_back(std::move(segment));
524
525 if (m_debug) {
526 std::cout << m_className << "::AddSegment:\n ("
527 << x0 << ", " << y0 << ") - (" << x1 << ", " << y1 << ")\n"
528 << " Potential: " << v << " V\n";
529 }
530
531 m_ready = false;
532 return true;
533}
534
535bool ComponentNeBem2d::AddWire(const double x, const double y, const double d,
536 const double v, const int ntrap) {
537 if (d < Small) {
538 std::cerr << m_className << "::AddWire: Diameter must be > 0.\n";
539 return false;
540 }
541 if (ntrap <= 0) {
542 std::cerr << m_className << "::AddWire: Nbr. of trap radii must be > 0.\n";
543 return false;
544 }
545
546 Wire wire;
547 wire.x = x;
548 wire.y = y;
549 wire.r = 0.5 * d;
550 wire.v = v;
551 wire.q = 0.;
552 wire.ntrap = ntrap;
553 m_wires.push_back(std::move(wire));
554
555 if (m_debug) {
556 std::cout << m_className << "::AddWire:\n"
557 << " Centre: (" << x << ", " << y << ")\n"
558 << " Diameter: " << d << " cm\n"
559 << " Potential: " << v << " V\n";
560 }
561
562 m_ready = false;
563 return true;
564}
565
566bool ComponentNeBem2d::AddRegion(const std::vector<double>& xp,
567 const std::vector<double>& yp,
568 Medium* medium, const unsigned int bctype,
569 const double v, const int ndiv) {
570
571 if (xp.size() != yp.size()) {
572 std::cerr << m_className << "::AddRegion:\n"
573 << " Mismatch between number of x- and y-coordinates.\n";
574 return false;
575 }
576 if (xp.size() < 3) {
577 std::cerr << m_className << "::AddRegion: Too few points.\n";
578 return false;
579 }
580 if (bctype != 1 && bctype != 4) {
581 std::cerr << m_className << "::AddRegion: Invalid boundary condition.\n";
582 return false;
583 }
584
585 // Check if this is a valid polygon (no self-crossing).
586 const unsigned int np = xp.size();
587 if (np > 3) {
588 for (unsigned int i0 = 0; i0 < np; ++i0) {
589 const unsigned int i1 = i0 < np - 1 ? i0 + 1 : 0;
590 for (unsigned int j = 0; j < np - 3; ++j) {
591 const unsigned int j0 = i1 < np - 1 ? i1 + 1 : 0;
592 const unsigned int j1 = j0 < np - 1 ? j0 + 1 : 0;
593 double xc = 0., yc = 0.;
594 if (Crossing(xp[i0], yp[i0], xp[i1], yp[i1],
595 xp[j0], yp[j0], xp[j1], yp[j1], xc, yc)) {
596 std::cerr << m_className << "::AddRegion: Edges cross each other.\n";
597 return false;
598 }
599 }
600 }
601 }
602 std::vector<double> xv = xp;
603 std::vector<double> yv = yp;
604 const double xmin = *std::min_element(std::begin(xv), std::end(xv));
605 const double ymin = *std::min_element(std::begin(yv), std::end(yv));
606 const double xmax = *std::max_element(std::begin(xv), std::end(xv));
607 const double ymax = *std::max_element(std::begin(yv), std::end(yv));
608
609 const double epsx = 1.e-6 * std::max(std::abs(xmax), std::abs(xmin));
610 const double epsy = 1.e-6 * std::max(std::abs(ymax), std::abs(ymin));
611
612 const double f = Polygon::Area(xp, yp);
613 if (std::abs(f) < std::max(1.e-10, epsx * epsy)) {
614 std::cerr << m_className << "::AddRegion: Degenerate polygon.\n";
615 return false;
616 } else if (f > 0.) {
617 // Make sure all polygons have the same "handedness".
618 if (m_debug) {
619 std::cout << m_className << "::AddRegion: Reversing orientation.\n";
620 }
621 std::reverse(xv.begin(), xv.end());
622 std::reverse(yv.begin(), yv.end());
623 }
624 for (const auto& region : m_regions) {
625 if (Intersecting(xv, yv, region.xv, region.yv)) {
626 std::cerr << m_className << "::AddRegion:\n"
627 << " Polygon intersects an existing region.\n";
628 return false;
629 }
630 }
631 Region region;
632 region.xv = xv;
633 region.yv = yv;
634 region.medium = medium;
635 if (bctype == 1) {
636 region.bc = std::make_pair(Voltage, v);
637 } else if (bctype == 4) {
638 region.bc = std::make_pair(Dielectric, v);
639 }
640 region.depth = 0;
641 region.ndiv = ndiv;
642 m_regions.push_back(std::move(region));
643 return true;
644}
645
646void ComponentNeBem2d::SetNumberOfDivisions(const unsigned int ndiv) {
647 if (ndiv == 0) {
648 std::cerr << m_className << "::SetNumberOfDivisions:\n"
649 << " Number of divisions must be greater than zero.\n";
650 return;
651 }
652
653 m_nDivisions = ndiv;
654 m_ready = false;
655}
656
658 if (ncoll == 0) {
659 std::cerr << m_className << "::SetNumberOfCollocationPoints:\n"
660 << " Number of points must be greater than zero.\n";
661 return;
662 }
663
664 m_nCollocationPoints = ncoll;
665 m_ready = false;
666}
667
668void ComponentNeBem2d::SetMaxNumberOfIterations(const unsigned int niter) {
669 if (niter == 0) {
670 std::cerr << m_className << "::SetMaxNumberOfIterations:\n"
671 << " Number of iterations must be greater than zero.\n";
672 return;
673 }
674 m_nMaxIterations = niter;
675}
676
677bool ComponentNeBem2d::GetRegion(const unsigned int i,
678 std::vector<double>& xv,
679 std::vector<double>& yv,
680 Medium*& medium, unsigned int& bctype,
681 double& v) {
682 if (i >= m_regions.size()) return false;
683 if (!m_ready) {
684 if (!Initialise()) return false;
685 }
686 const auto& region = m_regions[i];
687 xv = region.xv;
688 yv = region.yv;
689 medium = region.medium;
690 bctype = region.bc.first == Voltage ? 1 : 4;
691 v = region.bc.second;
692 return true;
693}
694
695bool ComponentNeBem2d::GetSegment(const unsigned int i,
696 double& x0, double& y0, double& x1, double& y1, double& v) const {
697
698 if (i >= m_segments.size()) return false;
699 const auto& seg = m_segments[i];
700 x0 = seg.x0[0];
701 y0 = seg.x0[1];
702 x1 = seg.x1[0];
703 y1 = seg.x1[1];
704 v = seg.bc.second;
705 return true;
706}
707
708bool ComponentNeBem2d::GetWire(const unsigned int i,
709 double& x, double& y, double& d, double& v, double& q) const {
710
711 if (i >= m_wires.size()) return false;
712 const auto& wire = m_wires[i];
713 x = wire.x;
714 y = wire.y;
715 d = 2 * wire.r;
716 v = wire.v;
717 q = wire.q;
718 return true;
719}
720
721bool ComponentNeBem2d::GetElement(const unsigned int i,
722 double& x0, double& y0, double& x1, double& y1, double& q) const {
723
724 if (i >= m_elements.size()) return false;
725 const auto& element = m_elements[i];
726 ToGlobal(-element.a, 0., element.cphi, element.sphi, x0, y0);
727 ToGlobal( element.a, 0., element.cphi, element.sphi, x1, y1);
728 x0 += element.x;
729 y0 += element.y;
730 x1 += element.x;
731 y1 += element.y;
732 q = element.q;
733 return true;
734}
735
737
738 m_ready = false;
739 m_elements.clear();
740
741 double vmin = 0., vmax = 0.;
742 if (!GetVoltageRange(vmin, vmax)) {
743 std::cerr << m_className << "::Initialise:\n"
744 << " Could not determine the voltage range.\n";
745 return false;
746 }
747 if (fabs(vmin - vmax) < 1.e-6 * (vmin + vmax)) {
748 std::cerr << m_className << "::Initialise:\n"
749 << " All potentials are the same.\n";
750 return false;
751 }
752
753 if (m_debug) std::cout << m_className << "::Initialise:\n";
754 // Loop over the regions.
755 const unsigned int nRegions = m_regions.size();
756 if (m_debug) std::cout << " " << nRegions << " regions.\n";
757 std::vector<std::vector<unsigned int> > motherRegions(nRegions);
758 for (unsigned int i = 0; i < nRegions; ++i) {
759 auto& region = m_regions[i];
760 // Check if the region is fully enclosed by other ones.
761 for (unsigned int j = 0; j < nRegions; ++j) {
762 if (i == j) continue;
763 const auto& other = m_regions[j];
764 if (!Enclosed(region.xv, region.yv, other.xv, other.yv)) continue;
765 motherRegions[i].push_back(j);
766 if (m_debug) {
767 std::cout << " Region " << i << " is enclosed by region "
768 << j << ".\n";
769 }
770 }
771 region.depth = motherRegions[i].size();
772 }
773
774 std::vector<Segment> segments;
775 for (unsigned int i = 0; i < nRegions; ++i) {
776 const auto& region = m_regions[i];
777 int outerRegion = -1;
778 for (const unsigned int k : motherRegions[i]) {
779 if (outerRegion < 0) {
780 outerRegion = k;
781 } else if (m_regions[outerRegion].depth < m_regions[k].depth) {
782 outerRegion = k;
783 }
784 }
785 // Add the segments bounding this region.
786 const unsigned int n = region.xv.size();
787 for (unsigned int j = 0; j < n; ++j) {
788 const unsigned int k = j < n - 1 ? j + 1 : 0;
789 Segment seg;
790 seg.x0 = {region.xv[j], region.yv[j]};
791 seg.x1 = {region.xv[k], region.yv[k]};
792 seg.region1 = i;
793 seg.region2 = outerRegion;
794 seg.bc = region.bc;
795 seg.ndiv = region.ndiv;
796 segments.push_back(std::move(seg));
797 }
798 }
799 // Add the segments specified by the user.
800 segments.insert(segments.end(), m_segments.begin(), m_segments.end());
801 const unsigned int nSegments = segments.size();
802 if (m_debug) std::cout << " " << nSegments << " segments.\n";
803 std::vector<bool> done(nSegments, false);
804 // Look for overlaps.
805 for (unsigned int i = 0; i < nSegments; ++i) {
806 if (done[i]) continue;
807 if (m_debug) {
808 std::cout << " Segment " << i << ". ("
809 << segments[i].x0[0] << ", " << segments[i].x0[1] << ") - ("
810 << segments[i].x1[0] << ", " << segments[i].x1[1] << ")\n";
811 }
812 const double x0 = segments[i].x0[0];
813 const double x1 = segments[i].x1[0];
814 const double y0 = segments[i].x0[1];
815 const double y1 = segments[i].x1[1];
816 // Pick up all collinear segments.
817 std::vector<Segment> newSegments;
818 for (unsigned int j = i + 1; j < nSegments; ++j) {
819 const double u0 = segments[j].x0[0];
820 const double u1 = segments[j].x1[0];
821 const double v0 = segments[j].x0[1];
822 const double v1 = segments[j].x1[1];
823 const double epsx = std::max(1.e-10 * std::max({fabs(x0), fabs(x1),
824 fabs(u0), fabs(u1)}),
825 1.e-10);
826 const double epsy = std::max(1.e-10 * std::max({fabs(y0), fabs(y1),
827 fabs(v0), fabs(v1)}),
828 1.e-10);
829 const double a00 = y1 - y0;
830 const double a01 = v1 - v0;
831 const double a10 = x0 - x1;
832 const double a11 = u0 - u1;
833 const double det = a00 * a11 - a10 * a01;
834 const double tol = epsx * epsy;
835 // Skip non-parallel segments.
836 if (std::abs(det) > tol) continue;
837
838 if (std::abs(x0 * (y1 - v0) + x1 * (v0 - y0) + u0 * (y0 - y1)) > tol) {
839 continue;
840 }
841 newSegments.push_back(segments[j]);
842 done[j] = true;
843 }
844 newSegments.push_back(segments[i]);
845 if (newSegments.size() > 1) {
846 if (m_debug) {
847 std::cout << " Determining overlaps of " << newSegments.size()
848 << " collinear segments.\n";
849 }
850 EliminateOverlaps(newSegments);
851 if (m_debug) {
852 std::cout << " " << newSegments.size()
853 << " segments after splitting/merging.\n";
854 }
855 }
856 for (const auto& segment : newSegments) {
857 double lambda = 0.;
858 if (segment.bc.first == Dielectric) {
859 // Dielectric-dielectric interface.
860 const int reg1 = segment.region1;
861 const int reg2 = segment.region2;
862 double eps1 = 1.;
863 if (reg1 < 0) {
864 if (m_medium) eps1 = m_medium->GetDielectricConstant();
865 } else if (m_regions[reg1].medium) {
866 eps1 = m_regions[reg1].medium->GetDielectricConstant();
867 }
868 double eps2 = 1.;
869 if (reg2 < 0) {
870 if (m_medium) eps2 = m_medium->GetDielectricConstant();
871 } else if (m_regions[reg2].medium) {
872 eps2 = m_regions[reg2].medium->GetDielectricConstant();
873 }
874 if (fabs(eps1 - eps2) < 1.e-6 * (1. + fabs(eps1) + fabs(eps2))) {
875 if (m_debug) std::cout << " Same epsilon. Skip.\n";
876 continue;
877 }
878 // Compute lambda.
879 lambda = (eps2 - eps1) / (eps1 + eps2);
880 if (m_debug) std::cout << " Lambda = " << lambda << "\n";
881 }
882 const int ndiv = segment.ndiv <= 0 ? m_nDivisions : segment.ndiv;
883 Discretise(segment, m_elements, lambda, ndiv);
884 }
885 }
886 std::vector<std::vector<double> > influenceMatrix;
887 std::vector<std::vector<double> > inverseMatrix;
888
889 bool converged = false;
890 unsigned int nIter = 0;
891 while (!converged) {
892 ++nIter;
893 if (m_autoSize) {
894 std::cout << m_className << "::Initialise: Iteration " << nIter << "\n";
895 }
896 const unsigned int nElements = m_elements.size();
897 const unsigned int nEntries = nElements + m_wires.size() + 1;
898 if (m_debug) {
899 std::cout << " " << nElements << " elements.\n"
900 << " Matrix has " << nEntries << " rows/columns.\n";
901 }
902 // Compute the influence matrix.
903 influenceMatrix.assign(nEntries, std::vector<double>(nEntries, 0.));
904 if (!ComputeInfluenceMatrix(influenceMatrix)) {
905 std::cerr << m_className << "::Initialise:\n"
906 << " Error computing the influence matrix.\n";
907 return false;
908 }
909
910 // Invert the influence matrix.
911 inverseMatrix.assign(nEntries, std::vector<double>(nEntries, 0.));
912 if (!InvertMatrix(influenceMatrix, inverseMatrix)) {
913 std::cerr << m_className << "::Initialise: Matrix inversion failed.\n";
914 return false;
915 }
916 if (m_debug) std::cout << " Matrix inversion ok.\n";
917
918 // Compute the right hand side vector (boundary conditions).
919 std::vector<double> boundaryConditions(nEntries, 0.);
920 for (unsigned int i = 0; i < nElements; ++i) {
921 if (m_elements[i].bc.first != Voltage) continue;
922 boundaryConditions[i] = m_elements[i].bc.second;
923 }
924 const unsigned int nWires = m_wires.size();
925 for (unsigned int i = 0; i < nWires; ++i) {
926 boundaryConditions[nElements + i] = m_wires[i].v;
927 }
928
929 // Solve for the charge distribution.
930 if (!Solve(inverseMatrix, boundaryConditions)) {
931 std::cerr << m_className << "::Initialise: Solution failed.\n";
932 return false;
933 }
934 if (m_debug) std::cout << " Solution ok.\n";
935 const double tol = 1.e-6 * fabs(vmax - vmin);
936 std::vector<bool> ok(nElements, true);
937 converged = CheckConvergence(tol, ok);
938 if (!m_autoSize) break;
939 if (nIter >= m_nMaxIterations) break;
940 for (unsigned int j = 0; j < nElements; ++j) {
941 if (!ok[j]) {
942 SplitElement(m_elements[j], m_elements);
943 if (m_debug) std::cout << " Splitting element " << j << ".\n";
944 }
945 }
946 }
947 // Sort the regions by depth (innermost first).
948 std::sort(m_regions.begin(), m_regions.end(),
949 [](const Region& lhs, const Region& rhs) {
950 return (lhs.depth > rhs.depth);
951 });
952 m_ready = true;
953 return true;
954}
955
956void ComponentNeBem2d::EliminateOverlaps(std::vector<Segment>& segments) {
957
958 if (segments.empty()) return;
959 const unsigned int nIn = segments.size();
960 // Find the first/last point along the line.
961 std::array<double, 2> x0 = segments[0].x0;
962 std::array<double, 2> x1 = segments[0].x1;
963 // Use x or y coordinate depending on the orientation of the line.
964 const unsigned int ic = fabs(x1[1] - x0[1]) > fabs(x1[0] - x0[0]) ? 1 : 0;
965 std::vector<bool> swapped(nIn, false);
966 for (unsigned int i = 0; i < nIn; ++i) {
967 const auto& seg = segments[i];
968 std::array<double, 2> u0 = seg.x0;
969 std::array<double, 2> u1 = seg.x1;
970 if (u0[ic] > u1[ic]) {
971 // Swap points.
972 std::swap(u0, u1);
973 swapped[i] = true;
974 }
975 if (u0[ic] < x0[ic]) x0 = u0;
976 if (u1[ic] > x1[ic]) x1 = u1;
977 }
978 const std::array<double, 2> d = {x1[0] - x0[0], x1[1] - x0[1]};
979
980 // Make a list of all points and their linear coordinate.
981 std::vector<std::pair<double, std::vector<unsigned int> > > points;
982 for (unsigned int i = 0; i < nIn; ++i) {
983 for (const auto& xl : {segments[i].x0, segments[i].x1}) {
984 const std::array<double, 2> d0 = {xl[0] - x0[0], xl[1] - x0[1]};
985 const std::array<double, 2> d1 = {x1[0] - xl[0], x1[1] - xl[1]};
986 double lambda = 0.;
987 if (Mag2(d0) < Mag2(d1)) {
988 // Point nearer to x0.
989 lambda = d0[ic] / d[ic];
990 } else {
991 // Point nearer to p1.
992 lambda = 1. - d1[ic] / d[ic];
993 }
994 // Add the point to the list.
995 bool found = false;
996 for (auto& point : points) {
997 if (fabs(point.first - lambda) < 1.e-6) {
998 found = true;
999 point.second.push_back(i);
1000 break;
1001 }
1002 }
1003 if (found) continue;
1004 points.push_back(std::make_pair(lambda,
1005 std::vector<unsigned int>({i})));
1006 }
1007 }
1008 // Sort the points by linear coordinate.
1009 std::sort(std::begin(points), std::end(points));
1010
1011 std::vector<Segment> newSegments;
1012 const unsigned int nPoints = points.size();
1013 std::array<double, 2> xl = {x0[0] + points[0].first * d[0],
1014 x0[1] + points[0].first * d[1]};
1015 std::vector<unsigned int> left = points[0].second;
1016 for (unsigned int i = 1; i < nPoints; ++i) {
1017 Segment seg = segments[left.front()];
1018 seg.x0 = xl;
1019 xl = {x0[0] + points[i].first * d[0], x0[1] + points[i].first * d[1]};
1020 seg.x1 = xl;
1021 if (swapped[left.front()]) std::swap(seg.x0, seg.x1);
1022 // Sort out the boundary conditions.
1023 if (left.size() > 1) {
1024 for (unsigned int j = 1; j < left.size(); ++j) {
1025 const auto& other = segments[left[j]];
1026 if (seg.bc.first == Dielectric) {
1027 if (other.bc.first == Dielectric) {
1028 // Dielectric-dielectric interface.
1029 if ((seg.x1[ic] - seg.x0[ic]) * (other.x1[ic] - other.x0[ic]) > 0) {
1030 // Same orientation.
1031 continue;
1032 }
1033 seg.region2 = other.region1;
1034 } else {
1035 seg.bc = other.bc;
1036 }
1037 } else if (seg.bc.first != other.bc.first) {
1038 std::cerr << m_className << "::EliminateOverlaps:\n"
1039 << " Warning: conflicting boundary conditions.\n";
1040 }
1041 }
1042 }
1043 newSegments.push_back(std::move(seg));
1044 for (unsigned int k : points[i].second) {
1045 const auto it = std::find(left.begin(), left.end(), k);
1046 if (it == left.end()) {
1047 left.push_back(k);
1048 } else {
1049 left.erase(it);
1050 }
1051 }
1052 }
1053 segments.swap(newSegments);
1054}
1055
1056bool ComponentNeBem2d::Discretise(const Segment& seg,
1057 std::vector<Element>& elements,
1058 const double lambda,
1059 const unsigned int ndiv) {
1060
1061 if (ndiv < 1) {
1062 std::cerr << m_className << "::Discretise: Number of elements < 1.\n";
1063 return false;
1064 }
1065 const double phi = atan2(seg.x1[1] - seg.x0[1], seg.x1[0] - seg.x0[0]);
1066 const double cphi = cos(phi);
1067 const double sphi = sin(phi);
1068 const double dx = (seg.x1[0] - seg.x0[0]) / ndiv;
1069 const double dy = (seg.x1[1] - seg.x0[1]) / ndiv;
1070 const double a = 0.5 * sqrt(dx * dx + dy * dy);
1071 double x = seg.x0[0] - 0.5 * dx;
1072 double y = seg.x0[1] - 0.5 * dy;
1073 for (unsigned int i = 0; i < ndiv; ++i) {
1074 x += dx;
1075 y += dy;
1076 Element element;
1077 element.cphi = cphi;
1078 element.sphi = sphi;
1079 element.x = x;
1080 element.y = y;
1081 element.a = a;
1082 element.bc = seg.bc;
1083 element.lambda = lambda;
1084 elements.push_back(std::move(element));
1085 }
1086 return true;
1087}
1088
1089bool ComponentNeBem2d::ComputeInfluenceMatrix(
1090 std::vector<std::vector<double> >& infmat) const {
1091
1092 const unsigned int nL = m_elements.size();
1093 const unsigned int nE = nL + m_wires.size();
1094 // Loop over the target elements (F).
1095 for (unsigned int iF = 0; iF < nE; ++iF) {
1096 const auto bcF = iF < nL ? m_elements[iF].bc.first : Voltage;
1097 const double cphiF = iF < nL ? m_elements[iF].cphi : 1.;
1098 const double sphiF = iF < nL ? m_elements[iF].sphi : 0.;
1099 // Collocation point.
1100 const double xF = iF < nL ? m_elements[iF].x : m_wires[iF - nL].x;
1101 const double yF = iF < nL ? m_elements[iF].y : m_wires[iF - nL].y;
1102
1103 // Loop over the source elements (S).
1104 for (unsigned int jS = 0; jS < nE; ++jS) {
1105 // Calculate the influence coefficient.
1106 double infCoeff = 0.;
1107 if (jS < nL) {
1108 // Straight line element.
1109 const auto& src = m_elements[jS];
1110 double xL = 0., yL = 0.;
1111 ToLocal(xF - src.x, yF - src.y, src.cphi, src.sphi, xL, yL);
1112 if (bcF == Voltage) {
1113 infCoeff = LinePotential(src.a, xL, yL);
1114 } else if (bcF == Dielectric) {
1115 // Dielectric-dielectric interface.
1116 // Normal component of the displacement vector is continuous.
1117 if (iF == jS) {
1118 // Self-influence.
1119 infCoeff = 1. / (2. * src.lambda * VacuumPermittivity);
1120 } else {
1121 // Compute flux at the collocation point.
1122 double fx = 0., fy = 0.;
1123 LineFlux(src.a, xL, yL, fx, fy);
1124 // Rotate to the global frame.
1125 ToGlobal(fx, fy, src.cphi, src.sphi, fx, fy);
1126 // Rotate to the local frame of the target element.
1127 ToLocal(fx, fy, cphiF, sphiF, fx, fy);
1128 infCoeff = fy;
1129 }
1130 }
1131 } else {
1132 // Wire.
1133 const auto& src = m_wires[jS - nL];
1134 if (bcF == Voltage) {
1135 infCoeff = WirePotential(src.r, xF - src.x, yF - src.y);
1136 } else if (bcF == Dielectric) {
1137 double fx = 0., fy = 0.;
1138 WireFlux(src.r, xF - src.x, yF - src.y, fx, fy);
1139 ToLocal(fx, fy, cphiF, sphiF, fx, fy);
1140 infCoeff = fy;
1141 }
1142 }
1143 infmat[iF][jS] = infCoeff;
1144 }
1145 }
1146
1147 // Add charge neutrality condition.
1148 for (unsigned int i = 0; i < nE; ++i) {
1149 if (i < nL) {
1150 infmat[nE][i] = m_elements[i].a;
1151 } else {
1152 infmat[nE][i] = m_wires[i - nL].r;
1153 }
1154 infmat[i][nE] = 0.;
1155 }
1156 infmat[nE][nE] = 0.;
1157
1158 return true;
1159}
1160
1161void ComponentNeBem2d::SplitElement(Element& oldElement,
1162 std::vector<Element>& elements) {
1163 oldElement.a *= 0.5;
1164
1165 Element newElement = oldElement;
1166 double dx = 0., dy = 0.;
1167 ToGlobal(newElement.a, 0., newElement.cphi, newElement.sphi, dx, dy);
1168 oldElement.x += dx;
1169 oldElement.y += dy;
1170 newElement.x -= dx;
1171 newElement.y -= dx;
1172
1173 elements.push_back(std::move(newElement));
1174}
1175
1176bool ComponentNeBem2d::InvertMatrix(
1177 std::vector<std::vector<double> >& influenceMatrix,
1178 std::vector<std::vector<double> >& inverseMatrix) const {
1179
1180 const unsigned int nEntries = influenceMatrix.size();
1181
1182 // Temporary arrays for LU decomposition/substitution
1183 std::vector<double> col(nEntries, 0.);
1184 std::vector<int> index(nEntries, 0);
1185
1186 // Decompose the influence matrix
1187 if (!LUDecomposition(influenceMatrix, index)) {
1188 std::cerr << m_className << "::InvertMatrix: LU decomposition failed.\n";
1189 return false;
1190 }
1191
1192 // Initialise the inverse influence matrix
1193 inverseMatrix.assign(nEntries, std::vector<double>(nEntries, 0.));
1194 // Invert the matrix.
1195 for (unsigned int j = 0; j < nEntries; ++j) {
1196 col.assign(nEntries, 0.);
1197 col[j] = 1.;
1198 LUSubstitution(influenceMatrix, index, col);
1199 for (unsigned int i = 0; i < nEntries; ++i) inverseMatrix[i][j] = col[i];
1200 }
1201
1202 // Clear the influence matrix.
1203 influenceMatrix.clear();
1204
1205 return true;
1206}
1207
1208bool ComponentNeBem2d::LUDecomposition(std::vector<std::vector<double> >& mat,
1209 std::vector<int>& index) const {
1210 // The influence matrix is replaced by the LU decomposition of a rowwise
1211 // permutation of itself. The implementation is based on:
1212 // W. H. Press,
1213 // Numerical recipes in C++: the Art of Scientific Computing (version 2.11)
1214
1215 const unsigned int n = m_elements.size() + m_wires.size();
1216 // v stores the implicit scaling of each row
1217 std::vector<double> v(n, 0.);
1218
1219 // Loop over rows to get the implicit scaling information.
1220 for (unsigned int i = 0; i < n; ++i) {
1221 double big = 0.;
1222 for (unsigned int j = 0; j < n; ++j) {
1223 big = std::max(big, fabs(mat[i][j]));
1224 }
1225 if (big == 0.) return false;
1226 // Save the scaling
1227 v[i] = 1. / big;
1228 }
1229
1230 // Loop over columns
1231 unsigned int imax = 0;
1232 for (unsigned int j = 0; j < n; ++j) {
1233 for (unsigned int i = 0; i < j; ++i) {
1234 double sum = mat[i][j];
1235 for (unsigned int k = 0; k < i; ++k) {
1236 sum -= mat[i][k] * mat[k][j];
1237 }
1238 mat[i][j] = sum;
1239 }
1240 // Initialise for the search for the largest pivot element
1241 double big = 0.;
1242 for (unsigned int i = j; i < n; ++i) {
1243 double sum = mat[i][j];
1244 for (unsigned int k = 0; k < j; ++k) {
1245 sum -= mat[i][k] * mat[k][j];
1246 }
1247 mat[i][j] = sum;
1248 // Is the figure of merit for the pivot better than the best so far?
1249 const double dum = v[i] * fabs(sum);
1250 if (dum >= big) {
1251 big = dum;
1252 imax = i;
1253 }
1254 }
1255 // Do we need to interchange rows?
1256 if (j != imax) {
1257 for (unsigned k = 0; k < n; ++k) {
1258 const double dum = mat[imax][k];
1259 mat[imax][k] = mat[j][k];
1260 mat[j][k] = dum;
1261 }
1262 // Interchange the scale factor
1263 v[imax] = v[j];
1264 }
1265 index[j] = imax;
1266 if (mat[j][j] == 0.) mat[j][j] = Small;
1267 if (j != n - 1) {
1268 // Divide by the pivot element
1269 const double dum = 1. / mat[j][j];
1270 for (unsigned int i = j + 1; i < n; ++i) {
1271 mat[i][j] *= dum;
1272 }
1273 }
1274 }
1275
1276 return true;
1277}
1278
1279void ComponentNeBem2d::LUSubstitution(
1280 const std::vector<std::vector<double> >& mat, const std::vector<int>& index,
1281 std::vector<double>& col) const {
1282
1283 const unsigned int n = m_elements.size() + m_wires.size();
1284 unsigned int ii = 0;
1285 // Forward substitution
1286 for (unsigned i = 0; i < n; ++i) {
1287 const unsigned int ip = index[i];
1288 double sum = col[ip];
1289 col[ip] = col[i];
1290 if (ii != 0) {
1291 for (unsigned j = ii - 1; j < i; ++j) {
1292 sum -= mat[i][j] * col[j];
1293 }
1294 } else if (sum != 0.) {
1295 ii = i + 1;
1296 }
1297 col[i] = sum;
1298 }
1299
1300 // Backsubstitution
1301 for (int i = n - 1; i >= 0; i--) {
1302 double sum = col[i];
1303 for (unsigned j = i + 1; j < n; ++j) {
1304 sum -= mat[i][j] * col[j];
1305 }
1306 col[i] = sum / mat[i][i];
1307 }
1308}
1309
1310bool ComponentNeBem2d::Solve(const std::vector<std::vector<double> >& invmat,
1311 const std::vector<double>& bc) {
1312 const unsigned int nEntries = bc.size();
1313 const unsigned int nElements = m_elements.size();
1314 for (unsigned int i = 0; i < nElements; ++i) {
1315 double solution = 0.;
1316 for (unsigned int j = 0; j < nEntries; ++j) {
1317 solution += invmat[i][j] * bc[j];
1318 }
1319 m_elements[i].q = solution;
1320 }
1321 const unsigned int nWires = m_wires.size();
1322 for (unsigned int i = 0; i < nWires; ++i) {
1323 double solution = 0.;
1324 for (unsigned int j = 0; j < nEntries; ++j) {
1325 solution += invmat[nElements + i][j] * bc[j];
1326 }
1327 m_wires[i].q = solution;
1328 }
1329
1330 if (m_debug) {
1331 std::cout << m_className << "::Solve:\n Element Solution\n";
1332 for (unsigned int i = 0; i < nElements; ++i) {
1333 std::printf(" %8u %15.5f\n", i, m_elements[i].q);
1334 }
1335 if (!m_wires.empty()) {
1336 std::cout << " Wire Solution\n";
1337 for (unsigned int i = 0; i < nWires; ++i) {
1338 std::printf(" %8u %15.5f\n", i, m_wires[i].q);
1339 }
1340 }
1341 }
1342 return true;
1343}
1344
1345bool ComponentNeBem2d::CheckConvergence(const double tol,
1346 std::vector<bool>& ok) {
1347
1348 // Potential and normal component of the electric field
1349 // evaluated at the collocation points.
1350 std::vector<double> v(m_nCollocationPoints, 0.);
1351 std::vector<double> n(m_nCollocationPoints, 0.);
1352
1353 if (m_debug) {
1354 std::cout << m_className << "::CheckConvergence:\n"
1355 << " element # type LHS RHS\n";
1356 }
1357 const double scale = 1. / m_nCollocationPoints;
1358 unsigned int i = 0;
1359 for (const auto& tgt : m_elements) {
1360 v.assign(m_nCollocationPoints, 0.);
1361 n.assign(m_nCollocationPoints, 0.);
1362 double dx = 0., dy = 0.;
1363 ToGlobal(2 * tgt.a, 0., tgt.cphi, tgt.sphi, dx, dy);
1364 const double x0 = tgt.x - 0.5 * dx;
1365 const double y0 = tgt.y - 0.5 * dy;
1366 // Loop over the collocation points.
1367 for (unsigned int k = 0; k < m_nCollocationPoints; ++k) {
1368 double xG = x0;
1369 double yG = y0;
1370 if (m_randomCollocation) {
1371 const double r = RndmUniformPos();
1372 xG += r * dx;
1373 yG += r * dy;
1374 } else {
1375 const double s = (k + 1.) / (m_nCollocationPoints + 1.);
1376 xG += s * dx;
1377 yG += s * dy;
1378 }
1379 // Sum up the contributions from all boundary elements.
1380 for (const auto& src : m_elements) {
1381 double xL = 0., yL = 0.;
1382 // Transform to local coordinate system.
1383 ToLocal(xG - src.x, yG - src.y, src.cphi, src.sphi, xL, yL);
1384 // Compute the potential.
1385 v[k] += LinePotential(src.a, xL, yL) * src.q;
1386 // Compute the field.
1387 double fx = 0., fy = 0.;
1388 LineFlux(src.a, xL, yL, fx, fy);
1389 // Rotate to the global frame.
1390 ToGlobal(fx, fy, src.cphi, src.sphi, fx, fy);
1391 // Rotate to the local frame of the test element.
1392 ToLocal(fx, fy, tgt.cphi, tgt.sphi, fx, fy);
1393 n[k] += fy * src.q;
1394 }
1395
1396 for (const auto& src : m_wires) {
1397 // Compute the potential.
1398 v[k] += WirePotential(src.r, xG - src.x, yG - src.y) * src.q;
1399 // Compute the field.
1400 double fx = 0., fy = 0.;
1401 WireFlux(src.r, xG - src.x, yG - src.y, fx, fy);
1402 // Rotate to the local frame of the test element.
1403 ToLocal(fx, fy, tgt.cphi, tgt.sphi, fx, fy);
1404 n[k] += fy * src.q;
1405 }
1406 }
1407 const double v0 = scale * std::accumulate(v.begin(), v.end(), 0.);
1408 const double n0 = scale * std::accumulate(n.begin(), n.end(), 0.);
1409 double n1 = 0.;
1410 if (tgt.bc.first == Voltage) {
1411 const double dv = v0 - tgt.bc.second;
1412 if (fabs(dv) > tol) ok[i] = false;
1413 if (m_debug) {
1414 std::printf(" %8u cond. %15.5f %15.5f %15.5f\n",
1415 i, v0, tgt.bc.second, dv);
1416 }
1417 } else if (tgt.bc.first == Dielectric) {
1418 // Dielectric-dielectric interface
1419 // TODO.
1420 n1 = n0 + 0.5 * InvEpsilon0 * tgt.q / tgt.lambda;
1421 if (m_debug) std::printf(" %8u diel. %15.5f %15.5f\n", i, n0, n1);
1422 }
1423 ++i;
1424 }
1425
1426 for (const auto& tgt : m_wires) {
1427 v.assign(m_nCollocationPoints, 0.);
1428 double x0 = tgt.x;
1429 double y0 = tgt.y;
1430 // Loop over the collocation points.
1431 for (unsigned int k = 0; k < m_nCollocationPoints; ++k) {
1432 const double phi = TwoPi * RndmUniform();
1433 const double xG = x0 + tgt.r * cos(phi);
1434 const double yG = y0 + tgt.r * sin(phi);
1435 // Sum up the contributions from all boundary elements.
1436 for (const auto& src : m_elements) {
1437 // Transform to local coordinate system.
1438 double xL = 0., yL = 0.;
1439 ToLocal(xG - src.x, yG - src.y, src.cphi, src.sphi, xL, yL);
1440 // Compute the potential.
1441 v[k] += LinePotential(src.a, xL, yL) * src.q;
1442 }
1443 for (const auto& src : m_wires) {
1444 v[k] += WirePotential(src.r, xG - src.x, yG - src.y) * src.q;
1445 }
1446 }
1447 const double v0 = scale * std::accumulate(v.begin(), v.end(), 0.);
1448 if (m_debug) {
1449 std::printf(" %8u wire %15.5f %15.5f\n", i, v0, tgt.v);
1450 }
1451 ++i;
1452 }
1453
1454 return true;
1455}
1456
1457double ComponentNeBem2d::LinePotential(const double a, const double x,
1458 const double y) const {
1459 double p = 0.;
1460 const double amx = a - x;
1461 const double apx = a + x;
1462 if (fabs(y) > Small) {
1463 const double y2 = y * y;
1464 p = 2. * a - y * (atan(amx / y) + atan(apx / y)) -
1465 0.5 * amx * log(amx * amx + y2) - 0.5 * apx * log(apx * apx + y2);
1466 } else if (fabs(x) != a) {
1467 p = 2. * a - 0.5 * amx * log(amx * amx) - 0.5 * apx * log(apx * apx);
1468 } else {
1469 p = 2. * a * (1. - log(2. * a));
1470 }
1471
1472 return InvTwoPiEpsilon0 * p;
1473}
1474
1475double ComponentNeBem2d::WirePotential(const double r0, const double x,
1476 const double y) const {
1477 const double r = sqrt(x * x + y * y);
1478 if (r >= r0) {
1479 return -log(r) * r0 * InvEpsilon0;
1480 }
1481
1482 // Inside the wire the potential is constant
1483 return -log(r0) * r0 * InvEpsilon0;
1484}
1485
1486void ComponentNeBem2d::LineFlux(const double a, const double x, const double y,
1487 double& ex, double& ey) const {
1488 const double amx = a - x;
1489 const double apx = a + x;
1490 if (fabs(y) > 0.) {
1491 const double y2 = y * y;
1492 ex = 0.5 * log((apx * apx + y2) / (amx * amx + y2));
1493 ey = atan(amx / y) + atan(apx / y);
1494 } else if (fabs(x) != a) {
1495 ex = 0.5 * log(apx * apx / (amx * amx));
1496 ey = 0.;
1497 } else {
1498 // Singularity at the end points of the line
1499 constexpr double eps2 = 1.e-24;
1500 ex = 0.25 * log(pow(apx * apx - eps2, 2) / pow(amx * amx - eps2, 2));
1501 ey = 0.;
1502 }
1503 ex *= InvTwoPiEpsilon0;
1504 ey *= InvTwoPiEpsilon0;
1505}
1506
1507void ComponentNeBem2d::WireFlux(const double r0, const double x, const double y,
1508 double& ex, double& ey) const {
1509 const double r02 = r0 * r0;
1510 const double r2 = x * x + y * y;
1511 if (r2 > r02) {
1512 ex = x * r0 / r2;
1513 ey = y * r0 / r2;
1514 } else if (r2 == r02) {
1515 ex = 0.5 * x / r0;
1516 ey = 0.5 * y / r0;
1517 } else {
1518 // Inside the wire the field is zero.
1519 ex = ey = 0.;
1520 return;
1521 }
1522 ex *= InvEpsilon0;
1523 ey *= InvEpsilon0;
1524}
1525
1526void ComponentNeBem2d::Reset() {
1527 m_regions.clear();
1528 m_segments.clear();
1529 m_wires.clear();
1530 m_elements.clear();
1531 m_ready = false;
1532}
1533
1534void ComponentNeBem2d::UpdatePeriodicity() {
1535 std::cerr << m_className << "::UpdatePeriodicity:\n"
1536 << " Periodicities are not supported.\n";
1537}
1538
1539void ComponentNeBem2d::ToLocal(const double xIn, const double yIn,
1540 const double cphi, const double sphi,
1541 double& xOut, double& yOut) const {
1542 if (fabs(sphi) < 1.e-12) {
1543 xOut = xIn;
1544 yOut = yIn;
1545 return;
1546 }
1547 xOut = +cphi * xIn + sphi * yIn;
1548 yOut = -sphi * xIn + cphi * yIn;
1549}
1550
1551void ComponentNeBem2d::ToGlobal(const double xIn, const double yIn,
1552 const double cphi, const double sphi,
1553 double& xOut, double& yOut) const {
1554 if (fabs(sphi) < 1.e-12) {
1555 xOut = xIn;
1556 yOut = yIn;
1557 return;
1558 }
1559 xOut = cphi * xIn - sphi * yIn;
1560 yOut = sphi * xIn + cphi * yIn;
1561}
1562
1563}
Abstract base class for components.
GeometryBase * m_geometry
Pointer to the geometry.
bool m_ready
Ready for use?
std::string m_className
Class name.
bool m_debug
Switch on/off debugging messages.
void SetNumberOfDivisions(const unsigned int ndiv)
Set the default number of elements per segment.
bool AddSegment(const double x0, const double y0, const double x1, const double y1, const double v, const int ndiv=-1)
void ElectricField(const double x, const double y, const double z, double &ex, double &ey, double &ez, Medium *&m, int &status) override
bool GetVoltageRange(double &vmin, double &vmax) override
Calculate the voltage range [V].
bool GetSegment(const unsigned int i, double &x0, double &y0, double &x1, double &x2, double &v) const
Return the coordinates and voltage of a given straight-line segment.
bool GetBoundingBox(double &xmin, double &ymin, double &zmin, double &xmax, double &ymax, double &zmax) override
Get the bounding box coordinates.
void SetRangeZ(const double zmin, const double zmax)
Set the extent of the drift region along z.
void SetNumberOfCollocationPoints(const unsigned int ncoll)
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
bool Initialise()
Discretise the geometry and compute the solution.
bool GetRegion(const unsigned int i, std::vector< double > &xv, std::vector< double > &yv, Medium *&medium, unsigned int &bctype, double &v)
Return the properties of a given region.
Medium * GetMedium(const double x, const double y, const double z) override
Get the medium at a given location (x, y, z).
bool GetWire(const unsigned int i, double &x, double &y, double &d, double &v, double &q) const
Return the coordinates, diameter, potential and charge of a given wire.
bool AddWire(const double x, const double y, const double d, const double v, const int ntrap=5)
void SetMaxNumberOfIterations(const unsigned int niter)
bool IsInTrapRadius(const double q0, const double x0, const double y0, const double z0, double &xw, double &yx, double &rw) override
bool AddRegion(const std::vector< double > &xp, const std::vector< double > &yp, Medium *medium, const unsigned int bctype=4, const double v=0., const int ndiv=-1)
bool GetElement(const unsigned int i, double &x0, double &y0, double &x1, double &y1, double &q) const
Return the coordinates and charge of a given boundary element.
virtual Medium * GetMedium(const double x, const double y, const double z) const =0
Retrieve the medium at a given point.
virtual bool GetBoundingBox(double &xmin, double &ymin, double &zmin, double &xmax, double &ymax, double &zmax)=0
Get the bounding box (envelope of the geometry).
Abstract base class for media.
Definition: Medium.hh:13
double GetDielectricConstant() const
Get the relative static dielectric constant.
Definition: Medium.hh:42
virtual bool IsConductor() const
Is this medium a conductor?
Definition: Medium.hh:29
double Area(const std::vector< double > &xp, const std::vector< double > &yp)
Determine the (signed) area of a polygon.
Definition: Polygon.cc:222
void Inside(const std::vector< double > &xpl, const std::vector< double > &ypl, const double x, const double y, bool &inside, bool &edge)
Definition: Polygon.cc:135
double RndmUniform()
Draw a random number uniformly distributed in the range [0, 1).
Definition: Random.hh:14
double RndmUniformPos()
Draw a random number uniformly distributed in the range (0, 1).
Definition: Random.hh:17
DoubleAc cos(const DoubleAc &f)
Definition: DoubleAc.cpp:432
DoubleAc pow(const DoubleAc &f, double p)
Definition: DoubleAc.cpp:337
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