MARLEY (Model of Argon Reaction Low Energy Yields) v2.0.0
A Monte Carlo event generator for tens-of-MeV neutrino interactions
Loading...
Searching...
No Matches
StrengthVariationWeightCalculator.cc
1
4//
5// This file is part of MARLEY (Model of Argon Reaction Low Energy Yields)
6//
7// MARLEY is free software: you can redistribute it and/or modify it under the
8// terms of version 3 of the GNU General Public License as published by the
9// Free Software Foundation.
10//
11// For the full text of the license please see COPYING or
12// visit http://opensource.org/licenses/GPL-3.0
13//
14// Please respect the MCnet academic usage guidelines. See GUIDELINES
15// or visit https://www.montecarlonet.org/GUIDELINES for details.
16
17// Standard library includes
18#include <iomanip>
19#include <limits>
20#include <set>
21#include <sstream>
22#include <unordered_set>
23
24// HepMC3 includes
25#include "HepMC3/GenEvent.h"
26#include "HepMC3/GenParticle.h"
27
28// MARLEY includes
29#include "marley/DiscreteNuclearReaction.hh"
30#include "marley/Error.hh"
31#include "marley/FileManager.hh"
32#include "marley/JSON.hh"
33#include "marley/Generator.hh"
34#include "marley/StrengthVariationWeightCalculator.hh"
35#include "marley/hepmc3_utils.hh"
36#include "marley/marley_utils.hh"
37
38namespace {
39
44 std::vector< std::string > shortest_roundtrip_labels(
45 const std::vector< double >& values )
46 {
47 constexpr int MAX_PREC = std::numeric_limits< double >::max_digits10;
48 std::vector< std::string > labels;
49 labels.reserve( values.size() );
50
51 for ( double v : values ) {
52 std::string best;
53 for ( int prec = 0; prec <= MAX_PREC; ++prec ) {
54 std::ostringstream ss;
55 ss << std::fixed << std::setprecision( prec ) << v;
56 std::string s = ss.str();
57
58 auto dot = s.find( '.' );
59 if ( dot != std::string::npos ) {
60 auto last = s.find_last_not_of( '0' );
61 if ( last == dot ) s.erase( dot );
62 else s.erase( last + 1 );
63 }
64
65 if ( std::stod( s ) == v ) { best = s; break; }
66 }
67 if ( best.empty() ) best = std::to_string( v );
68 labels.push_back( std::move( best ) );
69 }
70
71 std::unordered_set< std::string > seen;
72 for ( const auto& l : labels ) {
73 if ( !seen.insert( l ).second ) {
74 throw marley::Error( "Unexpected label collision in"
75 " shortest_roundtrip_labels" );
76 }
77 }
78
79 return labels;
80 }
81
82} // anonymous namespace
83
84// -------------------------------------------------------------------
85// Multisim constructor
86// -------------------------------------------------------------------
87marley::StrengthVariationWeightCalculator
88 ::StrengthVariationWeightCalculator( const std::string& name,
89 std::shared_ptr< std::mt19937_64 > rng,
90 const std::string& resolved_reaction_file )
91 : WeightCalculator( name ), rng_( std::move( rng ) ),
92 resolved_reaction_file_( resolved_reaction_file ),
93 mode_( VariationMode::multisim )
94{}
95
96// -------------------------------------------------------------------
97// Sigma_shift constructor
98// -------------------------------------------------------------------
99marley::StrengthVariationWeightCalculator
100 ::StrengthVariationWeightCalculator( const std::string& name,
101 double sigma_factor, const std::string& resolved_reaction_file )
102 : WeightCalculator( name ), rng_( nullptr ),
103 resolved_reaction_file_( resolved_reaction_file ),
104 mode_( VariationMode::shift ),
105 sigma_factor_( sigma_factor )
106{}
107
108// -------------------------------------------------------------------
109// Unisim constructor
110// -------------------------------------------------------------------
111marley::StrengthVariationWeightCalculator
112 ::StrengthVariationWeightCalculator( const std::string& name,
113 double sigma_factor, size_t matrix_element_index,
114 const std::string& resolved_reaction_file )
115 : WeightCalculator( name ), rng_( nullptr ),
116 resolved_reaction_file_( resolved_reaction_file ),
117 mode_( VariationMode::unisim ),
118 sigma_factor_( sigma_factor ),
119 me_idx_( matrix_element_index )
120{}
121
122// -------------------------------------------------------------------
123// Static factory: create_instances
124// -------------------------------------------------------------------
125std::vector< std::shared_ptr<
128 const marley::JSON& config, marley::Generator& gen )
129{
130 // Resolve the reaction file
131 if ( !config.has_key( "reaction_file" ) ) {
132 throw marley::Error( "Missing \"reaction_file\" key in a"
133 " strength_variation weight calculator JSON configuration" );
134 }
135 std::string reaction_file = config.at( "reaction_file" ).to_string();
136 std::string resolved_reaction_file = marley::FileManager::Instance()
137 .find_file( reaction_file );
138 if ( resolved_reaction_file.empty() ) {
139 throw marley::Error( "Could not find reaction data file \""
140 + reaction_file + "\" requested by a strength_variation"
141 " weight calculator" );
142 }
143
144 // Read the optional variation mode (default "multisim")
145 std::string mode_str = "multisim";
146 if ( config.has_key( "mode" ) ) {
147 mode_str = config.at( "mode" ).to_string();
148 }
149
150 // Extract the base name
151 if ( !config.has_key( "name" ) ) {
152 throw marley::Error( "Missing \"name\" key in a"
153 " strength_variation weight calculator JSON configuration" );
154 }
155 std::string base_name = config.at( "name" ).to_string();
156
157 std::vector< std::shared_ptr<
158 StrengthVariationWeightCalculator > > instances;
159
160 if ( mode_str == "multisim" ) {
161
162 // Reject sigma_factor key in multisim mode
163 if ( config.has_key( "sigma_factor" ) ) {
164 throw marley::Error( "The \"sigma_factor\" key is not allowed"
165 " in multisim mode for strength_variation weight calculators" );
166 }
167
168 // Read and validate the number of variations
169 if ( !config.has_key( "num_variations" ) ) {
170 throw marley::Error( "Missing \"num_variations\" key in a"
171 " strength_variation weight calculator JSON configuration" );
172 }
173 const auto& nv = config.at( "num_variations" );
174 if ( !nv.is_integer() ) {
175 throw marley::Error( "The \"num_variations\" value must be a"
176 " positive integer" );
177 }
178 long num_instances = nv.to_long();
179 if ( num_instances <= 0 ) {
180 throw marley::Error( "The \"num_variations\" value must be a"
181 " positive integer" );
182 }
183
184 // Read the optional seed (default 0)
185 long seed = 0;
186 if ( config.has_key( "seed" ) ) {
187 seed = config.at( "seed" ).to_long();
188 }
189
190 // Create one shared RNG for all instances
191 auto rng = std::make_shared< std::mt19937_64 >(
192 static_cast< std::mt19937_64::result_type >( seed ) );
193
194 // Create num_instances weight calculators, each sharing the RNG
195 for ( long idx = 0; idx < num_instances; ++idx ) {
196 instances.push_back( std::shared_ptr<
197 StrengthVariationWeightCalculator >(
198 new StrengthVariationWeightCalculator(
199 base_name + '_' + std::to_string( idx ),
200 rng, resolved_reaction_file ) ) );
201 }
202 }
203 else if ( mode_str == "shift" ) {
204
205 // Reject keys inappropriate for shift
206 if ( config.has_key( "num_variations" ) ) {
207 throw marley::Error( "The \"num_variations\" key is not allowed"
208 " in shift mode for strength_variation weight"
209 " calculators" );
210 }
211 if ( config.has_key( "seed" ) ) {
212 throw marley::Error( "The \"seed\" key is not allowed"
213 " in shift mode for strength_variation weight"
214 " calculators" );
215 }
216
217 // Read and validate sigma_factor
218 if ( !config.has_key( "sigma_factor" ) ) {
219 throw marley::Error( "Missing \"sigma_factor\" key in a"
220 " strength_variation weight calculator JSON configuration"
221 " with shift mode" );
222 }
223 const auto& sf = config.at( "sigma_factor" );
224
225 std::vector< double > factors;
226 if ( sf.is_array() ) {
227 for ( const auto& elem : sf.array_range() ) {
228 factors.push_back( elem.to_double_or_throw() );
229 }
230 if ( factors.empty() ) {
231 throw marley::Error( "The \"sigma_factor\" array must have"
232 " at least one element" );
233 }
234 }
235 else {
236 factors.push_back( sf.to_double_or_throw() );
237 }
238
239 // Reject duplicate sigma_factor values (exact equality)
240 {
241 std::set< double > seen;
242 for ( double f : factors ) {
243 if ( !seen.insert( f ).second ) {
244 throw marley::Error( "Duplicate sigma_factor value \""
245 + std::to_string( f ) + "\" in strength_variation"
246 " weight calculator configuration" );
247 }
248 }
249 }
250
251 // Generate shortest roundtrip labels for all factors
252 auto labels = shortest_roundtrip_labels( factors );
253
254 // Create two instances per factor (+k and -k)
255 for ( size_t i = 0; i < factors.size(); ++i ) {
256 double k = factors[ i ];
257
258 // +k "up" instance
259 instances.push_back( std::shared_ptr<
260 StrengthVariationWeightCalculator >(
261 new StrengthVariationWeightCalculator(
262 base_name + "-up@" + labels[ i ],
263 +k, resolved_reaction_file ) ) );
264
265 // -k "down" instance
266 instances.push_back( std::shared_ptr<
267 StrengthVariationWeightCalculator >(
268 new StrengthVariationWeightCalculator(
269 base_name + "-down@" + labels[ i ],
270 -k, resolved_reaction_file ) ) );
271 }
272 }
273 else if ( mode_str == "unisim" ) {
274
275 // Reject keys inappropriate for unisim
276 if ( config.has_key( "num_variations" ) ) {
277 throw marley::Error( "The \"num_variations\" key is not allowed"
278 " in unisim mode for strength_variation weight calculators" );
279 }
280 if ( config.has_key( "seed" ) ) {
281 throw marley::Error( "The \"seed\" key is not allowed"
282 " in unisim mode for strength_variation weight calculators" );
283 }
284
285 // Read and validate sigma_factor
286 if ( !config.has_key( "sigma_factor" ) ) {
287 throw marley::Error( "Missing \"sigma_factor\" key in a"
288 " strength_variation weight calculator JSON configuration"
289 " with unisim mode" );
290 }
291 const auto& sf = config.at( "sigma_factor" );
292
293 std::vector< double > factors;
294 if ( sf.is_array() ) {
295 for ( const auto& elem : sf.array_range() ) {
296 factors.push_back( elem.to_double_or_throw() );
297 }
298 if ( factors.empty() ) {
299 throw marley::Error( "The \"sigma_factor\" array must have"
300 " at least one element" );
301 }
302 }
303 else {
304 factors.push_back( sf.to_double_or_throw() );
305 }
306
307 // Reject duplicate sigma_factor values (exact equality)
308 {
309 std::set< double > seen;
310 for ( double f : factors ) {
311 if ( !seen.insert( f ).second ) {
312 throw marley::Error( "Duplicate sigma_factor value \""
313 + std::to_string( f ) + "\" in strength_variation"
314 " weight calculator configuration" );
315 }
316 }
317 }
318
319 // Find the matching DiscreteNuclearReaction from the Generator
320 const DiscreteNuclearReaction* dnr = nullptr;
321 for ( const auto& rptr : gen.get_reactions() ) {
322 if ( rptr->source_file() != resolved_reaction_file ) continue;
323 auto pt = rptr->process_type();
327 {
328 continue;
329 }
330 dnr = dynamic_cast< const DiscreteNuclearReaction* >(
331 rptr.get() );
332 if ( !dnr ) throw marley::Error( "Reaction data file \""
333 + resolved_reaction_file + "\" was loaded as a discrete nuclear"
334 " reaction type but the corresponding Reaction object is not a"
335 " DiscreteNuclearReaction. This should not happen and likely"
336 " indicates a bug in MARLEY." );
337 break;
338 }
339 if ( !dnr ) throw marley::Error( "Could not find a discrete nuclear"
340 " reaction with the source file \"" + resolved_reaction_file
341 + "\" in the Generator." );
342
343 size_t M = dnr->matrix_elements().size();
344
345 // Generate shortest roundtrip labels for all factors
346 auto labels = shortest_roundtrip_labels( factors );
347
348 // Create two instances per factor per matrix element
349 for ( size_t i = 0; i < factors.size(); ++i ) {
350 double k = factors[ i ];
351 for ( size_t m = 0; m < M; ++m ) {
352 // +k "up" instance for this matrix element
353 instances.push_back( std::shared_ptr<
354 StrengthVariationWeightCalculator >(
355 new StrengthVariationWeightCalculator(
356 base_name + "-me" + std::to_string( m )
357 + "_up@" + labels[ i ],
358 +k, m, resolved_reaction_file ) ) );
359
360 // -k "down" instance for this matrix element
361 instances.push_back( std::shared_ptr<
362 StrengthVariationWeightCalculator >(
363 new StrengthVariationWeightCalculator(
364 base_name + "-me" + std::to_string( m )
365 + "_down@" + labels[ i ],
366 -k, m, resolved_reaction_file ) ) );
367 }
368 }
369 }
370 else {
371 throw marley::Error( "Unrecognized variation mode \""
372 + mode_str + "\" for strength_variation weight calculator."
373 " Allowed values are \"multisim\", \"shift\","
374 " and \"unisim\"" );
375 }
376
377 return instances;
378}
379
381 marley::Generator& gen ) const
382{
383 if ( initialized_ ) return;
384
385 // Scan the Generator's reactions to find the one matching our
386 // resolved reaction file path and a discrete nuclear process type
387 for ( const auto& reaction_ptr : gen.get_reactions() ) {
388
389 if ( reaction_ptr->source_file() != resolved_reaction_file_ ) continue;
390
391 // Only nuclear reactions populating discrete nuclear levels may be
392 // handled by this weight calculator
393 auto pt = reaction_ptr->process_type();
397 {
398 continue;
399 }
400
401 auto* dnr = dynamic_cast< const DiscreteNuclearReaction* >(
402 reaction_ptr.get() );
403 if ( !dnr ) throw marley::Error( "Reaction data file \""
404 + resolved_reaction_file_ + "\" was loaded as a discrete nuclear"
405 " reaction type but the corresponding Reaction object is not a"
406 " DiscreteNuclearReaction. This should not happen and likely"
407 " indicates a bug in MARLEY." );
408
409 dnr_ = dnr;
410 process_type_ = pt;
411 target_pdg_ = dnr->pdg_b();
412
413 // Pre-generate varied strengths depending on the variation mode
414 const auto& matrix_els = dnr->matrix_elements();
415 varied_.reserve( matrix_els.size() );
416
417 if ( mode_ == VariationMode::multisim ) {
418 std::normal_distribution< double > normal_dist;
419 for ( const auto& me : matrix_els ) {
420 double nom = me.strength();
421 double err_low = me.strength_err_low();
422 double err_high = me.strength_err_high();
423
424 double varied;
425 if ( err_low == 0. && err_high == 0. ) {
426 varied = nom;
427 }
428 else {
429 double u = normal_dist( *rng_ );
430 double sigma = ( u >= 0. ) ? err_high : err_low;
431 varied = nom + u * sigma;
432 if ( varied < 0. ) varied = 0.;
433 }
434 varied_.push_back( varied );
435 }
436 }
437 else if ( mode_ == VariationMode::shift ) {
438 for ( const auto& me : matrix_els ) {
439 double nom = me.strength();
440 double err_low = me.strength_err_low();
441 double err_high = me.strength_err_high();
442
443 double varied;
444 if ( err_low == 0. && err_high == 0. ) {
445 varied = nom;
446 }
447 else {
448 double sigma = ( sigma_factor_ >= 0. ) ? err_high : err_low;
449 varied = nom + sigma_factor_ * sigma;
450 if ( varied < 0. ) varied = 0.;
451 }
452 varied_.push_back( varied );
453 }
454 }
455 else { // unisim
456 for ( const auto& me : matrix_els ) {
457 varied_.push_back( me.strength() );
458 }
459 const auto& target_me = matrix_els[ me_idx_ ];
460 double nom = target_me.strength();
461 double err_low = target_me.strength_err_low();
462 double err_high = target_me.strength_err_high();
463 if ( err_low != 0. || err_high != 0. ) {
464 double sigma = ( sigma_factor_ >= 0. ) ? err_high : err_low;
465 double varied = nom + sigma_factor_ * sigma;
466 if ( varied < 0. ) varied = 0.;
467 varied_[ me_idx_ ] = varied;
468 }
469 }
470
471 initialized_ = true;
472 return;
473 }
474
475 throw marley::Error( "Could not find a discrete nuclear reaction"
476 " with the source file \"" + resolved_reaction_file_
477 + "\" in the Generator." );
478}
479
481 HepMC3::GenEvent& event, marley::Generator& gen ) const
482{
483 ensure_initialized( gen );
484
485 // Check that the event's process type matches this calculator's
486 auto sp_attr = event.attribute< HepMC3::IntAttribute >(
487 "signal_process_id" );
488 if ( !sp_attr ) return 1.;
489 auto event_pt = marley_hepmc3::from_nuhepmc_proc_id(
490 sp_attr->value() );
491 if ( event_pt != process_type_ ) return 1.;
492
493 // Verify that the event's target nucleus PDG code matches
494 auto target = marley_hepmc3::get_target( event );
495 if ( !target || target->pdg_id() != target_pdg_ ) return 1.;
496
497 // Read the matrix element index from the event
498 auto mi_attr = event.attribute< HepMC3::IntAttribute >(
499 "me_index" );
500 if ( !mi_attr ) return 1.;
501 size_t mi = static_cast< size_t >( mi_attr->value() );
502 if ( mi >= varied_.size() ) return 1.;
503
504 // Compute the weight as the ratio of the varied strength to the
505 // nominal strength
506 double nom = dnr_->matrix_elements().at( mi ).strength();
507 if ( nom == 0. ) return 1.;
508 return varied_.at( mi ) / nom;
509}
Stores event-related information.
Definition GenEvent.h:47
Attribute that holds an Integer implemented as an int.
Definition Attribute.h:157
A neutrino-nucleus reaction whose cross section is calculated according to the allowed approximation.
const std::vector< marley::MatrixElement > & matrix_elements() const
Allows access to the owned vector of MatrixElement objects.
Base class for all exceptions thrown by MARLEY functions.
Definition Error.hh:26
static const FileManager & Instance()
Get a const reference to the singleton instance of the FileManager.
std::string find_file(const std::string &base_name, const std::vector< std::string > &search_dirs) const
Searches for a file in the given directories.
The MARLEY Event generator.
Definition Generator.hh:54
const std::vector< std::unique_ptr< marley::Reaction > > & get_reactions() const
Get a const reference to the vector of Reaction objects owned by this Generator.
Definition Generator.hh:468
@ NC_Discrete
Nuclear matrix elements contain for a transition to a discrete nuclear level.
Definition Reaction.hh:62
@ AntiNeutrinoCC_Discrete
Nuclear matrix elements contain for a transition to a discrete nuclear level.
Definition Reaction.hh:61
@ NeutrinoCC_Discrete
Nuclear matrix elements contain for a transition to a discrete nuclear level.
Definition Reaction.hh:60
WeightCalculator that varies nuclear matrix element strengths according to their experimental uncerta...
Reaction::ProcessType process_type_
Process type of the matched Reaction.
void ensure_initialized(marley::Generator &gen) const
Lazy initialization of per-instance varied strengths.
std::string resolved_reaction_file_
Resolved path of the reaction input file of interest.
const DiscreteNuclearReaction * dnr_
Pointer to the matched DiscreteNuclearReaction.
size_t me_idx_
Index of the single matrix element to vary (unisim mode)
double sigma_factor_
Signed sigma factor for systematic shifts (shift and unisim modes)
static std::vector< std::shared_ptr< StrengthVariationWeightCalculator > > create_instances(const marley::JSON &config, marley::Generator &gen)
Static factory: validates JSON configuration and creates all variation instances. Called by the Weigh...
bool initialized_
Whether lazy initialization has been completed.
std::vector< double > varied_
Pre-generated varied strengths (one per matrix element in the matched Reaction)
int target_pdg_
Target nucleus PDG code for the matched Reaction.
VariationMode mode_
Variation mode for this instance.
virtual double weight(HepMC3::GenEvent &event, marley::Generator &gen) const override
Compute the weight for the given event.
std::shared_ptr< std::mt19937_64 > rng_
Shared RNG (seeded once, shared across N instances)