Garfield++ 4.0
A toolkit for the detailed simulation of particle detectors based on ionisation measurement in gases and semiconductors
Loading...
Searching...
No Matches
ComponentComsol.cc
Go to the documentation of this file.
1#include <math.h>
2#include <stdlib.h>
3
4#include <fstream>
5#include <iostream>
6#include <map>
7#include <sstream>
8
10#include "Garfield/KDTree.hh"
11
12namespace {
13
14bool ends_with(std::string s, std::string t) {
15 if (!s.empty() && s.back() == '\r') s.pop_back();
16 return s.size() >= t.size() && s.substr(s.size() - t.size(), t.size()) == t;
17}
18
19bool isComment(const std::string& line) {
20 if (line.empty()) return false;
21 if (line[0] == '%') return true;
22 return false;
23}
24
25int readInt(std::string s) {
26 std::istringstream iss(s);
27 int ret;
28 iss >> ret;
29 return ret;
30}
31
32void PrintProgress(const double f) {
33 if (f < 0.) return;
34 constexpr unsigned int width = 70;
35 const unsigned int n = static_cast<unsigned int>(std::floor(width * f));
36 std::string bar = "[";
37 if (n < 1) {
38 bar += std::string(width, ' ');
39 } else if (n >= width) {
40 bar += std::string(width, '=');
41 } else {
42 bar += std::string(n, '=') + ">" + std::string(width - n - 1, ' ');
43 }
44 bar += "]";
45 std::cout << bar << "\r" << std::flush;
46}
47
48} // namespace
49
50namespace Garfield {
51
53
54ComponentComsol::ComponentComsol(const std::string& mesh,
55 const std::string& mplist,
56 const std::string& field,
57 const std::string& unit)
58 : ComponentComsol() {
59 Initialise(mesh, mplist, field, unit);
60}
61
62bool ComponentComsol::Initialise(const std::string& mesh,
63 const std::string& mplist,
64 const std::string& field,
65 const std::string& unit) {
66 Reset();
67
68 // Get the conversion factor to be applied to the coordinates.
69 m_unit = ScalingFactor(unit);
70 if (m_unit <= 0.) {
71 std::cerr << m_className << "::Initialise:\n Unknown length unit "
72 << unit << ". Will use default (m).\n";
73 m_unit = 100.;
74 }
75 // Open the materials file.
76 m_materials.clear();
77 std::ifstream fmplist;
78 fmplist.open(mplist.c_str(), std::ios::in);
79 if (fmplist.fail()) {
80 PrintCouldNotOpen("Initialise", mplist);
81 return false;
82 }
83 unsigned int nMaterials;
84 fmplist >> nMaterials;
85 for (unsigned int i = 0; i < nMaterials; ++i) {
86 Material material;
87 material.driftmedium = false;
88 material.medium = nullptr;
89 material.ohm = -1;
90 fmplist >> material.eps;
91 m_materials.push_back(std::move(material));
92 }
93 if (m_materials.empty()) {
94 // Add default material
95 Material material;
96 material.driftmedium = false;
97 material.medium = nullptr;
98 material.eps = material.ohm = -1;
99 m_materials.push_back(std::move(material));
100 nMaterials = 1;
101 }
102 std::map<int, int> domain2material;
103 int d2msize;
104 fmplist >> d2msize;
105 for (int i = 0; i < d2msize; ++i) {
106 int domain;
107 fmplist >> domain;
108 fmplist >> domain2material[domain];
109 }
110 fmplist.close();
111
112 // Find lowest epsilon, check for eps = 0, set default drift medium.
113 if (!SetDefaultDriftMedium()) return false;
114
115 m_nodes.clear();
116 std::ifstream fmesh;
117 fmesh.open(mesh.c_str(), std::ios::in);
118 if (fmesh.fail()) {
119 PrintCouldNotOpen("Initialise", mesh);
120 return false;
121 }
122
123 std::string line;
124 do {
125 if (!std::getline(fmesh, line)) {
126 std::cerr << m_className << "::Initialise:\n"
127 << " Could not read number of nodes from " << mesh << ".\n";
128 fmesh.close();
129 return false;
130 }
131 } while (!ends_with(line, "# number of mesh points") &&
132 !ends_with(line, "# number of mesh vertices"));
133 const int nNodes = readInt(line);
134
135 std::cout << m_className << "::Initialise: " << nNodes << " nodes.\n";
136 do {
137 if (!std::getline(fmesh, line)) {
138 std::cerr << m_className << "::Initialise:\n"
139 << " Error parsing " << mesh << ".\n";
140 fmesh.close();
141 return false;
142 }
143 } while (line.find("# Mesh point coordinates") == std::string::npos &&
144 line.find("# Mesh vertex coordinates") == std::string::npos);
145 for (int i = 0; i < nNodes; ++i) {
146 Node newNode;
147 fmesh >> newNode.x >> newNode.y >> newNode.z;
148 newNode.x *= m_unit;
149 newNode.y *= m_unit;
150 newNode.z *= m_unit;
151 m_nodes.push_back(std::move(newNode));
152 }
153
154 do {
155 if (!std::getline(fmesh, line)) {
156 std::cerr << m_className << "::Initialise:\n"
157 << " Error parsing " << mesh << ".\n";
158 fmesh.close();
159 return false;
160 }
161 } while (line.find("4 tet2 # type name") == std::string::npos);
162 do {
163 if (!std::getline(fmesh, line)) {
164 std::cerr << m_className << "::Initialise:\n"
165 << " Error parsing " << mesh << ".\n";
166 fmesh.close();
167 return false;
168 }
169 } while (!ends_with(line, "# number of elements"));
170 const int nElements = readInt(line);
171 m_elements.clear();
172 std::cout << m_className << "::Initialise: " << nElements << " elements.\n";
173 std::getline(fmesh, line);
174 // Elements 6 & 7 are swapped due to differences in COMSOL and ANSYS
175 // representation
176 int perm[10] = {0, 1, 2, 3, 4, 5, 7, 6, 8, 9};
177 for (int i = 0; i < nElements; ++i) {
178 Element newElement;
179 newElement.degenerate = false;
180 for (int j = 0; j < 10; ++j) {
181 fmesh >> newElement.emap[perm[j]];
182 }
183 m_elements.push_back(std::move(newElement));
184 }
185
186 do {
187 if (!std::getline(fmesh, line)) {
188 std::cerr << m_className << "::Initialise:\n"
189 << " Error parsing " << mesh << ".\n";
190 fmesh.close();
191 return false;
192 }
193 } while (line.find("# Geometric entity indices") == std::string::npos);
194 for (auto& element : m_elements) {
195 int domain;
196 fmesh >> domain;
197 element.matmap = domain2material.count(domain) ? domain2material[domain]
198 : nMaterials - 1;
199 }
200 fmesh.close();
201
202 std::ifstream ffield;
203 ffield.open(field.c_str(), std::ios::in);
204 if (ffield.fail()) {
205 PrintCouldNotOpen("Initialise", field);
206 return false;
207 }
208
209 const std::string hdr1 =
210 "% x y z "
211 " V (V)";
212
213 const std::string hdr2 =
214 "% x y z V (V)";
215 do {
216 if (!std::getline(ffield, line)) {
217 std::cerr << m_className << "::Initialise:\n"
218 << " Error parsing " << field << ".\n";
219 ffield.close();
220 return false;
221 }
222 } while (line.find(hdr1) == std::string::npos &&
223 line.find(hdr2) == std::string::npos);
224 std::istringstream sline(line);
225 std::string token;
226 sline >> token; // %
227 sline >> token; // x
228 sline >> token; // y
229 sline >> token; // z
230 sline >> token; // V
231 sline >> token; // (V)
232 m_wfields.clear();
233 m_wfieldsOk.clear();
234 while (sline >> token) {
235 std::cout << m_className << "::Initialise:\n"
236 << " Reading data for weighting field " << token << ".\n";
237 m_wfields.push_back(token);
238 m_wfieldsOk.push_back(true);
239 sline >> token; // (V)
240 }
241 const size_t nWeightingFields = m_wfields.size();
242
243 const unsigned int nPrint =
244 std::pow(10, static_cast<unsigned int>(
245 std::max(std::floor(std::log10(nNodes)) - 1, 1.)));
246 std::cout << m_className << "::Initialise: Reading potentials.\n";
247 PrintProgress(0.);
248 // Build a k-d tree from the node coordinates.
249 std::vector<std::vector<double> > points;
250 for (const auto& node : m_nodes) {
251 std::vector<double> point = {node.x, node.y, node.z};
252 points.push_back(std::move(point));
253 }
254 KDTree kdtree(points);
255 std::vector<bool> used(nNodes, false);
256 for (int i = 0; i < nNodes; ++i) {
257 double x, y, z, v;
258 ffield >> x >> y >> z >> v;
259 x *= m_unit;
260 y *= m_unit;
261 z *= m_unit;
262 std::vector<double> w;
263 for (size_t j = 0; j < nWeightingFields; ++j) {
264 double p;
265 ffield >> p;
266 w.push_back(p);
267 }
268 const std::vector<double> pt = {x, y, z};
269 std::vector<KDTreeResult> res;
270 kdtree.n_nearest(pt, 1, res);
271 if (res.empty()) {
272 std::cerr << std::endl
273 << m_className << "::Initialise:\n"
274 << " Could not find a matching mesh node for point (" << x
275 << ", " << y << ", " << z << ")\n.";
276 ffield.close();
277 return false;
278 }
279 const size_t k = res[0].idx;
280 used[k] = true;
281 m_nodes[k].v = v;
282 m_nodes[k].w = w;
283 if ((i + 1) % nPrint == 0) PrintProgress(double(i + 1) / nNodes);
284 }
285 PrintProgress(1.);
286 ffield.close();
287 auto nMissing = std::count(used.begin(), used.end(), false);
288 if (nMissing > 0) {
289 std::cerr << std::endl
290 << m_className << "::Initialise:\n"
291 << " Missing potentials for " << nMissing << " nodes.\n";
292 return false;
293 }
294
295 m_ready = true;
296 Prepare();
297 std::cout << std::endl << m_className << "::Initialise: Done.\n";
298 return true;
299}
300
301bool ComponentComsol::SetWeightingField(const std::string& field,
302 const std::string& label) {
303 if (!m_ready) {
304 std::cerr << m_className << "::SetWeightingField:\n"
305 << " No valid field map is present.\n"
306 << " Weighting fields cannot be added.\n";
307 return false;
308 }
309
310 // Open the voltage list.
311 std::ifstream ffield;
312 ffield.open(field.c_str(), std::ios::in);
313 if (ffield.fail()) {
314 PrintCouldNotOpen("SetWeightingField", field);
315 return false;
316 }
317
318 // Check if a weighting field with the same label already exists.
319 const size_t iw = GetOrCreateWeightingFieldIndex(label);
320 if (iw + 1 != m_wfields.size()) {
321 std::cout << m_className << "::SetWeightingField:\n"
322 << " Replacing existing weighting field " << label << ".\n";
323 }
324 m_wfieldsOk[iw] = false;
325
326 // Build a k-d tree from the node coordinates.
327 std::vector<std::vector<double> > points;
328 for (const auto& node : m_nodes) {
329 std::vector<double> point = {node.x, node.y, node.z};
330 points.push_back(std::move(point));
331 }
332 KDTree kdtree(points);
333
334 const std::string hdr =
335 "% x y z "
336 " V (V)";
337 std::string line;
338 do {
339 if (!std::getline(ffield, line)) {
340 std::cerr << m_className << "::SetWeightingField:\n"
341 << " Error parsing " << field << ".\n";
342 ffield.close();
343 return false;
344 }
345 } while (line.find(hdr) == std::string::npos);
346 const int nNodes = m_nodes.size();
347 for (int i = 0; i < nNodes; ++i) {
348 double x, y, z, v;
349 ffield >> x >> y >> z >> v;
350 x *= m_unit;
351 y *= m_unit;
352 z *= m_unit;
353 // Find the closest mesh node.
354 const std::vector<double> pt = {x, y, z};
355 std::vector<KDTreeResult> res;
356 kdtree.n_nearest(pt, 1, res);
357 if (res.empty()) {
358 std::cerr << m_className << "::SetWeightingField:\n"
359 << " Could not find a matching mesh node for point (" << x
360 << ", " << y << ", " << z << ")\n.";
361 ffield.close();
362 return false;
363 }
364 const size_t k = res[0].idx;
365 m_nodes[k].w[iw] = v;
366 }
367 ffield.close();
368 return true;
369}
370
371void ComponentComsol::ElectricField(const double x, const double y,
372 const double z, double& ex, double& ey,
373 double& ez, Medium*& m, int& status) {
374 double v = 0.;
375 ElectricField(x, y, z, ex, ey, ez, v, m, status);
376}
377
378void ComponentComsol::ElectricField(const double xin, const double yin,
379 const double zin, double& ex, double& ey,
380 double& ez, double& volt, Medium*& m,
381 int& status) {
382 // Copy the coordinates
383 double x = xin, y = yin, z = zin;
384
385 // Map the coordinates onto field map coordinates
386 bool xmirr, ymirr, zmirr;
387 double rcoordinate, rotation;
388 MapCoordinates(x, y, z, xmirr, ymirr, zmirr, rcoordinate, rotation);
389
390 // Initial values
391 ex = ey = ez = volt = 0.;
392 status = 0;
393 m = nullptr;
394
395 // Do not proceed if not properly initialised.
396 if (!m_ready) {
397 status = -10;
398 PrintNotReady("ElectricField");
399 return;
400 }
401
402 if (m_warning) PrintWarning("ElectricField");
403
404 // Find the element that contains this point
405 double t1, t2, t3, t4, jac[4][4], det;
406 const int imap = FindElement13(x, y, z, t1, t2, t3, t4, jac, det);
407 if (imap < 0) {
408 if (m_debug) {
409 std::cout << m_className << "::ElectricField:\n"
410 << " Point (" << x << ", " << y << ", " << z
411 << " not in the mesh.\n";
412 }
413 status = -6;
414 return;
415 }
416
417 const Element& element = m_elements[imap];
418 if (m_debug) {
419 PrintElement("ElectricField", x, y, z, t1, t2, t3, t4, element, 10);
420 }
421 const Node& n0 = m_nodes[element.emap[0]];
422 const Node& n1 = m_nodes[element.emap[1]];
423 const Node& n2 = m_nodes[element.emap[2]];
424 const Node& n3 = m_nodes[element.emap[3]];
425 const Node& n4 = m_nodes[element.emap[4]];
426 const Node& n5 = m_nodes[element.emap[5]];
427 const Node& n6 = m_nodes[element.emap[6]];
428 const Node& n7 = m_nodes[element.emap[7]];
429 const Node& n8 = m_nodes[element.emap[8]];
430 const Node& n9 = m_nodes[element.emap[9]];
431 // Tetrahedral field
432 volt = n0.v * t1 * (2 * t1 - 1) + n1.v * t2 * (2 * t2 - 1) +
433 n2.v * t3 * (2 * t3 - 1) + n3.v * t4 * (2 * t4 - 1) +
434 4 * n4.v * t1 * t2 + 4 * n5.v * t1 * t3 + 4 * n6.v * t1 * t4 +
435 4 * n7.v * t2 * t3 + 4 * n8.v * t2 * t4 + 4 * n9.v * t3 * t4;
436 ex = -(n0.v * (4 * t1 - 1) * jac[0][1] + n1.v * (4 * t2 - 1) * jac[1][1] +
437 n2.v * (4 * t3 - 1) * jac[2][1] + n3.v * (4 * t4 - 1) * jac[3][1] +
438 n4.v * (4 * t2 * jac[0][1] + 4 * t1 * jac[1][1]) +
439 n5.v * (4 * t3 * jac[0][1] + 4 * t1 * jac[2][1]) +
440 n6.v * (4 * t4 * jac[0][1] + 4 * t1 * jac[3][1]) +
441 n7.v * (4 * t3 * jac[1][1] + 4 * t2 * jac[2][1]) +
442 n8.v * (4 * t4 * jac[1][1] + 4 * t2 * jac[3][1]) +
443 n9.v * (4 * t4 * jac[2][1] + 4 * t3 * jac[3][1])) /
444 det;
445 ey = -(n0.v * (4 * t1 - 1) * jac[0][2] + n1.v * (4 * t2 - 1) * jac[1][2] +
446 n2.v * (4 * t3 - 1) * jac[2][2] + n3.v * (4 * t4 - 1) * jac[3][2] +
447 n4.v * (4 * t2 * jac[0][2] + 4 * t1 * jac[1][2]) +
448 n5.v * (4 * t3 * jac[0][2] + 4 * t1 * jac[2][2]) +
449 n6.v * (4 * t4 * jac[0][2] + 4 * t1 * jac[3][2]) +
450 n7.v * (4 * t3 * jac[1][2] + 4 * t2 * jac[2][2]) +
451 n8.v * (4 * t4 * jac[1][2] + 4 * t2 * jac[3][2]) +
452 n9.v * (4 * t4 * jac[2][2] + 4 * t3 * jac[3][2])) /
453 det;
454 ez = -(n0.v * (4 * t1 - 1) * jac[0][3] + n1.v * (4 * t2 - 1) * jac[1][3] +
455 n2.v * (4 * t3 - 1) * jac[2][3] + n3.v * (4 * t4 - 1) * jac[3][3] +
456 n4.v * (4 * t2 * jac[0][3] + 4 * t1 * jac[1][3]) +
457 n5.v * (4 * t3 * jac[0][3] + 4 * t1 * jac[2][3]) +
458 n6.v * (4 * t4 * jac[0][3] + 4 * t1 * jac[3][3]) +
459 n7.v * (4 * t3 * jac[1][3] + 4 * t2 * jac[2][3]) +
460 n8.v * (4 * t4 * jac[1][3] + 4 * t2 * jac[3][3]) +
461 n9.v * (4 * t4 * jac[2][3] + 4 * t3 * jac[3][3])) /
462 det;
463
464 // Transform field to global coordinates
465 UnmapFields(ex, ey, ez, x, y, z, xmirr, ymirr, zmirr, rcoordinate, rotation);
466
467 // Drift medium?
468 if (m_debug) {
469 std::cout << m_className << "::ElectricField:\n"
470 << " Material " << element.matmap << ", drift flag "
471 << m_materials[element.matmap].driftmedium << "\n";
472 }
473 m = m_materials[element.matmap].medium;
474 status = -5;
475 if (m_materials[element.matmap].driftmedium) {
476 if (m && m->IsDriftable()) status = 0;
477 }
478}
479
480void ComponentComsol::WeightingField(const double xin, const double yin,
481 const double zin, double& wx, double& wy,
482 double& wz, const std::string& label) {
483 // Initial values
484 wx = wy = wz = 0;
485
486 // Do not proceed if not properly initialised.
487 if (!m_ready) return;
488
489 // Look for the label.
490 const size_t iw = GetWeightingFieldIndex(label);
491 // Do not proceed if the requested weighting field does not exist.
492 if (iw == m_wfields.size()) return;
493 // Check if the weighting field is properly initialised.
494 if (!m_wfieldsOk[iw]) return;
495
496 // Copy the coordinates.
497 double x = xin, y = yin, z = zin;
498
499 // Map the coordinates onto field map coordinates
500 bool xmirr, ymirr, zmirr;
501 double rcoordinate, rotation;
502 MapCoordinates(x, y, z, xmirr, ymirr, zmirr, rcoordinate, rotation);
503
504 if (m_warning) PrintWarning("WeightingField");
505
506 // Find the element that contains this point.
507 double t1, t2, t3, t4, jac[4][4], det;
508 const int imap = FindElement13(x, y, z, t1, t2, t3, t4, jac, det);
509 // Check if the point is in the mesh.
510 if (imap < 0) return;
511
512 const Element& element = m_elements[imap];
513 if (m_debug) {
514 PrintElement("WeightingField", x, y, z, t1, t2, t3, t4, element, 10, iw);
515 }
516 const Node& n0 = m_nodes[element.emap[0]];
517 const Node& n1 = m_nodes[element.emap[1]];
518 const Node& n2 = m_nodes[element.emap[2]];
519 const Node& n3 = m_nodes[element.emap[3]];
520 const Node& n4 = m_nodes[element.emap[4]];
521 const Node& n5 = m_nodes[element.emap[5]];
522 const Node& n6 = m_nodes[element.emap[6]];
523 const Node& n7 = m_nodes[element.emap[7]];
524 const Node& n8 = m_nodes[element.emap[8]];
525 const Node& n9 = m_nodes[element.emap[9]];
526 // Tetrahedral field
527 wx = -(n0.w[iw] * (4 * t1 - 1) * jac[0][1] +
528 n1.w[iw] * (4 * t2 - 1) * jac[1][1] +
529 n2.w[iw] * (4 * t3 - 1) * jac[2][1] +
530 n3.w[iw] * (4 * t4 - 1) * jac[3][1] +
531 n4.w[iw] * (4 * t2 * jac[0][1] + 4 * t1 * jac[1][1]) +
532 n5.w[iw] * (4 * t3 * jac[0][1] + 4 * t1 * jac[2][1]) +
533 n6.w[iw] * (4 * t4 * jac[0][1] + 4 * t1 * jac[3][1]) +
534 n7.w[iw] * (4 * t3 * jac[1][1] + 4 * t2 * jac[2][1]) +
535 n8.w[iw] * (4 * t4 * jac[1][1] + 4 * t2 * jac[3][1]) +
536 n9.w[iw] * (4 * t4 * jac[2][1] + 4 * t3 * jac[3][1])) /
537 det;
538
539 wy = -(n0.w[iw] * (4 * t1 - 1) * jac[0][2] +
540 n1.w[iw] * (4 * t2 - 1) * jac[1][2] +
541 n2.w[iw] * (4 * t3 - 1) * jac[2][2] +
542 n3.w[iw] * (4 * t4 - 1) * jac[3][2] +
543 n4.w[iw] * (4 * t2 * jac[0][2] + 4 * t1 * jac[1][2]) +
544 n5.w[iw] * (4 * t3 * jac[0][2] + 4 * t1 * jac[2][2]) +
545 n6.w[iw] * (4 * t4 * jac[0][2] + 4 * t1 * jac[3][2]) +
546 n7.w[iw] * (4 * t3 * jac[1][2] + 4 * t2 * jac[2][2]) +
547 n8.w[iw] * (4 * t4 * jac[1][2] + 4 * t2 * jac[3][2]) +
548 n9.w[iw] * (4 * t4 * jac[2][2] + 4 * t3 * jac[3][2])) /
549 det;
550
551 wz = -(n0.w[iw] * (4 * t1 - 1) * jac[0][3] +
552 n1.w[iw] * (4 * t2 - 1) * jac[1][3] +
553 n2.w[iw] * (4 * t3 - 1) * jac[2][3] +
554 n3.w[iw] * (4 * t4 - 1) * jac[3][3] +
555 n4.w[iw] * (4 * t2 * jac[0][3] + 4 * t1 * jac[1][3]) +
556 n5.w[iw] * (4 * t3 * jac[0][3] + 4 * t1 * jac[2][3]) +
557 n6.w[iw] * (4 * t4 * jac[0][3] + 4 * t1 * jac[3][3]) +
558 n7.w[iw] * (4 * t3 * jac[1][3] + 4 * t2 * jac[2][3]) +
559 n8.w[iw] * (4 * t4 * jac[1][3] + 4 * t2 * jac[3][3]) +
560 n9.w[iw] * (4 * t4 * jac[2][3] + 4 * t3 * jac[3][3])) /
561 det;
562
563 // Transform field to global coordinates
564 UnmapFields(wx, wy, wz, x, y, z, xmirr, ymirr, zmirr, rcoordinate, rotation);
565}
566
567double ComponentComsol::WeightingPotential(const double xin, const double yin,
568 const double zin,
569 const std::string& label) {
570 // Do not proceed if not properly initialised.
571 if (!m_ready) return 0.;
572
573 // Look for the label.
574 const size_t iw = GetWeightingFieldIndex(label);
575 // Do not proceed if the requested weighting field does not exist.
576 if (iw == m_wfields.size()) return 0.;
577 // Check if the weighting field is properly initialised.
578 // if (!m_wfieldsOk[iw]) return 0.;
579
580 // Copy the coordinates.
581 double x = xin, y = yin, z = zin;
582
583 // Map the coordinates onto field map coordinates.
584 bool xmirr, ymirr, zmirr;
585 double rcoordinate, rotation;
586 MapCoordinates(x, y, z, xmirr, ymirr, zmirr, rcoordinate, rotation);
587
588 if (m_warning) PrintWarning("WeightingPotential");
589
590 // Find the element that contains this point.
591 double t1, t2, t3, t4, jac[4][4], det;
592 const int imap = FindElement13(x, y, z, t1, t2, t3, t4, jac, det);
593 if (imap < 0) return 0.;
594
595 const Element& element = m_elements[imap];
596 if (m_debug) {
597 PrintElement("WeightingPotential", x, y, z, t1, t2, t3, t4, element, 10,
598 iw);
599 }
600 const Node& n0 = m_nodes[element.emap[0]];
601 const Node& n1 = m_nodes[element.emap[1]];
602 const Node& n2 = m_nodes[element.emap[2]];
603 const Node& n3 = m_nodes[element.emap[3]];
604 const Node& n4 = m_nodes[element.emap[4]];
605 const Node& n5 = m_nodes[element.emap[5]];
606 const Node& n6 = m_nodes[element.emap[6]];
607 const Node& n7 = m_nodes[element.emap[7]];
608 const Node& n8 = m_nodes[element.emap[8]];
609 const Node& n9 = m_nodes[element.emap[9]];
610 // Tetrahedral field
611 return n0.w[iw] * t1 * (2 * t1 - 1) + n1.w[iw] * t2 * (2 * t2 - 1) +
612 n2.w[iw] * t3 * (2 * t3 - 1) + n3.w[iw] * t4 * (2 * t4 - 1) +
613 4 * n4.w[iw] * t1 * t2 + 4 * n5.w[iw] * t1 * t3 +
614 4 * n6.w[iw] * t1 * t4 + 4 * n7.w[iw] * t2 * t3 +
615 4 * n8.w[iw] * t2 * t4 + 4 * n9.w[iw] * t3 * t4;
616}
617
618Medium* ComponentComsol::GetMedium(const double xin, const double yin,
619 const double zin) {
620 // Copy the coordinates
621 double x = xin, y = yin, z = zin;
622
623 // Map the coordinates onto field map coordinates
624 bool xmirr, ymirr, zmirr;
625 double rcoordinate, rotation;
626 MapCoordinates(x, y, z, xmirr, ymirr, zmirr, rcoordinate, rotation);
627
628 // Do not proceed if not properly initialised.
629 if (!m_ready) {
630 PrintNotReady("GetMedium");
631 return nullptr;
632 }
633 if (m_warning) PrintWarning("GetMedium");
634
635 // Find the element that contains this point
636 double t1, t2, t3, t4, jac[4][4], det;
637 const int imap = FindElement13(x, y, z, t1, t2, t3, t4, jac, det);
638 if (imap < 0) {
639 if (m_debug) {
640 std::cout << m_className << "::GetMedium:\n"
641 << " Point (" << x << ", " << y << ", " << z
642 << ") not in the mesh.\n";
643 }
644 return nullptr;
645 }
646 const Element& element = m_elements[imap];
647 if (element.matmap >= m_materials.size()) {
648 if (m_debug) {
649 std::cerr << m_className << "::GetMedium:\n"
650 << " Point (" << x << ", " << y << ", " << z
651 << ") has out of range material number " << imap << ".\n";
652 }
653 return nullptr;
654 }
655
656 if (m_debug) {
657 PrintElement("GetMedium", x, y, z, t1, t2, t3, t4, element, 10);
658 }
659
660 return m_materials[element.matmap].medium;
661}
662
663double ComponentComsol::GetElementVolume(const unsigned int i) {
664 if (i >= m_elements.size()) return 0.;
665 const Element& element = m_elements[i];
666 const Node& n0 = m_nodes[element.emap[0]];
667 const Node& n1 = m_nodes[element.emap[1]];
668 const Node& n2 = m_nodes[element.emap[2]];
669 const Node& n3 = m_nodes[element.emap[3]];
670
671 // Uses formula V = |a (dot) b x c|/6
672 // with a => "3", b => "1", c => "2" and origin "0"
673 const double vol =
674 fabs((n3.x - n0.x) *
675 ((n1.y - n0.y) * (n2.z - n0.z) - (n2.y - n0.y) * (n1.z - n0.z)) +
676 (n3.y - n0.y) *
677 ((n1.z - n0.z) * (n2.x - n0.x) - (n2.z - n0.z) * (n1.x - n0.x)) +
678 (n3.z - n0.z) * ((n1.x - n0.x) * (n2.y - n0.y) -
679 (n3.x - n0.x) * (n1.y - n0.y))) /
680 6.;
681 return vol;
682}
683
684void ComponentComsol::GetAspectRatio(const unsigned int i, double& dmin,
685 double& dmax) {
686 if (i >= m_elements.size()) {
687 dmin = dmax = 0.;
688 return;
689 }
690
691 const Element& element = m_elements[i];
692 const int np = 4;
693 // Loop over all pairs of vertices.
694 for (int j = 0; j < np - 1; ++j) {
695 const Node& nj = m_nodes[element.emap[j]];
696 for (int k = j + 1; k < np; ++k) {
697 const Node& nk = m_nodes[element.emap[k]];
698 // Compute distance.
699 const double dx = nj.x - nk.x;
700 const double dy = nj.y - nk.y;
701 const double dz = nj.z - nk.z;
702 const double dist = sqrt(dx * dx + dy * dy + dz * dz);
703 if (k == 1) {
704 dmin = dmax = dist;
705 } else {
706 if (dist < dmin) dmin = dist;
707 if (dist > dmax) dmax = dist;
708 }
709 }
710 }
711}
712
714 const std::string& label) {
715 if (!m_ready) {
716 std::cerr << m_className << "::SetDelayedWeightingField:\n"
717 << " No valid field map is present.\n"
718 << " Weighting fields cannot be added.\n";
719 return false;
720 }
721
722 if (!GetTimeInterval(field)) return false;
723
724 if (!m_timeset) {
725 std::cerr << m_className << "::SetDelayedWeightingField:\n"
726 << " No valid times slices of potential set.\n"
727 << " Please add the time slices.\n";
728 return false;
729 }
730
731 const int T = m_wdtimes.size();
732
733 double x, y, z;
734
735 // Open the voltage list.
736 std::ifstream ffield;
737 ffield.open(field.c_str(), std::ios::in);
738
739 // Check if a weighting field with the same label already exists.
740 const size_t iw = GetOrCreateWeightingFieldIndex(label);
741
742 if (iw + 1 != m_wfields.size()) {
743 std::cout << m_className << "::SetDelayedWeightingField:\n"
744 << " Replacing existing weighting field " << label << ".\n";
745 }
746
747 m_wfieldsOk[iw] = false; //????
748
749 // Build a k-d tree from the node coordinates.
750
751 std::vector<std::vector<double> > points;
752 for (const auto& node : m_nodes) {
753 std::vector<double> point = {node.x, node.y, node.z};
754 points.push_back(std::move(point));
755 }
756
757 KDTree kdtree(points);
758
759 std::string line;
760 int Linecount = 1;
761 const int nNodes = m_nodes.size();
762 const unsigned int nPrint =
763 std::pow(10, static_cast<unsigned int>(
764 std::max(std::floor(std::log10(nNodes)) - 1, 1.)));
765 std::cout << m_className << "::SetDelayedWeightingField:\n"
766 << " Reading weighting potentials.\n";
767 PrintProgress(0.);
768
769 while (std::getline(ffield, line)) {
770 // Skip empty lines.
771 if (line.empty()) continue;
772 // Skip lines that are not comments.
773 if (isComment(line)) continue;
774
775 std::vector<double> pvect;
776
777 std::istringstream data;
778 data.str(line);
779 data >> x >> y >> z;
780 x *= m_unit;
781 y *= m_unit;
782 z *= m_unit;
783 const std::vector<double> pt = {x, y, z};
784 std::vector<KDTreeResult> res;
785 kdtree.n_nearest(pt, 1, res);
786
787 if (res.empty()) {
788 std::cerr << m_className << "::SetDelayedWeightingField:\n"
789 << " Could not find a matching mesh node for point (" << x
790 << ", " << y << ", " << z << ")\n.";
791 ffield.close();
792 return false;
793 }
794
795 double pholder = 0.;
796 double pholder0 = 0.;
797 for (int i = 0; i < T; i++) {
798 data >> pholder;
799 if (i == 0) pholder0 = pholder;
800 pvect.push_back(pholder - pholder0);
801 }
802 const size_t k = res[0].idx;
803 m_nodes[k].dw[iw] = pvect;
804 m_nodes[k].w[iw] = pholder0;
805
806 if ((Linecount + 1) % nPrint == 0)
807 PrintProgress(double(Linecount + 1) / nNodes);
808 Linecount++;
809 }
810
811 PrintProgress(1.);
812 std::cout << std::endl
813 << m_className << "::SetDelayedWeightingField: Done.\n";
814 ffield.close();
815 return true;
816}
817
819 const double yin,
820 const double zin,
821 const double t,
822 const std::string& label) {
823 if (m_wdtimes.empty()) return 0.;
824 // Assume no weighting field for times outside the range of available maps.
825 if (t < m_wdtimes.front() || t > m_wdtimes.back()) return 0.;
826
827 // Do not proceed if not properly initialised.
828 if (!m_ready) return 0.;
829 // Look for the label.
830 const size_t iw = GetWeightingFieldIndex(label);
831 // Do not proceed if the requested weighting field does not exist.
832 if (iw == m_wfields.size()) return 0.;
833 // Check if the weighting field is properly initialised.
834
835 // Copy the coordinates.
836 double x = xin, y = yin, z = zin;
837
838 // Map the coordinates onto field map coordinates.
839 bool xmirr, ymirr, zmirr;
840 double rcoordinate, rotation;
841 MapCoordinates(x, y, z, xmirr, ymirr, zmirr, rcoordinate, rotation);
842
843 if (m_warning) PrintWarning("WeightingPotential");
844
845 // Find the element that contains this point.
846 double t1, t2, t3, t4, jac[4][4], det;
847 const int imap = FindElement13(x, y, z, t1, t2, t3, t4, jac, det);
848 if (imap < 0) return 0.;
849 const Element& element = m_elements[imap];
850 if (m_debug) {
851 PrintElement("WeightingPotential", x, y, z, t1, t2, t3, t4, element, 10,
852 iw);
853 }
854 const Node& n0 = m_nodes[element.emap[0]];
855 const Node& n1 = m_nodes[element.emap[1]];
856 const Node& n2 = m_nodes[element.emap[2]];
857 const Node& n3 = m_nodes[element.emap[3]];
858 const Node& n4 = m_nodes[element.emap[4]];
859 const Node& n5 = m_nodes[element.emap[5]];
860 const Node& n6 = m_nodes[element.emap[6]];
861 const Node& n7 = m_nodes[element.emap[7]];
862 const Node& n8 = m_nodes[element.emap[8]];
863 const Node& n9 = m_nodes[element.emap[9]];
864 // Tetrahedral field
865
866 const auto it1 = std::upper_bound(m_wdtimes.cbegin(), m_wdtimes.cend(), t);
867 const auto it0 = std::prev(it1);
868
869 const double dt = t - *it0;
870 const unsigned int i0 = it0 - m_wdtimes.cbegin();
871 double dp0 =
872 n0.dw[iw][i0] * t1 * (2 * t1 - 1) + n1.dw[iw][i0] * t2 * (2 * t2 - 1) +
873 n2.dw[iw][i0] * t3 * (2 * t3 - 1) + n3.dw[iw][i0] * t4 * (2 * t4 - 1) +
874 4 * n4.dw[iw][i0] * t1 * t2 + 4 * n5.dw[iw][i0] * t1 * t3 +
875 4 * n6.dw[iw][i0] * t1 * t4 + 4 * n7.dw[iw][i0] * t2 * t3 +
876 4 * n8.dw[iw][i0] * t2 * t4 + 4 * n9.dw[iw][i0] * t3 * t4;
877
878 const unsigned int i1 = it1 - m_wdtimes.cbegin();
879
880 double dp1 =
881 n0.dw[iw][i1] * t1 * (2 * t1 - 1) + n1.dw[iw][i1] * t2 * (2 * t2 - 1) +
882 n2.dw[iw][i1] * t3 * (2 * t3 - 1) + n3.dw[iw][i1] * t4 * (2 * t4 - 1) +
883 4 * n4.dw[iw][i1] * t1 * t2 + 4 * n5.dw[iw][i1] * t1 * t3 +
884 4 * n6.dw[iw][i1] * t1 * t4 + 4 * n7.dw[iw][i1] * t2 * t3 +
885 4 * n8.dw[iw][i1] * t2 * t4 + 4 * n9.dw[iw][i1] * t3 * t4;
886
887 const double f1 = dt / (*it1 - *it0);
888 const double f0 = 1. - f1;
889
890 return f0 * dp0 + f1 * dp1;
891}
892
893void ComponentComsol::SetTimeInterval(const double mint, const double maxt,
894 const double stept) {
895 std::cout << std::endl
896 << m_className
897 << "::SetTimeInterval: Overwriting time interval of weighting "
898 "potential.\n";
899
900 if (m_wdtimes.empty()) {
901 double t = mint;
902 while (t <= maxt) {
903 m_wdtimes.push_back(t);
904 t += stept;
905 }
906 }
907 m_timeset = true;
908
909 std::cout << std::endl
910 << m_className
911 << "::SetTimeInterval: Time of weighting potential set for t in ["
912 << mint << "," << maxt << "].\n";
913}
914
915bool ComponentComsol::GetTimeInterval(const std::string& field) {
916 if (!m_wdtimes.empty()) return false;
917
918 std::ifstream ffield;
919 ffield.open(field.c_str(), std::ios::in);
920
921 if (ffield.fail()) {
922 PrintCouldNotOpen("SetDelayedWeightingField", field);
923 return false;
924 }
925
926 std::string strtime = "t=";
927
928 std::string line;
929 // Find first occurrence of "geeks"
930 size_t found = 0;
931
932 bool searching = true;
933 while (std::getline(ffield, line)) {
934 // Skip empty lines.
935 if (line.empty()) continue;
936 // Skip lines that are not comments.
937 if (line[0] == '%' && line[2] != 'x') continue;
938
939 while (searching) {
940 found = line.find(strtime, found + 1);
941
942 searching = false;
943
944 if (found != std::string::npos) {
945 searching = true;
946
947 int i = 2;
948
949 std::string holder = "";
950
951 while (true) {
952 holder += line[found + i];
953 i++;
954
955 if (found + i == line.size()) break;
956 if (line[found + i] == ' ') break;
957 }
958 m_wdtimes.push_back(stod(holder));
959 }
960 }
961 break;
962 }
963
964 m_timeset = true;
965
966 std::cout << std::endl
967 << m_className
968 << "::GetTimeInterval: Time of weighting potential set for t in ["
969 << m_wdtimes.front() << "," << m_wdtimes.back() << "].\n";
970
971 ffield.close();
972
973 return true;
974}
975
976} // namespace Garfield
Component for importing and interpolating Comsol field maps.
bool SetDelayedWeightingPotential(const std::string &file, const std::string &label)
Import the time-dependent weighting field maps.
void SetTimeInterval(const double mint, const double maxt, const double stept)
Set the time interval of the time-dependent weighting field.
bool Initialise(const std::string &header="mesh.mphtxt", const std::string &mplist="dielectrics.dat", const std::string &field="field.txt", const std::string &unit="m")
Medium * GetMedium(const double x, const double y, const double z) override
Get the medium at a given location (x, y, z).
void WeightingField(const double x, const double y, const double z, double &wx, double &wy, double &wz, const std::string &label) override
bool SetWeightingField(const std::string &file, const std::string &label)
Import the time-independent weighting field maps.
double WeightingPotential(const double x, const double y, const double z, const std::string &label) override
double DelayedWeightingPotential(const double x, const double y, const double z, const double t, const std::string &label) override
ComponentComsol()
Default constructor.
void ElectricField(const double x, const double y, const double z, double &ex, double &ey, double &ez, Medium *&m, int &status) override
double GetElementVolume(const unsigned int i) override
void GetAspectRatio(const unsigned int i, double &dmin, double &dmax) override
Base class for components based on finite-element field maps.
bool SetDefaultDriftMedium()
Find lowest epsilon, check for eps = 0, set default drift media flags.
void MapCoordinates(double &xpos, double &ypos, double &zpos, bool &xmirrored, bool &ymirrored, bool &zmirrored, double &rcoordinate, double &rotation) const
Move (xpos, ypos, zpos) to field map coordinates.
void PrintWarning(const std::string &header)
static double ScalingFactor(std::string unit)
std::vector< Material > m_materials
std::vector< bool > m_wfieldsOk
size_t GetWeightingFieldIndex(const std::string &label) const
void PrintCouldNotOpen(const std::string &header, const std::string &filename) const
std::vector< double > m_wdtimes
std::vector< std::string > m_wfields
int FindElement13(const double x, const double y, const double z, double &t1, double &t2, double &t3, double &t4, double jac[4][4], double &det)
Find the element for a point in curved quadratic tetrahedra.
std::vector< Element > m_elements
void PrintElement(const std::string &header, const double x, const double y, const double z, const double t1, const double t2, const double t3, const double t4, const Element &element, const unsigned int n, const int iw=-1) const
void Reset() override
Reset the component.
void UnmapFields(double &ex, double &ey, double &ez, double &xpos, double &ypos, double &zpos, bool &xmirrored, bool &ymirrored, bool &zmirrored, double &rcoordinate, double &rotation) const
Move (ex, ey, ez) to global coordinates.
void PrintNotReady(const std::string &header) const
size_t GetOrCreateWeightingFieldIndex(const std::string &label)
bool m_debug
Switch on/off debugging messages.
Definition: Component.hh:341
std::string m_className
Class name.
Definition: Component.hh:329
bool m_ready
Ready for use?
Definition: Component.hh:338
void n_nearest(const std::vector< double > &qv, const unsigned int nn, std::vector< KDTreeResult > &result) const
Definition: KDTree.cc:174
Abstract base class for media.
Definition: Medium.hh:13
bool IsDriftable() const
Is charge carrier transport enabled in this medium?
Definition: Medium.hh:74
std::vector< std::vector< double > > dw