HybridNewton.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_HPP
23#define HYBRID_NEWTON_HPP
24
25
28#include <opm/input/eclipse/Units/UnitSystem.hpp>
29
30#include <opm/ml/ml_model.hpp>
31
32#include <string>
33#include <vector>
34
35namespace Opm {
36
57template <typename TypeTag>
59{
60protected:
67
68 enum { numPhases = FluidSystem::numPhases };
69
70 enum { gasPhaseIdx = FluidSystem::gasPhaseIdx };
71 enum { oilPhaseIdx = FluidSystem::oilPhaseIdx };
72 enum { waterPhaseIdx = FluidSystem::waterPhaseIdx };
73
74 static constexpr bool compositionSwitchEnabled = Indices::compositionSwitchIdx >= 0;
75
76public:
77 explicit BlackOilHybridNewton(Simulator& simulator)
78 : simulator_(simulator)
79 , configsLoaded_(false)
80 {}
81
100 {
101 // Check if flag activated
102 if (!Parameters::Get<Parameters::UseHyNe>())
103 return;
104
106
107 if (!configsLoaded_) {
108 std::string config_file = Parameters::Get<Parameters::HyNeConfigFile>();
109 PropertyTree pt(config_file);
110 for (const auto& model_key : pt.get_child_keys()) {
111 HybridNewtonConfig config(pt.get_child(model_key));
113 configs_.push_back(std::move(config));
114 }
115 configsLoaded_ = true;
116 }
117
118 Scalar current_time = simulator_.time();
119 // Find and apply all models that should run at this time
120 for (const auto& config : configs_) {
121 if (shouldApplyHybridNewton(current_time, config)) {
122 runHybridNewton(config);
123 }
124 }
125 }
126
127private:
142 void runHybridNewton(const HybridNewtonConfig& config)
143 {
144 auto input = constructInputTensor(config);
145 auto output = constructOutputTensor(input, config);
146 updateInitialGuess(output, config);
147 }
148
149protected:
150
152 {
153 const auto& eclState = simulator_.vanguard().eclState();
154 const auto& phases = eclState.runspec().phases();
155
156 bool hasWater = phases.active(Phase::WATER);
157 bool hasGas = phases.active(Phase::GAS);
158 bool hasOil = phases.active(Phase::OIL);
159 bool hasSolvent = phases.active(Phase::SOLVENT);
160
161 // Check for three-phase black oil system
162 if (!(hasWater && hasOil && hasGas && !hasSolvent)) {
163 OPM_THROW(std::runtime_error,
164 "HybridNewton: Unsupported fluid system. Only three-phase black oil is supported.");
165 }
166 }
167
188 bool shouldApplyHybridNewton(Scalar current_time, const HybridNewtonConfig& config) const
189 {
190 if (config.apply_times.size() == 1) {
191 // Apply exactly at one point in time (with optional tolerance)
192 constexpr Scalar tolerance = 1e-6;
193 bool apply = std::abs(current_time - config.apply_times[0]) < tolerance;
194 return apply;
195 }
196
197 if (config.apply_times.size() == 2) {
198 Scalar start_time = config.apply_times[0];
199 Scalar end_time = config.apply_times[1];
200 bool apply = (current_time >= start_time) && (current_time <= end_time);
201 return apply;
202 }
203
204 // If apply_times has an unexpected number of entries
205 return false;
206 }
207
208
233 ML::Tensor<Evaluation> constructInputTensor(
234 const HybridNewtonConfig& config
235 )
236 {
237 const auto& features = config.input_features;
238
239 // compute total length directly
240 std::size_t input_tensor_length = 0;
241 for (const auto& feature : features) {
242 const FeatureSpec& spec = feature.second;
243 if (spec.actual_name == "TIMESTEP") {
244 input_tensor_length += 1;
245 } else {
246 input_tensor_length += config.n_cells;
247 }
248 }
249
250 ML::Tensor<Evaluation> input(input_tensor_length);
251 std::size_t offset = 0;
252
253 // fill in exact feature order from config
254 for (const auto& feature: features) {
255 const FeatureSpec& spec = feature.second;
256
257 if (spec.actual_name == "TIMESTEP") {
258 auto value = static_cast<Evaluation>(getScalarFeatureValue(spec));
259 input(offset++) = value;
260 } else {
261 for (std::size_t i = 0; i < config.n_cells; ++i) {
262 auto value = static_cast<Evaluation>(
263 getPerCellFeatureValue(spec, config.cell_indices[i])
264 );
265 input(offset + i) = value;
266 }
267 offset += config.n_cells;
268 }
269 }
270
271 return input;
272 }
273
289 {
290 Scalar value = 0.0;
291
292 if (spec.actual_name == "TIMESTEP") {
293 value = simulator_.timeStepSize();
294 } else {
295 OPM_THROW(std::runtime_error, "Unknown scalar feature: " + spec.actual_name);
296 }
297
298 value = spec.transform.apply(value);
299 return spec.scaler.scale(value);
300 }
301
319 Scalar getPerCellFeatureValue(const FeatureSpec& spec, int cell_index)
320 {
321 const auto& intQuants = simulator_.model().intensiveQuantities(cell_index, 0);
322 const auto& fs = intQuants.fluidState();
323 const auto& unitSyst = simulator_.vanguard().schedule().getUnits();
324
325 Scalar value = 0.0;
326
327 if (spec.actual_name == "PRESSURE") {
328 value = getValue(fs.pressure(oilPhaseIdx));
329 value = unitSyst.from_si(UnitSystem::measure::pressure, value);
330 } else if (spec.actual_name == "SWAT") {
331 value = getValue(fs.saturation(waterPhaseIdx));
332 } else if (spec.actual_name == "SGAS") {
333 value = getValue(fs.saturation(gasPhaseIdx));
334 } else if (spec.actual_name == "SOIL") {
335 value = getValue(fs.saturation(oilPhaseIdx));
336 } else if (spec.actual_name == "RS") {
337 value = getValue(fs.Rs());
338 value = unitSyst.from_si(UnitSystem::measure::gas_oil_ratio, value);
339 } else if (spec.actual_name == "RV") {
340 value = getValue(fs.Rv());
341 value = unitSyst.from_si(UnitSystem::measure::oil_gas_ratio, value);
342 } else if (spec.actual_name == "PERMX") {
343 const auto& eclState = simulator_.vanguard().eclState();
344 const auto& fp = eclState.fieldProps();
345 auto permX = fp.get_double("PERMX");
346 value = permX[cell_index];
347 value = unitSyst.from_si(UnitSystem::measure::permeability, value);
348 } else {
349 OPM_THROW(std::runtime_error, "Unknown per-cell feature: " + spec.actual_name);
350 }
351
352 Scalar transformed = spec.transform.apply(value);
353 Scalar scaled = spec.scaler.scale(transformed);
354 return scaled;
355 }
356
357
376 ML::Tensor<Evaluation>
377 constructOutputTensor(const ML::Tensor<Evaluation>& input, const HybridNewtonConfig& config)
378 {
379 const auto& features = config.output_features;
380 const int n_features = features.size();
381
382 ML::NNModel<Evaluation> model;
383 model.loadModel(config.model_path);
384
385 ML::Tensor<Evaluation> output(1, config.n_cells * n_features);
386 model.apply(input, output);
387
388 return output;
389 }
390
419 ML::Tensor<Evaluation>& output,
420 const HybridNewtonConfig& config
421 )
422 {
423 const auto& features = config.output_features;
424
425 FeatureFlags flags = flagFeatures(features);
426
427 const auto& unitSyst = simulator_.vanguard().schedule().getUnits();
428
429 for (std::size_t i = 0; i < config.n_cells; ++i) {
430 const int cell_idx = config.cell_indices[i];
431 const auto& intQuants = simulator_.model().intensiveQuantities(cell_idx, /*timeIdx*/0);
432 auto fs = intQuants.fluidState();
433
434 int feature_idx = 0;
435
436 // Temp variables per cell
437 Scalar sw_val = -1.0;
438 Scalar so_val = -1.0;
439 Scalar sg_val = -1.0;
440 Scalar po_val = -1.0;
441
442 for (const auto& [name, spec] : features) {
443
444 auto scaled_value = getValue(output(feature_idx * config.n_cells + i));
445
446 // Inverse scaling
447 Scalar raw_value = spec.scaler.unscale(scaled_value);
448
449 // Inverse transform
450 raw_value = spec.transform.applyInverse(raw_value);
451
452 if (spec.is_delta) {
453 if (spec.actual_name == "PRESSURE") {
454 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::pressure, getValue(fs.pressure(oilPhaseIdx)));
455 } else if (spec.actual_name == "SWAT") {
456 raw_value += getValue(fs.saturation(waterPhaseIdx));
457 } else if (spec.actual_name == "SOIL") {
458 raw_value += getValue(fs.saturation(oilPhaseIdx));
459 } else if (spec.actual_name == "SGAS") {
460 raw_value += getValue(fs.saturation(gasPhaseIdx));
461 } else if (spec.actual_name == "RS") {
462 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::gas_oil_ratio, getValue(fs.Rs()));
463 } else if (spec.actual_name == "RV") {
464 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::oil_gas_ratio, getValue(fs.Rv()));
465 } else {
466 OPM_THROW(std::runtime_error, "Unknown delta feature: " + name);
467 }
468 }
469
470 if (spec.actual_name == "PRESSURE") {
471 po_val = unitSyst.to_si(UnitSystem::measure::pressure, raw_value);
472 } else if (spec.actual_name == "SWAT") {
473 sw_val = raw_value;
474 } else if (spec.actual_name == "SOIL") {
475 so_val = raw_value;
476 } else if (spec.actual_name == "SGAS") {
477 sg_val = raw_value;
478 } else if (spec.actual_name == "RS") {
479 if constexpr (compositionSwitchEnabled) {
480 raw_value = unitSyst.to_si(UnitSystem::measure::gas_oil_ratio, raw_value);
481 fs.setRs(raw_value);
482 }
483 } else if (spec.actual_name == "RV") {
484 if constexpr (compositionSwitchEnabled) {
485 raw_value = unitSyst.to_si(UnitSystem::measure::oil_gas_ratio, raw_value);
486 fs.setRv(raw_value);
487 }
488 } else {
489 OPM_THROW(std::runtime_error, "Unknown output feature: " + name);
490 }
491
492 ++feature_idx;
493 }
494
495 int sat_count = static_cast<int>(flags.has_SWAT) + static_cast<int>(flags.has_SOIL) + static_cast<int>(flags.has_SGAS);
496
497 if (sat_count >= 2) {
498 Scalar sw = sw_val;
499 Scalar so = so_val;
500 Scalar sg = sg_val;
501
502 if (!flags.has_SWAT) {
503 sw = 1.0 - so - sg;
504 } else if (!flags.has_SOIL) {
505 so = 1.0 - sw - sg;
506 } else if (!flags.has_SGAS) {
507 sg = 1.0 - sw - so;
508 }
509
510 sw = max(0.0, min(sw, 1.0));
511 so = max(0.0, min(so, 1.0));
512 sg = max(0.0, min(sg, 1.0));
513
514 Scalar sum = sw + so + sg;
515 if (sum <= 1e-12) {
516 OPM_THROW(std::runtime_error, "Saturation sum is zero in cell " + std::to_string(cell_idx));
517 }
518
519 fs.setSaturation(waterPhaseIdx, sw / sum);
520 fs.setSaturation(oilPhaseIdx, so / sum);
521 fs.setSaturation(gasPhaseIdx, sg / sum);
522 }
523
524 if (flags.has_PRESSURE) {
525 std::array<Evaluation, numPhases> pC;
526 const auto& materialParams = simulator_.problem().materialLawParams(cell_idx);
527 MaterialLaw::capillaryPressures(pC, materialParams, fs);
528
529 for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
530 if (!FluidSystem::phaseIsActive(phaseIdx)) continue;
531 if (phaseIdx == oilPhaseIdx) {
532 fs.setPressure(phaseIdx, po_val);
533 } else {
534 fs.setPressure(phaseIdx, po_val - pC[phaseIdx]);
535 }
536 }
537 }
538
539 auto& primaryVars = simulator_.model().solution(/*timeIdx*/0)[cell_idx];
540 primaryVars.assignNaive(fs);
541 }
542
543 simulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx*/0);
544 }
545
547 bool has_SWAT = false;
548 bool has_SOIL = false;
549 bool has_SGAS = false;
550 bool has_PRESSURE = false;
551 };
552
553 FeatureFlags flagFeatures(const std::vector<std::pair<std::string, FeatureSpec>>& features)
554 {
555 FeatureFlags flags;
556
557 for (const auto& [name, spec] : features) {
558
559 if (spec.actual_name == "SWAT") {
560 flags.has_SWAT = true;
561 } else if (spec.actual_name == "SOIL") {
562 flags.has_SOIL = true;
563 } else if (spec.actual_name == "SGAS") {
564 flags.has_SGAS = true;
565 } else if (spec.actual_name == "PRESSURE") {
566 flags.has_PRESSURE = true;
567 }
568 }
569
570 return flags;
571 }
572
573 protected:
575 std::vector<HybridNewtonConfig> configs_;
577 };
578
579} // namespace Opm
580
581#endif // HYBRID_NEWTON_CLASS_HPP
Hybrid Newton solver extension for the black-oil model.
Definition: HybridNewton.hpp:59
bool configsLoaded_
Definition: HybridNewton.hpp:576
GetPropType< TypeTag, Properties::Indices > Indices
Definition: HybridNewton.hpp:65
@ gasPhaseIdx
Definition: HybridNewton.hpp:70
void tryApplyHybridNewton()
Attempt to apply the Hybrid Newton correction at the current timestep.
Definition: HybridNewton.hpp:99
FeatureFlags flagFeatures(const std::vector< std::pair< std::string, FeatureSpec > > &features)
Definition: HybridNewton.hpp:553
@ numPhases
Definition: HybridNewton.hpp:68
GetPropType< TypeTag, Properties::Evaluation > Evaluation
Definition: HybridNewton.hpp:62
BlackOilHybridNewton(Simulator &simulator)
Definition: HybridNewton.hpp:77
Scalar getPerCellFeatureValue(const FeatureSpec &spec, int cell_index)
Retrieve and transform a per-cell feature value.
Definition: HybridNewton.hpp:319
std::vector< HybridNewtonConfig > configs_
Definition: HybridNewton.hpp:575
bool shouldApplyHybridNewton(Scalar current_time, const HybridNewtonConfig &config) const
Check whether the Hybrid Newton method should be applied at the given time.
Definition: HybridNewton.hpp:188
static constexpr bool compositionSwitchEnabled
Definition: HybridNewton.hpp:74
GetPropType< TypeTag, Properties::MaterialLaw > MaterialLaw
Definition: HybridNewton.hpp:64
@ waterPhaseIdx
Definition: HybridNewton.hpp:72
GetPropType< TypeTag, Properties::Scalar > Scalar
Definition: HybridNewton.hpp:66
ML::Tensor< Evaluation > constructOutputTensor(const ML::Tensor< Evaluation > &input, const HybridNewtonConfig &config)
Run the Hybrid Newton model to produce output predictions.
Definition: HybridNewton.hpp:377
GetPropType< TypeTag, Properties::Simulator > Simulator
Definition: HybridNewton.hpp:61
GetPropType< TypeTag, Properties::FluidSystem > FluidSystem
Definition: HybridNewton.hpp:63
Scalar getScalarFeatureValue(const FeatureSpec &spec)
Retrieve and transform a scalar feature (global across the domain).
Definition: HybridNewton.hpp:288
void updateInitialGuess(ML::Tensor< Evaluation > &output, const HybridNewtonConfig &config)
Update the nonlinear solver's initial guess using ML predictions.
Definition: HybridNewton.hpp:418
void validateFluidSystem()
Definition: HybridNewton.hpp:151
@ oilPhaseIdx
Definition: HybridNewton.hpp:71
ML::Tensor< Evaluation > constructInputTensor(const HybridNewtonConfig &config)
Construct the input feature tensor for the Hybrid Newton model.
Definition: HybridNewton.hpp:233
Simulator & simulator_
Definition: HybridNewton.hpp:574
Configuration for a Hybrid Newton ML model.
Definition: HybridNewtonConfig.hpp:130
std::vector< double > apply_times
Definition: HybridNewtonConfig.hpp:136
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
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
std::vector< std::string > get_child_keys() const
PropertyTree get_child(const std::string &key) const
Definition: blackoilbioeffectsmodules.hh:43
typename Properties::Detail::GetPropImpl< TypeTag, Property >::type::type GetPropType
get the type alias defined in the property (equivalent to old macro GET_PROP_TYPE(....
Definition: propertysystem.hh:233
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Definition: HybridNewton.hpp:546
bool has_SOIL
Definition: HybridNewton.hpp:548
bool has_SWAT
Definition: HybridNewton.hpp:547
bool has_SGAS
Definition: HybridNewton.hpp:549
bool has_PRESSURE
Definition: HybridNewton.hpp:550
Metadata for a single feature (input or output).
Definition: HybridNewtonConfig.hpp:115
Transform transform
Definition: HybridNewtonConfig.hpp:116
std::string actual_name
Definition: HybridNewtonConfig.hpp:119
Scaler scaler
Definition: HybridNewtonConfig.hpp:117
double scale(double raw_value) const
Definition: HybridNewtonConfig.hpp:48
double apply(double raw_value) const
Definition: HybridNewtonConfig.hpp:89