HybridNewtonConfig.hpp
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3/*
4 Copyright 2025 NORCE Research AS
5
6 This file is part of the Open Porous Media project (OPM).
7
8 OPM is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 OPM is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with OPM. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#ifndef HYBRID_NEWTON_CONFIG_HPP
23#define HYBRID_NEWTON_CONFIG_HPP
24
25#include <algorithm>
26#include <cmath>
27#include <fstream>
28#include <string>
29#include <stdexcept>
30#include <vector>
31
33
34namespace Opm {
35
41struct Scaler {
42 enum class Type { None, Standard, MinMax } type = Type::None;
43 double mean = 0.0;
44 double std = 1.0;
45 double min = 0.0;
46 double max = 1.0;
47
48 double scale(double raw_value) const {
49 switch (type) {
50 case Type::Standard:
51 return (std == 0.0) ? mean : (raw_value - mean) / std;
52 case Type::MinMax: {
53 double denom = max - min;
54 return (denom == 0.0) ? min : (raw_value - min) / denom;
55 }
56 case Type::None:
57 default:
58 return raw_value;
59 }
60 }
61
62 double unscale(double scaled_value) const {
63 switch (type) {
64 case Type::Standard: return scaled_value * std + mean;
65 case Type::MinMax: return scaled_value * (max - min) + min;
66 case Type::None:
67 default: return scaled_value;
68 }
69 }
70};
71
78struct Transform {
79 enum class Type { None, Log, Log10, Log1p } type = Type::None;
80
81 Transform() = default;
82 explicit Transform(const std::string& name) {
83 if (name == "log10") type = Type::Log10;
84 else if (name == "log") type = Type::Log;
85 else if (name == "log1p") type = Type::Log1p;
86 else type = Type::None;
87 }
88
89 double apply(double raw_value) const {
90 switch (type) {
91 case Type::Log10: return std::log10(raw_value);
92 case Type::Log: return std::log(raw_value);
93 case Type::Log1p: return std::log1p(raw_value);
94 case Type::None:
95 default: return raw_value;
96 }
97 }
98
99 double applyInverse(double transformed_value) const {
100 switch (type) {
101 case Type::Log10: return std::pow(10.0, transformed_value);
102 case Type::Log: return std::exp(transformed_value);
103 case Type::Log1p: return std::expm1(transformed_value);
104 case Type::None:
105 default: return transformed_value;
106 }
107 }
108};
109
118 bool is_delta = false;
119 std::string actual_name;
120
121 FeatureSpec() = default;
122};
123
131public:
132 std::string model_path;
133 std::string cell_indices_file;
134 std::vector<int> cell_indices;
135 std::size_t n_cells = 0;
136 std::vector<double> apply_times;
137 std::vector<std::pair<std::string, FeatureSpec>> input_features;
138 std::vector<std::pair<std::string, FeatureSpec>> output_features;
139
140 // Default constructor
142
149 explicit HybridNewtonConfig(const PropertyTree& model_config)
150 {
151 model_path = model_config.get<std::string>("model_path", "");
152 if (model_path.empty())
153 throw std::runtime_error("Missing 'model_path' in HybridNewton config");
154
155 cell_indices_file = model_config.get<std::string>("cell_indices_file", "");
156 if (cell_indices_file.empty())
157 throw std::runtime_error("Missing 'cell_indices_file' in HybridNewton config");
158
159 // Load cell indices immediately
160 cell_indices = loadCellIndicesFromFile(cell_indices_file);
161 n_cells = cell_indices.size();
162
163 // Load apply times
164 auto applyTimesOpt = model_config.get_child_optional("apply_times");
165 if (!applyTimesOpt)
166 throw std::runtime_error("Missing 'apply_times' in HybridNewton config");
167
168 for (const auto& key : applyTimesOpt->get_child_keys())
169 apply_times.push_back(applyTimesOpt->get_child(key).get<double>(""));
170
171 if (apply_times.empty())
172 throw std::runtime_error("'apply_times' must contain at least one value");
173
174 // Parse features
175 parseFeatures(model_config, "features.inputs", input_features);
176 parseFeatures(model_config, "features.outputs", output_features);
177 }
178
179 bool hasInputFeature(const std::string& name) const {
180 return std::any_of(input_features.begin(), input_features.end(),
181 [&](const auto& p) { return p.first == name; });
182 }
183
184 bool hasOutputFeature(const std::string& name) const {
185 return std::any_of(output_features.begin(), output_features.end(),
186 [&](const auto& p) { return p.first == name; });
187 }
188
195 void validateConfig(bool compositionSwitchEnabled) const
196 {
197 bool hasRsFeature = hasInputFeature("RS") ||
198 hasOutputFeature("RS") ||
199 hasOutputFeature("DELTA_RS");
200
201 bool hasRvFeature = hasInputFeature("RV") ||
202 hasOutputFeature("RV") ||
203 hasOutputFeature("DELTA_RV");
204
205 if ((hasRsFeature || hasRvFeature) && !compositionSwitchEnabled) {
206 OPM_THROW(std::runtime_error,
207 "HybridNewton: RS or RV features detected but composition support is disabled. "
208 "CompositionSwitch must be enabled for RS/RV features."
209 );
210 }
211 }
212
213private:
220 std::vector<int> loadCellIndicesFromFile(const std::string& filename) const {
221 std::vector<int> indices;
222 std::ifstream cellFile(filename);
223 if (!cellFile.is_open())
224 throw std::runtime_error("Cannot open cell indices file: " + filename);
225
226 std::string line;
227 int lineNumber = 0;
228 while (std::getline(cellFile, line)) {
229 ++lineNumber;
230 if (line.empty() || line[0] == '#') continue;
231 try { indices.push_back(std::stoi(line)); }
232 catch (...) {
233 throw std::runtime_error("Invalid cell index at line " + std::to_string(lineNumber) +
234 " in file " + filename + ": " + line);
235 }
236 }
237
238 if (indices.empty())
239 throw std::runtime_error("No valid cell indices found in file: " + filename);
240
241 return indices;
242 }
243
254 void parseFeatures(const PropertyTree& pt, const std::string& path,
255 std::vector<std::pair<std::string, FeatureSpec>>& features) {
256 auto subtreeOpt = pt.get_child_optional(path);
257 if (!subtreeOpt) return;
258
259 for (const auto& name : subtreeOpt->get_child_keys()) {
260 const PropertyTree& ft = subtreeOpt->get_child(name);
261 FeatureSpec spec;
262 spec.transform = Transform(ft.get<std::string>("feature_engineering", "none"));
263
264 if (auto sOpt = ft.get_child_optional("scaling_params")) {
265 const PropertyTree& s = *sOpt;
266 if (s.get_child_optional("mean") && s.get_child_optional("std")) {
267 spec.scaler.type = Scaler::Type::Standard;
268 spec.scaler.mean = s.get<double>("mean", 0.0);
269 spec.scaler.std = s.get<double>("std", 1.0);
270 } else if (s.get_child_optional("min") && s.get_child_optional("max")) {
271 spec.scaler.type = Scaler::Type::MinMax;
272 spec.scaler.min = s.get<double>("min", 0.0);
273 spec.scaler.max = s.get<double>("max", 1.0);
274 } else {
275 spec.scaler.type = Scaler::Type::None;
276 }
277 } else {
278 spec.scaler.type = Scaler::Type::None;
279 }
280
281 spec.is_delta = name.compare(0, 6, "DELTA_") == 0;
282 spec.actual_name = spec.is_delta ? name.substr(6) : name;
283
284 features.emplace_back(name, std::move(spec));
285 }
286 }
287};
288
289} // namespace Opm
290
291#endif
Configuration for a Hybrid Newton ML model.
Definition: HybridNewtonConfig.hpp:130
std::vector< double > apply_times
Definition: HybridNewtonConfig.hpp:136
bool hasInputFeature(const std::string &name) const
Definition: HybridNewtonConfig.hpp:179
std::vector< std::pair< std::string, FeatureSpec > > input_features
Definition: HybridNewtonConfig.hpp:137
std::vector< std::pair< std::string, FeatureSpec > > output_features
Definition: HybridNewtonConfig.hpp:138
void validateConfig(bool compositionSwitchEnabled) const
Validate feature compatibility with simulator settings.
Definition: HybridNewtonConfig.hpp:195
std::size_t n_cells
Definition: HybridNewtonConfig.hpp:135
bool hasOutputFeature(const std::string &name) const
Definition: HybridNewtonConfig.hpp:184
std::string cell_indices_file
Definition: HybridNewtonConfig.hpp:133
HybridNewtonConfig(const PropertyTree &model_config)
Construct configuration from a PropertyTree.
Definition: HybridNewtonConfig.hpp:149
std::vector< int > cell_indices
Definition: HybridNewtonConfig.hpp:134
std::string model_path
Definition: HybridNewtonConfig.hpp:132
Hierarchical collection of key/value pairs.
Definition: PropertyTree.hpp:39
T get(const std::string &key) const
std::optional< PropertyTree > get_child_optional(const std::string &key) const
Definition: blackoilbioeffectsmodules.hh:43
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Metadata for a single feature (input or output).
Definition: HybridNewtonConfig.hpp:115
Transform transform
Definition: HybridNewtonConfig.hpp:116
FeatureSpec()=default
bool is_delta
Definition: HybridNewtonConfig.hpp:118
std::string actual_name
Definition: HybridNewtonConfig.hpp:119
Scaler scaler
Definition: HybridNewtonConfig.hpp:117
Represents scaling information for a feature.
Definition: HybridNewtonConfig.hpp:41
double std
Definition: HybridNewtonConfig.hpp:44
double scale(double raw_value) const
Definition: HybridNewtonConfig.hpp:48
enum Opm::Scaler::Type type
double max
Definition: HybridNewtonConfig.hpp:46
double unscale(double scaled_value) const
Definition: HybridNewtonConfig.hpp:62
double min
Definition: HybridNewtonConfig.hpp:45
double mean
Definition: HybridNewtonConfig.hpp:43
Type
Definition: HybridNewtonConfig.hpp:42
Represents a transformation applied to a feature.
Definition: HybridNewtonConfig.hpp:78
double applyInverse(double transformed_value) const
Definition: HybridNewtonConfig.hpp:99
Transform()=default
Transform(const std::string &name)
Definition: HybridNewtonConfig.hpp:82
enum Opm::Transform::Type type
Type
Definition: HybridNewtonConfig.hpp:79
double apply(double raw_value) const
Definition: HybridNewtonConfig.hpp:89