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