Hyperiso 1.0.3
Modular flavour-physics calculations, Wilson coefficients and statistical inference
Loading...
Searching...
No Matches
NuisanceReader.cpp
Go to the documentation of this file.
1#include "NuisanceReader.h"
2
3#include <algorithm>
4#include <cctype>
5#include <stdexcept>
6#include <sstream>
7
8namespace {
9
10std::string strip_optional_quotes(std::string s) {
11 if (s.size() >= 2) {
12 const char first = s.front();
13 const char last = s.back();
14
15 if ((first == '"' && last == '"') ||
16 (first == '\'' && last == '\'')) {
17 return s.substr(1, s.size() - 2);
18 }
19 }
20 return s;
21}
22
23bool is_numeric_string(const std::string& s) {
24 if (s.empty()) return false;
25 char* end = nullptr;
26 std::strtod(s.c_str(), &end);
27 return end != s.c_str() && *end == '\0';
28}
29
30bool looks_like_nuisance_entry(const std::shared_ptr<DBNode>& node) {
31 return node
32 && node->contains("block")
33 && node->contains("code");
34}
35
36const char* value_kind(const DBNode::Value& value) {
37 if (std::holds_alternative<BlockName>(value)) return "string";
38 if (std::holds_alternative<int>(value)) return "int";
39 if (std::holds_alternative<double>(value)) return "double";
40 if (std::holds_alternative<bool>(value)) return "bool";
41 if (std::holds_alternative<std::shared_ptr<DBNode>>(value)) return "node";
42 if (std::holds_alternative<std::vector<std::shared_ptr<DBNode>>>(value)) return "list";
43 return "unknown";
44}
45
46void collect_entry_nodes_from_value(const DBNode::Value& value,
47 std::vector<std::shared_ptr<DBNode>>& out);
48
49void collect_entry_nodes_from_node(const std::shared_ptr<DBNode>& node,
50 std::vector<std::shared_ptr<DBNode>>& out)
51{
52 if (!node) {
53 return;
54 }
55
56 if (looks_like_nuisance_entry(node)) {
57 out.push_back(node);
58 return;
59 }
60
61 for (const auto& key : node->get_keys()) {
62 const auto child = node->get(key);
63 collect_entry_nodes_from_value(child, out);
64 }
65}
66
67void collect_entry_nodes_from_value(const DBNode::Value& value,
68 std::vector<std::shared_ptr<DBNode>>& out)
69{
70 if (std::holds_alternative<std::vector<std::shared_ptr<DBNode>>>(value)) {
71 const auto& entries = std::get<std::vector<std::shared_ptr<DBNode>>>(value);
72 for (const auto& entry : entries) {
73 collect_entry_nodes_from_node(entry, out);
74 }
75 return;
76 }
77
78 if (std::holds_alternative<std::shared_ptr<DBNode>>(value)) {
79 collect_entry_nodes_from_node(std::get<std::shared_ptr<DBNode>>(value), out);
80 return;
81 }
82}
83
84std::vector<std::shared_ptr<DBNode>> extract_nuisance_entries(const DBNode::Value& value) {
85 std::vector<std::shared_ptr<DBNode>> out;
86 collect_entry_nodes_from_value(value, out);
87 return out;
88}
89
90}
91
92NuisanceReader::NuisanceReader(std::shared_ptr<INuisancePathsProvider> paths_provider)
93 : paths_provider_(std::move(paths_provider))
94{
95 if (!paths_provider_) {
96 throw std::invalid_argument("NuisanceReader: paths_provider is null");
97 }
98}
99
101 return paths_provider_->default_nuisances_path();
102}
103
105 return paths_provider_->user_nuisances_path();
106}
107
109 NuisanceRegistry registry;
110 const fs::path path = default_path();
111
112 if (path.empty()) {
113 throw std::runtime_error("NuisanceReader: default path is empty");
114 }
115 if (!fs::exists(path)) {
116 throw std::runtime_error("NuisanceReader: default file not found: " + path.string());
117 }
118
119 merge_file_into_registry(path, registry);
120 return registry;
121}
122
126
127NuisanceRegistry NuisanceReader::load_user(const fs::path& path) const {
128 NuisanceRegistry registry;
129
130 if (path.empty()) {
131 return registry;
132 }
133
134 if (!fs::exists(path)) {
135 throw std::runtime_error("NuisanceReader: user file not found: " + path.string());
136 }
137
138 merge_file_into_registry(path, registry);
139 return registry;
140}
141
143 NuisanceRegistry registry = load_default();
144 NuisanceRegistry user_registry = load_user();
145
146 for (const auto& [pid, spec] : user_registry) {
147 registry[pid] = spec;
148 }
149
150 return registry;
151}
152
153
154void NuisanceReader::merge_file_into_registry(const fs::path& path,
155 NuisanceRegistry& registry) const
156{
158 if (!provider) {
159 throw std::runtime_error("NuisanceReader: could not create provider for: " + path.string());
160 }
161
162 auto root = provider->provide_db_as_node();
163 if (!root) {
164 throw std::runtime_error("NuisanceReader: provider returned null DBNode for: " + path.string());
165 }
166
167 merge_node_into_registry(*root, registry);
168}
169
170void NuisanceReader::merge_node_into_registry(const DBNode& root,
171 NuisanceRegistry& registry) const
172{
173 if (!root.contains("nuisances")) {
174 return;
175 }
176
177 const auto nuisances_value = root.get("nuisances");
178 const auto entries = extract_nuisance_entries(nuisances_value);
179
180 // if (entries.empty()) {
181 // std::ostringstream oss;
182 // oss << "NuisanceReader: 'nuisances' found but no entries could be extracted "
183 // << "(stored as " << value_kind(nuisances_value) << ")";
184 // throw std::runtime_error(oss.str());
185 // }
186
187 for (const auto& entry_ptr : entries) {
188 if (!entry_ptr) {
189 continue;
190 }
191
192 NuisanceSpec spec = parse_entry(*entry_ptr);
193 registry[spec.param_id] = std::move(spec);
194 }
195}
196
197NuisanceSpec NuisanceReader::parse_entry(const DBNode& entry) {
198 NuisanceSpec spec;
199
200 spec.param_id = make_param_id(entry);
201
202 const double min_val = value_to_double(
203 get_required_value(entry, {"min_val", "min"}),
204 "min_val"
205 );
206
207 const double max_val = value_to_double(
208 get_required_value(entry, {"max_val", "max"}),
209 "max_val"
210 );
211
212 if (min_val > max_val) {
213 std::ostringstream oss;
214 oss << "NuisanceReader: invalid bounds for parameter (min_val="
215 << min_val << " > max_val=" << max_val << ")";
216 throw std::runtime_error(oss.str());
217 }
218
219 spec.bounds = {min_val, max_val};
220
221 const std::string distribution = value_to_string(
222 get_required_value(entry, {"distribution", "marginal"}),
223 "distribution"
224 );
225
226 spec.marginal = parse_marginal_type(distribution);
227
228 return spec;
229}
230
231ParamId NuisanceReader::make_param_id(const DBNode& entry) {
232 const std::string block_str = value_to_string(
233 get_required_value(entry, {"block"}),
234 "block"
235 );
236
237 const std::string code_str = value_to_code_string(
238 get_required_value(entry, {"code"}),
239 "code"
240 );
241
242 BlockName block(block_str);
243 LhaID code(code_str);
244
245 return ParamId(block, code);
246}
247
248DBNode::Value NuisanceReader::get_required_value(const DBNode& node,
249 std::initializer_list<const char*> candidate_keys)
250{
251 for (const char* key : candidate_keys) {
252 if (node.contains(key)) {
253 return node.get(key);
254 }
255 }
256
257 std::ostringstream oss;
258 oss << "NuisanceReader: missing required field among {";
259 bool first = true;
260 for (const char* key : candidate_keys) {
261 if (!first) oss << ", ";
262 oss << key;
263 first = false;
264 }
265 oss << "}";
266
267 throw std::runtime_error(oss.str());
268}
269
270std::string NuisanceReader::value_to_string(const DBNode::Value& value,
271 const std::string& field_name)
272{
273 if (std::holds_alternative<BlockName>(value)) {
274 return strip_optional_quotes(std::get<BlockName>(value));
275 }
276
277 if (std::holds_alternative<int>(value)) {
278 return std::to_string(std::get<int>(value));
279 }
280
281 if (std::holds_alternative<double>(value)) {
282 std::ostringstream oss;
283 oss << std::get<double>(value);
284 return oss.str();
285 }
286
287 throw std::runtime_error(
288 "NuisanceReader: field '" + field_name + "' must be string-like"
289 );
290}
291
292std::string NuisanceReader::value_to_code_string(const DBNode::Value& value,
293 const std::string& field_name)
294{
295 const std::string s = value_to_string(value, field_name);
296 return strip_optional_quotes(s);
297}
298
299double NuisanceReader::value_to_double(const DBNode::Value& value,
300 const std::string& field_name)
301{
302 if (std::holds_alternative<double>(value)) {
303 return std::get<double>(value);
304 }
305
306 if (std::holds_alternative<int>(value)) {
307 return static_cast<double>(std::get<int>(value));
308 }
309
310 if (std::holds_alternative<BlockName>(value)) {
311 const std::string s = strip_optional_quotes(std::get<BlockName>(value));
312 if (is_numeric_string(s)) {
313 return std::stod(s);
314 }
315 }
316
317 throw std::runtime_error(
318 "NuisanceReader: field '" + field_name + "' must be numeric"
319 );
320}
321
322MarginalType NuisanceReader::parse_marginal_type(std::string raw) {
323 raw = normalise(strip_optional_quotes(std::move(raw)));
324
325 if (raw == "gaussian") {
327 }
328
329 if (raw == "half_gaussian" || raw == "halfgaussian" || raw == "split_gaussian") {
331 }
332
333 if (raw == "flat" || raw == "uniform") {
334 return MarginalType::FLAT;
335 }
336
337 if (raw == "likelihood") {
339 }
340
341 throw std::runtime_error("NuisanceReader: unknown distribution '" + raw + "'");
342}
343
344std::string NuisanceReader::normalise(std::string s) {
345 std::transform(s.begin(), s.end(), s.begin(),
346 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
347
348 std::replace(s.begin(), s.end(), '-', '_');
349 std::replace(s.begin(), s.end(), ' ', '_');
350
351 return s;
352}
MarginalType
Supported marginal-distribution families.
@ GAUSSIAN
Symmetric Gaussian marginal.
@ HALF_GAUSSIAN
Asymmetric / half-Gaussian-like marginal (currently mapped to split Gaussian logic).
@ LIKELIHOOD
Discrete likelihood-based marginal built from weighted support points.
@ FLAT
Uniform (flat) marginal on a finite interval.
Concrete reader for nuisance-parameter definition files.
std::unordered_map< ParamId, NuisanceSpec > NuisanceRegistry
Registry of nuisance specifications indexed by parameter id.
std::unordered_set< T > get_keys(const std::map< T, U > &map)
Extracts the key set from a std::map into an unordered_set.
Definition Utils.h:108
Block identifier with alias support.
Definition BlockName.h:59
static std::shared_ptr< IDBNodeProvider > createDBNodeProvider(fs::path src_path)
Creates an IDBNodeProvider for a given file path.
Value get(Keys &&... keys) const
Retrieves a value from the node using a sequence of keys.
std::variant< BlockName, int, double, bool, std::shared_ptr< DBNode >, std::vector< std::shared_ptr< DBNode > > > Value
Variant type used to store values in the tree.
Definition DBNode.h:45
bool contains(const BlockName &key) const
Checks whether the node contains a direct child with the given key.
Definition DBNode.cpp:263
fs::path default_path() const
Returns the configured default nuisance-file path.
NuisanceReader(std::shared_ptr< INuisancePathsProvider > paths_provider)
Constructs a nuisance reader with an external path provider.
NuisanceRegistry load() const
Loads the merged nuisance registry.
NuisanceRegistry load_default() const override
Loads the built-in nuisance-parameter registry.
fs::path user_path() const
Returns the configured user nuisance-file path.
NuisanceRegistry load_user() const override
Loads the nuisance registry from the configured user source.
Hash specialization for SymbolId<Tag>.
Definition BlockName.h:353
Represents an identifier of a LHA element, possibly containing several sub-ids.
Definition LhaID.h:56
Specification of one nuisance parameter.
MarginalType marginal
Marginal model used for the nuisance constraint.
std::pair< double, double > bounds
Inclusive lower and upper bounds for the nuisance value.
ParamId param_id
Parameter identifier, usually an LHA block/code pair.
Composite identifier for a single parameter.
Definition ParamID.h:57