6#include "G4UnitsTable.hh"
24struct FieldQueryPoint {
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;
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 <<
FATALERRORL <<
"field query point must contain exactly three coordinates with units";
59 else { std::cerr <<
" in " <<
source; }
60 std::cerr <<
". Got <" << expression <<
">." << std::endl;
61 std::exit(EC__G4NUMBERERROR);
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 <<
FATALERRORL <<
"can't open field query point file " << filename <<
"." << std::endl;
79 std::exit(EC__FILENOTFOUND);
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_variable_in_option<std::string>(
144 gfield_def.
add_map_parameter(
"pole_number", gopts->get_variable_in_option<std::string>(
152 gfield_def.
add_map_parameter(
"rotation_angle", gopts->get_variable_in_option<std::string>(
154 gfield_def.
add_map_parameter(
"rotaxis", gopts->get_variable_in_option<std::string>(
156 gfield_def.
add_map_parameter(
"strength", gopts->get_variable_in_option<std::string>(
158 gfield_def.
add_map_parameter(
"longitudinal", gopts->get_variable_in_option<std::string>(
159 gmultipoles_item,
"longitudinal",
"false"));
162 gfield_def.
type =
"multipoles";
165 gfield_defs.push_back(gfield_def);
172 auto gfields_node = gopts->getOptionNode(
"gfields");
173 for (
auto gfields_item : gfields_node) {
177 gfield_def.
name = gopts->get_variable_in_option<std::string>(
179 gfield_def.
type = gopts->get_variable_in_option<std::string>(
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") {
197 gfield_defs.push_back(gfield_def);
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)"},
220 {
"pole_number",
goptions::NODFLT,
"Pole number (even integer >= 2): 2=dipole, 4=quadrupole, ..."},
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)"}
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 = {
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"},
250 goptions.defineOption(
"gfields",
"define a generic plugin-backed e.m. field",
gfields, gfields_help);
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");
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"
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"
270 " -" NO_FIELD_OPTION "=all (reset every field, including the global field)\n \n");
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"
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");
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");
297 const auto field_at = gopts->getScalarString(
"fieldAt");
298 const auto field_map_points = gopts->getScalarString(
"fieldMapPoints");
300 if (!is_query_set(field_at) && !is_query_set(field_map_points)) {
return false; }
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); }
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."
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);
constexpr const char * PLUGIN_LOGGER
constexpr const char * GFIELD_LOGGER
constexpr const char * GMAGNETO_LOGGER
#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
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.
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.
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").