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 != UNINITIALIZEDSTRINGQUANTITY && 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 << 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(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 << FATALERRORL << "can't open field query point file " << filename << "." << std::endl;
79 std::exit(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_variable_in_option<std::string>(
136 gmultipoles_item, "name", goptions::NODFLT);
137 gfield_def.integration_stepper = gopts->get_variable_in_option<std::string>(
138 gmultipoles_item, "integration_stepper", GFIELD_DEFAULT_INTEGRATION_STEPPER);
139 gfield_def.minimum_step = gutilities::getG4Number(gopts->get_variable_in_option<std::string>(
140 gmultipoles_item, "minimum_step", GFIELD_DEFAULT_MINIMUM_STEP));
141
142 // Multipole parameters:
143 // Values are stored as strings to preserve unit expressions and are parsed later by the concrete field.
144 gfield_def.add_map_parameter("pole_number", gopts->get_variable_in_option<std::string>(
145 gmultipoles_item, "pole_number", goptions::NODFLT));
146 gfield_def.add_map_parameter("vx", gopts->get_variable_in_option<std::string>(
147 gmultipoles_item, "vx", GFIELD_DEFAULT_VERTEX));
148 gfield_def.add_map_parameter("vy", gopts->get_variable_in_option<std::string>(
149 gmultipoles_item, "vy", GFIELD_DEFAULT_VERTEX));
150 gfield_def.add_map_parameter("vz", gopts->get_variable_in_option<std::string>(
151 gmultipoles_item, "vz", GFIELD_DEFAULT_VERTEX));
152 gfield_def.add_map_parameter("rotation_angle", gopts->get_variable_in_option<std::string>(
153 gmultipoles_item, "rotation_angle", GFIELD_DEFAULT_ROTANGLE));
154 gfield_def.add_map_parameter("rotaxis", gopts->get_variable_in_option<std::string>(
155 gmultipoles_item, "rotaxis", goptions::NODFLT));
156 gfield_def.add_map_parameter("strength", gopts->get_variable_in_option<std::string>(
157 gmultipoles_item, "strength", goptions::NODFLT));
158 gfield_def.add_map_parameter("longitudinal", gopts->get_variable_in_option<std::string>(
159 gmultipoles_item, "longitudinal", "false"));
160
161 // The type field controls the shared-library plugin name through GFieldDefinition::gfieldPluginName().
162 gfield_def.type = "multipoles";
163 gfield_def.config_dir = config_dir;
164
165 gfield_defs.push_back(gfield_def);
166 }
167
168 // Generic plugin-backed fields:
169 // Each "gfields" entry names a field, selects a plugin through "type", and carries an arbitrary
170 // set of scalar parameters that are forwarded verbatim to the plugin via field_parameters. This
171 // lets external plugins (e.g. clas12 mapped fields) be configured without changing this parser.
172 auto gfields_node = gopts->getOptionNode("gfields");
173 for (auto gfields_item : gfields_node) {
174 GFieldDefinition gfield_def = GFieldDefinition();
175
176 // Core identity and integration configuration (the schema-defined keys).
177 gfield_def.name = gopts->get_variable_in_option<std::string>(
178 gfields_item, "name", goptions::NODFLT);
179 gfield_def.type = gopts->get_variable_in_option<std::string>(
180 gfields_item, "type", goptions::NODFLT);
181 gfield_def.integration_stepper = gopts->get_variable_in_option<std::string>(
182 gfields_item, "integration_stepper", GFIELD_DEFAULT_INTEGRATION_STEPPER);
183 gfield_def.minimum_step = gutilities::getG4Number(gopts->get_variable_in_option<std::string>(
184 gfields_item, "minimum_step", GFIELD_DEFAULT_MINIMUM_STEP));
185
186 // Every remaining (scalar) key is forwarded to the plugin as a string parameter.
187 // Nested maps/sequences are not supported here: plugin parameters must be scalar values.
188 for (auto it = gfields_item.begin(); it != gfields_item.end(); ++it) {
189 auto key = it->first.as<std::string>();
190 if (key == "name" || key == "type" || key == "integration_stepper" || key == "minimum_step") {
191 continue;
192 }
193 gfield_def.add_map_parameter(key, it->second.as<std::string>());
194 }
195 gfield_def.config_dir = config_dir;
196
197 gfield_defs.push_back(gfield_def);
198 }
199
200 return gfield_defs;
201}
202
203
204// Define all options for this module, including plugin fields and logger integration (non-Doxygen summary).
210
211 std::string help;
212 help = "Adds electromagnetic multipole field(s) to the simulation. \n \n";
213 help += "Mandatory keys: name, pole_number, rotaxis, strength. \n \n";
214 help += "Example (a quadrupole centered 30 cm downstream): \n";
215 help += "-gmultipoles=\"[{name: q1, pole_number: 4, rotaxis: Z, strength: 1.2, vz: 30*cm}]\"\n";
216 std::vector<GVariable> gmultipoles = {
217 {"name", goptions::NODFLT, "Field name (unique key used by GMagneto maps)"},
218 {"integration_stepper", GFIELD_DEFAULT_INTEGRATION_STEPPER, "Geant4 integration stepper name (string)"},
219 {"minimum_step", GFIELD_DEFAULT_MINIMUM_STEP, "Minimum step for the G4ChordFinder (Geant4 length units)"},
220 {"pole_number", goptions::NODFLT, "Pole number (even integer >= 2): 2=dipole, 4=quadrupole, ..."},
221 {"vx", GFIELD_DEFAULT_VERTEX, "Origin X component (Geant4 length units)"},
222 {"vy", GFIELD_DEFAULT_VERTEX, "Origin Y component (Geant4 length units)"},
223 {"vz", GFIELD_DEFAULT_VERTEX, "Origin Z component (Geant4 length units)"},
224 {"rotation_angle", GFIELD_DEFAULT_ROTANGLE, "Roll rotation angle about rotaxis (Geant4 angle units)"},
225 {"rotaxis", goptions::NODFLT, "Rotation/longitudinal axis: one of X, Y, Z"},
226 {"strength", goptions::NODFLT, "Field strength in Tesla (defined at 1 m reference radius for multipoles)"},
227 {"longitudinal", "false", "If true, return a uniform field aligned with rotaxis (solenoid-like)"}
228 };
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::NODFLT, "Field name (unique key used by GMagneto maps)"},
246 {"type", goptions::NODFLT, "Field type; selects the plugin shared library gfield<type>Factory"},
247 {"integration_stepper", GFIELD_DEFAULT_INTEGRATION_STEPPER, "Geant4 integration stepper name (string)"},
248 {"minimum_step", GFIELD_DEFAULT_MINIMUM_STEP, "Minimum step for the G4ChordFinder (Geant4 length units)"}
249 };
250 goptions.defineOption("gfields", "define a generic plugin-backed e.m. field", gfields, gfields_help);
251
252 goptions.defineOption(
253 GVariable(GLOBAL_FIELD_OPTION, UNINITIALIZEDSTRINGQUANTITY, "associate a field with the ROOT world volume"),
254 "Associates a configured electromagnetic field with the ROOT (top-level) world volume.\n \n"
255 "The value must be the name of a field defined with -gmultipoles or -gfields. The field's\n"
256 "G4FieldManager is installed on the ROOT world volume and propagated to all daughters, so it\n"
257 "applies everywhere a more specific per-volume field has not been set.\n \n"
258 "Example: -global_field=dipole1\n \n");
259
260 goptions.defineOption(
261 GVariable(NO_FIELD_OPTION, UNINITIALIZEDSTRINGQUANTITY, "reset the field of one or more volumes"),
262 "Removes the electromagnetic field association from one or more volumes.\n \n"
263 "The value is either the name of a gvolume, a whitespace- or comma-separated list of gvolume\n"
264 "names, or the special value 'all'. A listed volume that was associated with a field (per-volume\n"
265 "or inherited) has that association removed, so it is left with no field. The special value 'all'\n"
266 "resets every per-volume field and also clears the '" GLOBAL_FIELD_OPTION "' option.\n \n"
267 "Fields that no volume uses as a result are not loaded: their plugins and field maps are skipped.\n \n"
268 "Examples: -" NO_FIELD_OPTION "=target (reset only the 'target' volume)\n"
269 " -" NO_FIELD_OPTION "=\"target, magnet\" (reset both volumes)\n"
270 " -" NO_FIELD_OPTION "=all (reset every field, including the global field)\n \n");
271
272 goptions.defineOption(
273 GVariable(MAX_FIELD_STEP_OPTION, GFIELD_DEFAULT_MAXIMUM_STEP, "maximum accepted field step"),
274 "Sets the maximum acceptable propagation step used by Geant4 magnetic-field transportation.\n \n"
275 "The value is parsed as a Geant4 length expression and is passed to\n"
276 "G4PropagatorInField::SetLargestAcceptableStep() when positive. The default value\n"
277 "(" GFIELD_DEFAULT_MAXIMUM_STEP ") leaves the Geant4 default unchanged.\n \n"
278 "Example: -" MAX_FIELD_STEP_OPTION "=5*mm\n \n");
279
280 goptions.defineOption(
281 GVariable("fieldAt", UNINITIALIZEDSTRINGQUANTITY, "query all configured fields at x y z"),
282 "Evaluate all configured electromagnetic fields at one absolute coordinate.\n \n"
283 "The value must contain three coordinate expressions with units, separated by spaces.\n \n"
284 "Example: -fieldAt=\"10*cm 0*mm 2*m\"\n \n");
285
286 goptions.defineOption(
287 GVariable("fieldMapPoints", UNINITIALIZEDSTRINGQUANTITY, "ASCII file of x y z points for field queries"),
288 "Evaluate all configured electromagnetic fields at coordinates listed in an ASCII file.\n \n"
289 "Each non-empty, non-comment line must contain three coordinate expressions with units.\n"
290 "Coordinates may be separated by spaces or commas. Lines beginning with # are ignored.\n \n"
291 "Example: -fieldMapPoints=points.txt\n \n");
292
293 return goptions;
294}
295
296bool runFieldQueries(const std::shared_ptr<GOptions>& gopts) {
297 const auto field_at = gopts->getScalarString("fieldAt");
298 const auto field_map_points = gopts->getScalarString("fieldMapPoints");
299
300 if (!is_query_set(field_at) && !is_query_set(field_map_points)) { return false; }
301
302 std::vector<FieldQueryPoint> points;
303 if (is_query_set(field_at)) { points.push_back(parse_field_query_point(field_at, "fieldAt", 0)); }
304 if (is_query_set(field_map_points)) { append_field_query_file_points(field_map_points, points); }
305
306 auto magneto = std::make_shared<GMagneto>(gopts);
307 auto field_names = magneto->getFieldNames();
308 if (field_names.empty()) {
309 std::cerr << FATALERRORL << "field query requested, but no electromagnetic fields are configured."
310 << std::endl;
311 std::exit(EC__NOOPTIONFOUND);
312 }
313
314 std::cout << "# field query results" << std::endl;
315 for (const auto& point : points) {
316 for (const auto& field_name : field_names) {
317 double bfield[3] = {0.0, 0.0, 0.0};
318 magneto->getField(field_name)->GetFieldValue(point.position, bfield);
319 print_field_query_result(field_name, point, bfield);
320 }
321 }
322
323 return true;
324}
325
326}
constexpr const char * PLUGIN_LOGGER
constexpr const char * GFIELD_LOGGER
Definition gfield.h:13
constexpr const char * GMAGNETO_LOGGER
Definition gfield.h:14
std::string source
int line
double position[3]
#define EC__NOOPTIONFOUND
#define GFIELD_DEFAULT_ROTANGLE
Default multipole roll rotation angle (string with Geant4 units).
#define GFIELD_DEFAULT_VERTEX
Default origin coordinate component for multipole fields (string with Geant4 units).
#define GFIELD_DEFAULT_MAXIMUM_STEP
Default maximum acceptable field step for Geant4 propagation.
#define NO_FIELD_OPTION
Command-line option name used to reset (remove) field associations.
#define GFIELD_DEFAULT_INTEGRATION_STEPPER
Default integration stepper name used when the requested stepper is unsupported.
#define GLOBAL_FIELD_OPTION
Command-line option name used to associate a field with the ROOT world volume.
#define MAX_FIELD_STEP_OPTION
Command-line option name used to set the global maximum acceptable field step.
#define GFIELD_DEFAULT_MINIMUM_STEP
Default minimum step for the chord finder (string with Geant4 units).
#define UNINITIALIZEDSTRINGQUANTITY
#define FATALERRORL
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.
const std::string NODFLT
double getG4Number(const string &v, bool warnIfNotUnit=false)
string removeLeadingAndTrailingSpacesFromString(const std::string &input)
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