gfields
Loading...
Searching...
No Matches
gfield_options.cc
Go to the documentation of this file.
1#include "gfield_options.h"
2#include "gfieldConventions.h"
3#include "gmagneto.h"
4
5// geant4
6#include "G4UnitsTable.hh"
7
8// gemc
9#include "gutilities.h"
10#include "gfactory_options.h"
11#include "goptionsConventions.h"
12
13// c++
14#include <cmath>
15#include <cstdlib>
16#include <fstream>
17#include <iostream>
18#include <sstream>
19
20namespace gfields {
21
22namespace {
23
24struct FieldQueryPoint {
25 double position[3] = {0.0, 0.0, 0.0};
26 std::string source;
27 int line = 0;
28};
29
30bool is_query_set(const std::string& value) {
31 return !value.empty() && value != guts::SERIALIZED_NULL_TOKEN && value != "not provided";
32}
33
34FieldQueryPoint parse_field_query_point(const std::string& expression, const std::string& source, int line) {
35 std::string cleaned = expression;
36 if (const auto comment = cleaned.find('#'); comment != std::string::npos) {
37 cleaned = cleaned.substr(0, comment);
38 }
39 for (auto& c : cleaned) {
40 if (c == ',') { c = ' '; }
41 }
43
44 FieldQueryPoint point;
45 point.source = source;
46 point.line = line;
47
48 if (cleaned.empty()) { return point; }
49
50 std::istringstream tokens(cleaned);
51 std::string x;
52 std::string y;
53 std::string z;
54 std::string extra;
55 tokens >> x >> y >> z >> extra;
56 if (x.empty() || y.empty() || z.empty() || !extra.empty()) {
57 std::cerr << guts::FATALERRORL << "field query point must contain exactly three coordinates with units";
58 if (line > 0) { std::cerr << " at " << source << ":" << line; }
59 else { std::cerr << " in " << source; }
60 std::cerr << ". Got <" << expression << ">." << std::endl;
61 std::exit(guts::EC__G4NUMBERERROR);
62 }
63
64 point.position[0] = gutilities::getG4Number(x, true);
65 point.position[1] = gutilities::getG4Number(y, true);
66 point.position[2] = gutilities::getG4Number(z, true);
67 return point;
68}
69
70bool is_blank_or_comment_line(const std::string& line) {
72 return trimmed.empty() || trimmed[0] == '#';
73}
74
75void append_field_query_file_points(const std::string& filename, std::vector<FieldQueryPoint>& points) {
76 std::ifstream input(filename);
77 if (!input) {
78 std::cerr << guts::FATALERRORL << "can't open field query point file " << filename << "." << std::endl;
79 std::exit(guts::EC__FILENOTFOUND);
80 }
81
82 std::string line;
83 int line_number = 0;
84 while (std::getline(input, line)) {
85 ++line_number;
86 if (is_blank_or_comment_line(line)) { continue; }
87 points.push_back(parse_field_query_point(line, filename, line_number));
88 }
89}
90
91void print_field_query_result(const std::string& field_name,
92 const FieldQueryPoint& point,
93 const double bfield[3]) {
94 const double bmag = std::sqrt(
95 bfield[0] * bfield[0] +
96 bfield[1] * bfield[1] +
97 bfield[2] * bfield[2]);
98 std::cout << "field=" << field_name
99 << " source=" << point.source;
100 if (point.line > 0) { std::cout << ":" << point.line; }
101 std::cout << " x=" << G4BestUnit(point.position[0], "Length")
102 << " y=" << G4BestUnit(point.position[1], "Length")
103 << " z=" << G4BestUnit(point.position[2], "Length")
104 << " Bx=" << G4BestUnit(bfield[0], "Magnetic flux density")
105 << " By=" << G4BestUnit(bfield[1], "Magnetic flux density")
106 << " Bz=" << G4BestUnit(bfield[2], "Magnetic flux density")
107 << " |B|=" << G4BestUnit(bmag, "Magnetic flux density")
108 << std::endl;
109}
110
111} // namespace
112
113// Build field definitions by reading the option tree and translating each entry into a GFieldDefinition.
114std::vector<GFieldDefinition> get_GFieldDefinition(const std::shared_ptr<GOptions>& gopts) {
115 std::vector<GFieldDefinition> gfield_defs;
116
117 // Directory of the (first) YAML file passed on the command line. Plugins that read companion data
118 // files next to their definition (e.g. the ASCII field map) default to it, so a plain .yaml works
119 // whether it is run from its own directory or referenced by an absolute path. Empty when no YAML was
120 // given (pure command-line configuration), or "." when the YAML was given without a directory.
121 std::string config_dir;
122 if (const auto yaml_files = gopts->getYamlFiles(); !yaml_files.empty()) {
123 const std::string& first = yaml_files.front();
124 const auto slash = first.find_last_of('/');
125 config_dir = (slash == std::string::npos) ? "." : first.substr(0, slash);
126 }
127
128 // Multipoles:
129 // Each "gmultipoles" entry becomes one independently named field definition.
130 auto gmultipoles_node = gopts->getOptionNode("gmultipoles");
131 for (auto gmultipoles_item : gmultipoles_node) {
132 GFieldDefinition gfield_def = GFieldDefinition();
133
134 // Core identity and integration configuration.
135 gfield_def.name = gopts->get_required_variable_in_option<std::string>(gmultipoles_item, "name");
136 gfield_def.integration_stepper = gopts->get_variable_in_option<std::string>(
137 gmultipoles_item, "integration_stepper", gfields::GFIELD_DEFAULT_INTEGRATION_STEPPER);
138 gfield_def.minimum_step = gutilities::getG4Number(gopts->get_variable_in_option<std::string>(
139 gmultipoles_item, "minimum_step", gfields::GFIELD_DEFAULT_MINIMUM_STEP));
140
141 // Multipole parameters:
142 // Values are stored as strings to preserve unit expressions and are parsed later by the concrete field.
143 gfield_def.add_map_parameter(
144 "pole_number", gopts->get_required_variable_in_option<std::string>(gmultipoles_item, "pole_number"));
145 gfield_def.add_map_parameter("vx", gopts->get_variable_in_option<std::string>(
146 gmultipoles_item, "vx", gfields::GFIELD_DEFAULT_VERTEX));
147 gfield_def.add_map_parameter("vy", gopts->get_variable_in_option<std::string>(
148 gmultipoles_item, "vy", gfields::GFIELD_DEFAULT_VERTEX));
149 gfield_def.add_map_parameter("vz", gopts->get_variable_in_option<std::string>(
150 gmultipoles_item, "vz", gfields::GFIELD_DEFAULT_VERTEX));
151 gfield_def.add_map_parameter("rotation_angle", gopts->get_variable_in_option<std::string>(
152 gmultipoles_item, "rotation_angle", gfields::GFIELD_DEFAULT_ROTANGLE));
153 gfield_def.add_map_parameter(
154 "rotaxis", gopts->get_required_variable_in_option<std::string>(gmultipoles_item, "rotaxis"));
155 gfield_def.add_map_parameter(
156 "strength", gopts->get_required_variable_in_option<std::string>(gmultipoles_item, "strength"));
157 gfield_def.add_map_parameter("longitudinal", gopts->get_variable_in_option<std::string>(
158 gmultipoles_item, "longitudinal", "false"));
159
160 // The type field controls the shared-library plugin name through GFieldDefinition::gfieldPluginName().
161 gfield_def.type = "multipoles";
162 gfield_def.config_dir = config_dir;
163
164 gfield_defs.push_back(gfield_def);
165 }
166
167 // Generic plugin-backed fields:
168 // Each "gfields" entry names a field, selects a plugin through "type", and carries an arbitrary
169 // set of scalar parameters that are forwarded verbatim to the plugin via field_parameters. This
170 // lets external plugins (e.g. clas12 mapped fields) be configured without changing this parser.
171 auto gfields_node = gopts->getOptionNode("gfields");
172 for (auto gfields_item : gfields_node) {
173 GFieldDefinition gfield_def = GFieldDefinition();
174
175 // Core identity and integration configuration (the schema-defined keys).
176 gfield_def.name = gopts->get_required_variable_in_option<std::string>(gfields_item, "name");
177 gfield_def.type = gopts->get_required_variable_in_option<std::string>(gfields_item, "type");
178 gfield_def.integration_stepper = gopts->get_variable_in_option<std::string>(
179 gfields_item, "integration_stepper", gfields::GFIELD_DEFAULT_INTEGRATION_STEPPER);
180 gfield_def.minimum_step = gutilities::getG4Number(gopts->get_variable_in_option<std::string>(
181 gfields_item, "minimum_step", gfields::GFIELD_DEFAULT_MINIMUM_STEP));
182
183 // Every remaining (scalar) key is forwarded to the plugin as a string parameter.
184 // Nested maps/sequences are not supported here: plugin parameters must be scalar values.
185 for (auto it = gfields_item.begin(); it != gfields_item.end(); ++it) {
186 auto key = it->first.as<std::string>();
187 if (key == "name" || key == "type" || key == "integration_stepper" || key == "minimum_step") {
188 continue;
189 }
190 gfield_def.add_map_parameter(key, it->second.as<std::string>());
191 }
192 gfield_def.config_dir = config_dir;
193
194 gfield_defs.push_back(gfield_def);
195 }
196
197 return gfield_defs;
198}
199
200
201// Define all options for this module, including plugin fields and logger integration (non-Doxygen summary).
207
208 std::string help;
209 help = "Adds electromagnetic multipole field(s) to the simulation. \n \n";
210 help += "Mandatory keys: name, pole_number, rotaxis, strength. \n \n";
211 help += "Example (a quadrupole centered 30 cm downstream): \n";
212 help += "-gmultipoles=\"[{name: q1, pole_number: 4, rotaxis: Z, strength: 1.2, vz: 30*cm}]\"\n";
213 std::vector<GVariable> gmultipoles = {
214 {"name", goptions::REQUIRED, "Field name (unique key used by GMagneto maps)"},
215 {"integration_stepper", gfields::GFIELD_DEFAULT_INTEGRATION_STEPPER,
216 "Geant4 integration stepper name (string)"},
218 "Minimum step for the G4ChordFinder (Geant4 length units)"},
219 {"pole_number", goptions::REQUIRED, "Pole number (even integer >= 2): 2=dipole, 4=quadrupole, ..."},
220 {"vx", gfields::GFIELD_DEFAULT_VERTEX, "Origin X component (Geant4 length units)"},
221 {"vy", gfields::GFIELD_DEFAULT_VERTEX, "Origin Y component (Geant4 length units)"},
222 {"vz", gfields::GFIELD_DEFAULT_VERTEX, "Origin Z component (Geant4 length units)"},
223 {"rotation_angle", gfields::GFIELD_DEFAULT_ROTANGLE,
224 "Roll rotation angle about rotaxis (Geant4 angle units)"},
225 {"rotaxis", goptions::REQUIRED, "Rotation/longitudinal axis: one of X, Y, Z"},
226 {"strength", goptions::REQUIRED,
227 "Field strength in Tesla (defined at 1 m reference radius for multipoles)"},
228 {"longitudinal", "false", "If true, return a uniform field aligned with rotaxis (solenoid-like)"}};
229 goptions.defineOption("gmultipoles", "define the e.m. gmultipoles", gmultipoles, help);
230
231 std::string gfields_help;
232 gfields_help = "Adds a generic, plugin-backed electromagnetic field to the simulation. \n \n";
233 gfields_help += "The 'type' selects the plugin shared library named gfield<type>Factory. \n";
234 gfields_help += "Any additional scalar keys are forwarded verbatim to that plugin as string \n";
235 gfields_help += "parameters (so the plugin alone decides which parameters it understands). \n \n";
236 gfields_help += "Mandatory keys: name, type. \n \n";
237 gfields_help += "Example (clas12 binary mapped field from the clas12-systems plugin): \n";
238 gfields_help += "-gfields=\"[{name: clas12, type: clas12bin, solenoid: solenoid_map, torus: torus_map}]\"\n \n";
239 gfields_help += "Example (generic ASCII field map, type asciimap): the data-only map file holds the \n";
240 gfields_help += "coordinate columns followed by the field components, while the grid is defined here. \n";
241 gfields_help += "-gfields=\"[{name: solenoid, type: asciimap, symmetry: cylindrical-z, map: solenoid.txt, \n";
242 gfields_help += " field_unit: T, coordinate1: 'transverse, 601, 0*m, 3*m', \n";
243 gfields_help += " coordinate2: 'longitudinal, 1201, -3*m, 3*m'}]\"\n";
244 std::vector<GVariable> gfields = {
245 {"name", goptions::REQUIRED, "Field name (unique key used by GMagneto maps)"},
246 {"type", goptions::REQUIRED, "Field type; selects the plugin shared library gfield<type>Factory"},
247 {"integration_stepper", gfields::GFIELD_DEFAULT_INTEGRATION_STEPPER,
248 "Geant4 integration stepper name (string)"},
250 "Minimum step for the G4ChordFinder (Geant4 length units)"}};
251 goptions.defineOption("gfields", "define a generic plugin-backed e.m. field", gfields, gfields_help);
252
253 goptions.defineOption(
255 "associate a field with the ROOT world volume"),
256 "Associates a configured electromagnetic field with the ROOT (top-level) world volume.\n \n"
257 "The value must be the name of a field defined with -gmultipoles or -gfields. The field's\n"
258 "G4FieldManager is installed on the ROOT world volume and propagated to all daughters, so it\n"
259 "applies everywhere a more specific per-volume field has not been set.\n \n"
260 "Example: -global_field=dipole1\n \n");
261
262 goptions.defineOption(
264 "reset the field of one or more volumes"),
265 std::string(
266 "Removes the electromagnetic field association from one or more volumes.\n \n"
267 "The value is either the name of a gvolume, a whitespace- or comma-separated list of gvolume\n"
268 "names, or the special value 'all'. A listed volume that was associated with a field (per-volume\n"
269 "or inherited) has that association removed, so it is left with no field. The special value 'all'\n"
270 "resets every per-volume field and also clears the '") +
272 "' option.\n \nFields that no volume uses as a result are not loaded: their plugins and field "
273 "maps are skipped.\n \nExamples: -" +
274 gfields::NO_FIELD_OPTION + "=target (reset only the 'target' volume)\n -" +
275 gfields::NO_FIELD_OPTION + "=\"target, magnet\" (reset both volumes)\n -" +
277 "=all (reset every field, including the global field)\n \n");
278
279 goptions.defineOption(
281 "maximum accepted field step"),
282 std::string(
283 "Sets the maximum acceptable propagation step used by Geant4 magnetic-field transportation.\n \n"
284 "The value is parsed as a Geant4 length expression and is passed to\n"
285 "G4PropagatorInField::SetLargestAcceptableStep() when positive. The default value\n"
286 "(") +
287 gfields::GFIELD_DEFAULT_MAXIMUM_STEP + ") leaves the Geant4 default unchanged.\n \nExample: -" +
288 gfields::MAX_FIELD_STEP_OPTION + "=5*mm\n \n");
289
290 goptions.defineOption(
291 GVariable("fieldAt", std::nullopt, "query all configured fields at x y z"),
292 "Evaluate all configured electromagnetic fields at one absolute coordinate.\n \n"
293 "The value must contain three coordinate expressions with units, separated by spaces.\n \n"
294 "Example: -fieldAt=\"10*cm 0*mm 2*m\"\n \n");
295
296 goptions.defineOption(
297 GVariable("fieldMapPoints", std::nullopt,
298 "ASCII file of x y z points for field queries"),
299 "Evaluate all configured electromagnetic fields at coordinates listed in an ASCII file.\n \n"
300 "Each non-empty, non-comment line must contain three coordinate expressions with units.\n"
301 "Coordinates may be separated by spaces or commas. Lines beginning with # are ignored.\n \n"
302 "Example: -fieldMapPoints=points.txt\n \n");
303
304 return goptions;
305}
306
307bool runFieldQueries(const std::shared_ptr<GOptions>& gopts) {
308 const auto field_at = gopts->getOptionalScalarString("fieldAt");
309 const auto field_map_points = gopts->getOptionalScalarString("fieldMapPoints");
310
311 const bool field_at_set = field_at && is_query_set(*field_at);
312 const bool field_map_points_set = field_map_points && is_query_set(*field_map_points);
313 if (!field_at_set && !field_map_points_set) { return false; }
314
315 std::vector<FieldQueryPoint> points;
316 if (field_at_set) { points.push_back(parse_field_query_point(*field_at, "fieldAt", 0)); }
317 if (field_map_points_set) { append_field_query_file_points(*field_map_points, points); }
318
319 auto magneto = std::make_shared<GMagneto>(gopts);
320 auto field_names = magneto->getFieldNames();
321 if (field_names.empty()) {
322 std::cerr << guts::FATALERRORL << "field query requested, but no electromagnetic fields are configured."
323 << std::endl;
325 }
326
327 std::cout << "# field query results" << std::endl;
328 for (const auto& point : points) {
329 for (const auto& field_name : field_names) {
330 double bfield[3] = {0.0, 0.0, 0.0};
331 magneto->getField(field_name)->GetFieldValue(point.position, bfield);
332 print_field_query_result(field_name, point, bfield);
333 }
334 }
335
336 return true;
337}
338
339}
constexpr const char * PLUGIN_LOGGER
constexpr const char * GFIELD_LOGGER
Definition gfield.h:13
constexpr const char * GMAGNETO_LOGGER
Definition gfield.h:14
constexpr char GFIELD_DEFAULT_ROTANGLE[]
Default multipole roll rotation angle (string with Geant4 units).
constexpr char GFIELD_DEFAULT_VERTEX[]
Default origin coordinate component for multipole fields (string with Geant4 units).
constexpr char GFIELD_DEFAULT_INTEGRATION_STEPPER[]
Default integration stepper name used when the requested stepper is unsupported.
constexpr char NO_FIELD_OPTION[]
Command-line option name used to reset (remove) field associations.
constexpr char MAX_FIELD_STEP_OPTION[]
Command-line option name used to set the global maximum acceptable field step.
constexpr char GFIELD_DEFAULT_MINIMUM_STEP[]
Default minimum step for the chord finder (string with Geant4 units).
constexpr char GLOBAL_FIELD_OPTION[]
Command-line option name used to associate a field with the ROOT world volume.
constexpr char GFIELD_DEFAULT_MAXIMUM_STEP[]
Default maximum acceptable field step for Geant4 propagation.
GOptions defineOptions()
std::vector< GFieldDefinition > get_GFieldDefinition(const std::shared_ptr< GOptions > &gopts)
Build the list of field definitions from the provided options.
bool runFieldQueries(const std::shared_ptr< GOptions > &gopts)
Evaluate configured fields from fieldAt and fieldMapPoints options, if requested.
GOptions defineOptions()
Define all options used by the GField module and its built-in field factories.
constexpr int EC__NOOPTIONFOUND
constexpr RequiredValue REQUIRED
double getG4Number(const string &v, bool warnIfNotUnit=false)
string removeLeadingAndTrailingSpacesFromString(const std::string &input)
constexpr int EC__FILENOTFOUND
constexpr char FATALERRORL[]
constexpr char SERIALIZED_NULL_TOKEN[]
constexpr int EC__G4NUMBERERROR
Lightweight configuration carrier used to load and configure a GField plugin.
Definition gfield.h:27
std::string config_dir
Definition gfield.h:48
double minimum_step
Minimum step size used when constructing the G4ChordFinder (Geant4 length units).
Definition gfield.h:40
std::string name
Field name key used by GMagneto maps.
Definition gfield.h:34
void add_map_parameter(const std::string &key, const std::string &value)
Add or overwrite a parameter in the field-parameter map.
Definition gfield.h:58
std::string integration_stepper
Integration stepper name (string) used when creating the G4ChordFinder.
Definition gfield.h:37
std::string type
Field type discriminator used to derive the plugin factory name (e.g. "multipoles").
Definition gfield.h:43