6#include "G4UnitsTable.hh"
24struct FieldQueryPoint {
25 double position[3] = {0.0, 0.0, 0.0};
30bool is_query_set(
const std::string& value) {
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);
39 for (
auto& c : cleaned) {
40 if (c ==
',') { c =
' '; }
44 FieldQueryPoint point;
45 point.source = source;
48 if (cleaned.empty()) {
return point; }
50 std::istringstream tokens(cleaned);
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;
70bool is_blank_or_comment_line(
const std::string& line) {
72 return trimmed.empty() || trimmed[0] ==
'#';
75void append_field_query_file_points(
const std::string& filename, std::vector<FieldQueryPoint>& points) {
76 std::ifstream input(filename);
78 std::cerr <<
guts::FATALERRORL <<
"can't open field query point file " << filename <<
"." << std::endl;
84 while (std::getline(input, line)) {
86 if (is_blank_or_comment_line(line)) {
continue; }
87 points.push_back(parse_field_query_point(line, filename, line_number));
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")
115 std::vector<GFieldDefinition> gfield_defs;
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);
130 auto gmultipoles_node = gopts->getOptionNode(
"gmultipoles");
131 for (
auto gmultipoles_item : gmultipoles_node) {
135 gfield_def.
name = gopts->get_required_variable_in_option<std::string>(gmultipoles_item,
"name");
144 "pole_number", gopts->get_required_variable_in_option<std::string>(gmultipoles_item,
"pole_number"));
151 gfield_def.
add_map_parameter(
"rotation_angle", gopts->get_variable_in_option<std::string>(
154 "rotaxis", gopts->get_required_variable_in_option<std::string>(gmultipoles_item,
"rotaxis"));
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"));
161 gfield_def.
type =
"multipoles";
164 gfield_defs.push_back(gfield_def);
171 auto gfields_node = gopts->getOptionNode(
"gfields");
172 for (
auto gfields_item : gfields_node) {
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");
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") {
194 gfield_defs.push_back(gfield_def);
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 = {
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, ..."},
224 "Roll rotation angle about rotaxis (Geant4 angle units)"},
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);
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 = {
246 {
"type",
goptions::REQUIRED,
"Field type; selects the plugin shared library gfield<type>Factory"},
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);
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");
264 "reset the field of one or more volumes"),
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: -" +
277 "=all (reset every field, including the global field)\n \n");
281 "maximum accepted field step"),
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"
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");
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");
308 const auto field_at = gopts->getOptionalScalarString(
"fieldAt");
309 const auto field_map_points = gopts->getOptionalScalarString(
"fieldMapPoints");
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; }
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); }
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."
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);
constexpr const char * PLUGIN_LOGGER
constexpr const char * GFIELD_LOGGER
constexpr const char * GMAGNETO_LOGGER
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.
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.
double minimum_step
Minimum step size used when constructing the G4ChordFinder (Geant4 length units).
std::string name
Field name key used by GMagneto maps.
void add_map_parameter(const std::string &key, const std::string &value)
Add or overwrite a parameter in the field-parameter map.
std::string integration_stepper
Integration stepper name (string) used when creating the G4ChordFinder.
std::string type
Field type discriminator used to derive the plugin factory name (e.g. "multipoles").