Garfield++ 5.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.
2
3#include <math.h>
4#include <stdlib.h>
5
6#include <fstream>
7#include <iostream>
8#include <map>
9#include <sstream>
10
11#include "Garfield/KDTree.hh"
12
13namespace {
14
15bool ends_with(std::string s, std::string t) {
16 if (!s.empty() && s.back() == '\r') s.pop_back();
17 return s.size() >= t.size() && s.substr(s.size() - t.size(), t.size()) == t;
18}
19
20bool isComment(const std::string &line) {
21 if (line.empty()) return false;
22 if (line[0] == '%') return true;
23 return false;
24}
25
26void PrintProgress(const double f) {
27 if (f < 0.) return;
28 constexpr unsigned int width = 70;
29 const unsigned int n = static_cast<unsigned int>(std::floor(width * f));
30 std::string bar = "[";
31 if (n < 1) {
32 bar += std::string(width, ' ');
33 } else if (n >= width) {
34 bar += std::string(width, '=');
35 } else {
36 bar += std::string(n, '=') + ">" + std::string(width - n - 1, ' ');
37 }
38 bar += "]";
39 std::cout << bar << "\r" << std::flush;
40}
41
42} // namespace
43
44namespace Garfield {
45
47
48ComponentComsol::ComponentComsol(const std::string &mesh,
49 const std::string &mplist,
50 const std::string &field,
51 const std::string &unit)
52 : ComponentComsol() {
53 Initialise(mesh, mplist, field, unit);
54}
55
56bool ComponentComsol::Initialise(const std::string &mesh,
57 const std::string &mplist,
58 const std::string &field,
59 const std::string &unit) {
61
62 std::vector<int> nodeIndices;
63
64 // Get the conversion factor to be applied to the coordinates.
65 m_unit = ScalingFactor(unit);
66 if (m_unit <= 0.) {
67 std::cerr << m_className << "::Initialise:\n Unknown length unit "
68 << unit << ". Will use default (m).\n";
69 m_unit = 100.;
70 }
71 // Open the materials file.
72 m_materials.clear();
73 std::ifstream fmplist(mplist);
74 if (!fmplist) {
75 PrintCouldNotOpen("Initialise", mplist);
76 return false;
77 }
78 unsigned int nMaterials;
79 fmplist >> nMaterials;
80 for (unsigned int i = 0; i < nMaterials; ++i) {
81 Material material;
82 material.driftmedium = false;
83 material.medium = nullptr;
84 material.ohm = -1;
85 fmplist >> material.eps;
86 m_materials.push_back(std::move(material));
87 }
88 if (m_materials.empty()) {
89 // Add default material
90 Material material;
91 material.driftmedium = false;
92 material.medium = nullptr;
93 material.eps = material.ohm = -1;
94 m_materials.push_back(std::move(material));
95 nMaterials = 1;
96 }
97 std::map<int, int> domain2material;
98 int d2msize;
99 fmplist >> d2msize;
100 for (int i = 0; i < d2msize; ++i) {
101 int domain, mat;
102 fmplist >> domain >> mat;
103 domain2material[domain] = mat;
104 }
105 fmplist.close();
106
107 // Find lowest epsilon, check for eps = 0, set default drift medium.
108 if (!SetDefaultDriftMedium()) return false;
109
110 m_nodes.clear();
111 std::ifstream fmesh(mesh);
112 if (!fmesh) {
113 PrintCouldNotOpen("Initialise", mesh);
114 return false;
115 }
116
117 std::string line;
118 do {
119 if (!std::getline(fmesh, line)) {
120 std::cerr << m_className << "::Initialise:\n"
121 << " Could not read number of nodes from " << mesh << ".\n";
122 fmesh.close();
123 return false;
124 }
125 } while (!ends_with(line, "# number of mesh points") &&
126 !ends_with(line, "# number of mesh vertices"));
127
128 const int nNodes = std::stoi(line);
129 int nInRange = 0;
130 std::cout << m_className << "::Initialise: " << nNodes << " nodes.\n";
131 do {
132 if (!std::getline(fmesh, line)) {
133 std::cerr << m_className << "::Initialise:\n"
134 << " Error parsing " << mesh << ".\n";
135 fmesh.close();
136 return false;
137 }
138 } while (line.find("# Mesh point coordinates") == std::string::npos &&
139 line.find("# Mesh vertex coordinates") == std::string::npos);
140
141 std::vector<Node> allNodes;
142 for (int i = 0; i < nNodes; ++i) {
143 Node node;
144 fmesh >> node.x >> node.y >> node.z;
145 node.x *= m_unit;
146 node.y *= m_unit;
147 node.z *= m_unit;
148 if (m_range.set) {
149 allNodes.push_back(std::move(node));
150 if (CheckInRange(node.x, node.y, node.z)) nInRange++;
151 } else {
152 m_nodes.push_back(std::move(node));
153 }
154 }
155
156 m_elements.clear();
157 do {
158 if (!std::getline(fmesh, line)) {
159 std::cerr << m_className << "::Initialise:\n"
160 << " Error parsing " << mesh << ".\n";
161 fmesh.close();
162 return false;
163 }
164 } while (line.find("4 tet2 # type name") == std::string::npos);
165 do {
166 if (!std::getline(fmesh, line)) {
167 std::cerr << m_className << "::Initialise:\n"
168 << " Error parsing " << mesh << ".\n";
169 fmesh.close();
170 return false;
171 }
172 } while (!ends_with(line, "# number of elements"));
173
174 const int nElements = std::stoi(line);
175 std::cout << m_className << "::Initialise: " << nElements << " elements.\n";
176 std::getline(fmesh, line);
177 std::vector<Element> allElements;
178 // Elements 6 & 7 are swapped due to differences in COMSOL and ANSYS
179 // representation
180 int perm[10] = {0, 1, 2, 3, 4, 5, 7, 6, 8, 9};
181 for (int i = 0; i < nElements; ++i) {
182 Element element;
183 for (int j = 0; j < 10; ++j) {
184 fmesh >> element.emap[perm[j]];
185 }
186 allElements.push_back(std::move(element));
187 }
188
189 do {
190 if (!std::getline(fmesh, line)) {
191 std::cerr << m_className << "::Initialise:\n"
192 << " Error parsing " << mesh << ".\n";
193 fmesh.close();
194 return false;
195 }
196 } while (line.find("# Geometric entity indices") == std::string::npos);
197 for (auto& element : allElements) {
198 int domain;
199 fmesh >> domain;
200 if (domain2material.count(domain) > 0) {
201 element.matmap = domain2material[domain];
202 } else {
203 element.matmap = nMaterials - 1;
204 }
205 }
206 fmesh.close();
207
208 for (auto& element : allElements) {
209 if (ElementInRange(element, allNodes)) {
210 for (int j = 0; j < 10; j++) {
211 nodeIndices.push_back(element.emap[j]);
212 }
213 m_elements.push_back(std::move(element));
214 }
215 }
216 m_degenerate.assign(m_elements.size(), false);
217
218 if (m_range.set) {
219 std::vector<int> nodeMap(nNodes, -1);
220 // Rearrange node indices and delete duplicates.
221 std::sort(nodeIndices.begin(), nodeIndices.end());
222 nodeIndices.erase(std::unique(nodeIndices.begin(), nodeIndices.end()),
223 nodeIndices.end());
224 // Go over node indices and add the corresponding nodes to m_nodes.
225 for (int i : nodeIndices) {
226 m_nodes.push_back(allNodes[i]);
227 // Update map to get correct node index.
228 nodeMap[i] = m_nodes.size() - 1;
229 }
230 // Go over the elements and update the node indices
231 // using the map you just created.
232 for (Element& element : m_elements) {
233 for (int j = 0; j < 10; ++j) {
234 element.emap[j] = nodeMap[element.emap[j]];
235 if (element.emap[j] == -1) return false;
236 }
237 }
238 }
239
240 std::ifstream ffield(field);
241 if (!ffield) {
242 PrintCouldNotOpen("Initialise", field);
243 return false;
244 }
245 m_pot.resize(m_nodes.size());
246 const std::string hdr1 =
247 "% x y z "
248 " V (V)";
249
250 const std::string hdr2 =
251 "% x y z V (V)";
252 do {
253 if (!std::getline(ffield, line)) {
254 std::cerr << m_className << "::Initialise:\n"
255 << " Error parsing " << field << ".\n";
256 ffield.close();
257 return false;
258 }
259 } while (line.find(hdr1) == std::string::npos &&
260 line.find(hdr2) == std::string::npos);
261 std::istringstream sline(line);
262 std::string token;
263 sline >> token; // %
264 sline >> token; // x
265 sline >> token; // y
266 sline >> token; // z
267 sline >> token; // V
268 sline >> token; // (V)
269 std::vector<std::string> wfields;
270 while (sline >> token) {
271 std::cout << m_className << "::Initialise:\n"
272 << " Reading data for weighting field " << token << ".\n";
273 wfields.push_back(token);
274 m_wpot.emplace(token, std::vector<double>(m_nodes.size(), 0.));
275 sline >> token; // (V)
276 }
277 const size_t nWeightingFields = wfields.size();
278
279 const unsigned int nPrint =
280 std::pow(10, static_cast<unsigned int>(
281 std::max(std::floor(std::log10(nNodes)) - 1, 1.)));
282 std::cout << m_className << "::Initialise: Reading potentials.\n";
283 PrintProgress(0.);
284 // Build a k-d tree from the node coordinates.
285 std::vector<std::vector<double> > points;
286 for (const auto &node : m_nodes) {
287 std::vector<double> point = {node.x, node.y, node.z};
288 points.push_back(std::move(point));
289 }
290 KDTree kdtree(points);
291
292 const int usedSize = m_range.set ? m_nodes.size() : nNodes;
293 std::vector<bool> used(usedSize, false);
294 for (int i = 0; i < nNodes; ++i) {
295 double x, y, z, v;
296 ffield >> x >> y >> z >> v;
297 x *= m_unit;
298 y *= m_unit;
299 z *= m_unit;
300 std::vector<double> w;
301 for (size_t j = 0; j < nWeightingFields; ++j) {
302 double p;
303 ffield >> p;
304 w.push_back(p);
305 }
306 if (!CheckInRange(x, y, z)) continue;
307 std::vector<KDTreeResult> res;
308 kdtree.n_nearest({x, y, z}, 1, res);
309 if (res.empty()) {
310 std::cerr << m_className << "::Initialise:\n"
311 << " Could not find a matching mesh node for point ("
312 << x << ", " << y << ", " << z << ")\n.";
313 ffield.close();
314 return false;
315 }
316 if (res[0].dis > MaxNodeDistance) continue;
317 const size_t k = res[0].idx;
318 used[k] = true;
319 m_pot[k] = v;
320 for (size_t j = 0; j < nWeightingFields; ++j) {
321 m_wpot[wfields[j]][k] = w[j];
322 }
323 if ((i + 1) % nPrint == 0) PrintProgress(double(i + 1) / nNodes);
324 }
325 PrintProgress(1.);
326 ffield.close();
327 auto nMissing = std::count(used.begin(), used.end(), false);
328 if (m_range.set) nMissing = nMissing - m_nodes.size() + nInRange;
329 if (nMissing > 0) {
330 std::cerr << m_className << "::Initialise:\n"
331 << " Missing potentials for " << nMissing << " nodes.\n";
332 // return false;
333 }
334
335 m_ready = true;
336 Prepare();
337 std::cout << std::endl << m_className << "::Initialise: Done.\n";
338 return true;
339}
340
341bool ComponentComsol::SetWeightingPotential(const std::string &field,
342 const std::string &label) {
343 if (!m_ready) {
344 std::cerr << m_className << "::SetWeightingPotential:\n"
345 << " No valid field map is present.\n"
346 << " Weighting fields cannot be added.\n";
347 return false;
348 }
349
350 std::cout << m_className << "::SetWeightingPotential:\n"
351 << " Reading field map for electrode " << label << ".\n";
352
353 // Check if a weighting field with the same label already exists.
354 if (m_wpot.count(label) > 0) {
355 std::cout << " Replacing existing weighting field.\n";
356 m_wpot[label].clear();
357 }
358
359 std::vector<double> pot(m_nodes.size(), 0.);
360 if (!LoadPotentials(field, pot)) return false;
361 m_wpot[label] = pot;
362 return true;
363}
364
365bool ComponentComsol::LoadPotentials(const std::string& field,
366 std::vector<double>& pot) {
367
368 // Open the file.
369 std::ifstream ffield(field);
370 if (!ffield) {
371 PrintCouldNotOpen("LoadPotentials", field);
372 return false;
373 }
374 // Build a k-d tree from the node coordinates.
375 std::vector<std::vector<double> > points;
376 for (const auto& node : m_nodes) {
377 std::vector<double> point = {node.x, node.y, node.z};
378 points.push_back(std::move(point));
379 }
380 KDTree kdtree(points);
381
382 std::string line;
383 int nLines = 1;
384 const int nNodes = m_nodes.size();
385 const unsigned int nPrint =
386 std::pow(10, static_cast<unsigned int>(
387 std::max(std::floor(std::log10(nNodes)) - 1, 1.)));
388 PrintProgress(0.);
389
390 while (std::getline(ffield, line)) {
391 // Skip empty lines.
392 if (line.empty()) continue;
393 // Skip comments.
394 if (isComment(line)) continue;
395
396 std::vector<double> pvect;
397
398 std::istringstream data(line);
399 double x = 0., y = 0., z = 0.;
400 data >> x >> y >> z;
401 x *= m_unit;
402 y *= m_unit;
403 z *= m_unit;
404 if (!CheckInRange(x, y, z)) continue;
405 std::vector<KDTreeResult> res;
406 kdtree.n_nearest({x, y, z}, 1, res);
407 if (res.empty()) {
408 std::cerr << m_className << "::LoadPotentials:\n"
409 << " Could not find a matching mesh node for point ("
410 << x << ", " << y << ", " << z << ")\n.";
411 ffield.close();
412 return false;
413 }
414 if (res[0].dis > MaxNodeDistance) continue;
415
416 double p = 0.;
417 data >> p;
418 const size_t k = res[0].idx;
419 pot[k] = p;
420
421 if ((nLines + 1) % nPrint == 0) {
422 PrintProgress(double(nLines + 1) / nNodes);
423 }
424 nLines++;
425 }
426
427 PrintProgress(1.);
428 ffield.close();
429 return true;
430}
431
433 const std::string &label) {
434 if (!m_ready) {
435 std::cerr << m_className << "::SetDynamicWeightingPotential:\n"
436 << " No valid field map is present.\n"
437 << " Weighting fields cannot be added.\n";
438 return false;
439 }
440
441 if (!m_timeset && !GetTimeInterval(field)) return false;
442
443 if (!m_timeset) {
444 std::cerr << m_className << "::SetDynamicWeightingPotential:\n"
445 << " No valid times slices of potential set.\n"
446 << " Please add the time slices.\n";
447 return false;
448 }
449
450 const int T = m_wdtimes.size();
451
452 // Open the voltage list.
453 std::ifstream ffield(field);
454 if (!ffield) {
455 PrintCouldNotOpen("SetDynamicWeightingPotential", field);
456 return false;
457 }
458
459 // Check if a weighting field with the same label already exists.
460 if (m_wpot.count(label) > 0) {
461 std::cout << m_className << "::SetDynamicWeightingPotential:\n"
462 << " Replacing existing weighting field " << label << ".\n";
463 m_wpot[label].clear();
464 }
465 if (m_dwpot.count(label) > 0) {
466 m_dwpot[label].clear();
467 }
468
469 std::vector<double> pot(m_nodes.size(), 0.);
470 std::vector<std::vector<double> > dpot(m_nodes.size());
471
472 // Build a k-d tree from the node coordinates.
473 std::vector<std::vector<double> > points;
474 for (const auto &node : m_nodes) {
475 std::vector<double> point = {node.x, node.y, node.z};
476 points.push_back(std::move(point));
477 }
478 KDTree kdtree(points);
479
480 std::string line;
481 int nLines = 1;
482 const int nNodes = m_nodes.size();
483 const unsigned int nPrint =
484 std::pow(10, static_cast<unsigned int>(
485 std::max(std::floor(std::log10(nNodes)) - 1, 1.)));
486 std::cout << m_className << "::SetDynamicWeightingPotential:\n"
487 << " Reading weighting potentials for " << label << ".\n";
488 PrintProgress(0.);
489
490 while (std::getline(ffield, line)) {
491 // Skip empty lines.
492 if (line.empty()) continue;
493 // Skip comments.
494 if (isComment(line)) continue;
495
496 std::istringstream data(line);
497 double x = 0., y = 0., z = 0.;
498 data >> x >> y >> z;
499 x *= m_unit;
500 y *= m_unit;
501 z *= m_unit;
502 if (!CheckInRange(x, y, z)) continue;
503 std::vector<KDTreeResult> res;
504 kdtree.n_nearest({x, y, z}, 1, res);
505 if (res.empty()) {
506 std::cerr << m_className << "::SetDynamicWeightingPotential:\n"
507 << " Could not find a matching mesh node for point ("
508 << x << ", " << y << ", " << z << ")\n.";
509 ffield.close();
510 return false;
511 }
512 if (res[0].dis > MaxNodeDistance) continue;
513
514 double p = 0.;
515 double p0 = 0.;
516 std::vector<double> pvect;
517 for (int i = 0; i < T; i++) {
518 data >> p;
519 if (i == 0) p0 = p;
520 pvect.push_back(p - p0);
521 }
522 const size_t k = res[0].idx;
523 dpot[k] = pvect;
524 pot[k] = p0;
525
526 if ((nLines + 1) % nPrint == 0) {
527 PrintProgress(double(nLines + 1) / nNodes);
528 }
529 nLines++;
530 }
531
532 PrintProgress(1.);
533 std::cout << std::endl
534 << m_className << "::SetDynamicWeightingPotential: Done.\n";
535 ffield.close();
536 m_wpot[label] = std::move(pot);
537 m_dwpot[label] = std::move(dpot);
538 return true;
539}
540
541void ComponentComsol::SetTimeInterval(const double mint, const double maxt,
542 const double stept) {
543 std::cout << std::endl
544 << m_className
545 << "::SetTimeInterval: Overwriting time interval of weighting "
546 "potential.\n";
547
548 if (m_wdtimes.empty()) {
549 double t = mint;
550 while (t <= maxt) {
551 m_wdtimes.push_back(t);
552 t += stept;
553 }
554 }
555 m_timeset = true;
556
557 std::cout << std::endl
558 << m_className
559 << "::SetTimeInterval: Time of weighting potential set for t in ["
560 << mint << "," << maxt << "].\n";
561}
562
563bool ComponentComsol::GetTimeInterval(const std::string &field) {
564 if (!m_wdtimes.empty())
565 std::cout << std::endl
566 << m_className
567 << "::GetTimeInterval: Overwriting time interval of weighting "
568 "potential.\n";
569
570 std::ifstream ffield(field);
571 if (!ffield) {
572 PrintCouldNotOpen("GetTimeInterval", field);
573 return false;
574 }
575
576 std::string strtime = "t=";
577
578 std::string line;
579 // Find first occurrence of "geeks"
580 size_t found = 0;
581 bool searching = true;
582 while (std::getline(ffield, line)) {
583 // Skip empty lines.
584 if (line.empty()) continue;
585 // Skip lines that are not comments.
586 if (line[0] == '%' && line[2] != 'x') continue;
587
588 while (searching) {
589 found = line.find(strtime, found + 1);
590 searching = false;
591 if (found != std::string::npos) {
592 searching = true;
593
594 int i = 2;
595
596 std::string holder = "";
597
598 while (true) {
599 holder += line[found + i];
600 i++;
601
602 if (found + i == line.size()) break;
603 if (line[found + i] == ' ') break;
604 }
605 m_wdtimes.push_back(stod(holder));
606 }
607 }
608 break;
609 }
610
611 m_timeset = true;
612
613 std::cout << std::endl
614 << m_className
615 << "::GetTimeInterval: Time of weighting potential set for t in ["
616 << m_wdtimes.front() << "," << m_wdtimes.back() << "].\n";
617
618 ffield.close();
619
620 return true;
621}
622
623} // namespace Garfield
bool SetWeightingPotential(const std::string &file, const std::string &label)
Import the weighting potential 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")
bool SetDynamicWeightingPotential(const std::string &file, const std::string &label)
Import the time-dependent weighting field maps.
ComponentComsol()
Default constructor.
bool SetDefaultDriftMedium()
Find lowest epsilon, check for eps = 0, set default drift media flags.
static double ScalingFactor(std::string unit)
std::vector< Material > m_materials
ComponentFieldMap()=delete
Default constructor.
void PrintCouldNotOpen(const std::string &header, const std::string &filename) const
std::map< std::string, std::vector< double > > m_wpot
std::vector< double > m_wdtimes
std::vector< Element > m_elements
std::map< std::string, std::vector< std::vector< double > > > m_dwpot
void Reset() override
Reset the component.
std::string m_className
Class name.
Definition Component.hh:359
bool m_ready
Ready for use?
Definition Component.hh:368
void n_nearest(const std::vector< double > &qv, const unsigned int nn, std::vector< KDTreeResult > &result) const
Definition KDTree.cc:174