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 <limits>
33#include <string>
34#include <vector>
35
36namespace Opm {
37
58template <typename TypeTag>
60{
61protected:
68
69 enum { numPhases = FluidSystem::numPhases };
70
71 enum { gasPhaseIdx = FluidSystem::gasPhaseIdx };
72 enum { oilPhaseIdx = FluidSystem::oilPhaseIdx };
73 enum { waterPhaseIdx = FluidSystem::waterPhaseIdx };
74
75 static constexpr bool compositionSwitchEnabled =
76 Indices::compositionSwitchIdx != std::numeric_limits<unsigned>::max();
77
78public:
79 explicit BlackOilHybridNewton(Simulator& simulator)
80 : simulator_(simulator)
81 , configsLoaded_(false)
82 {}
83
102 {
103 // Check if flag activated
104 if (!Parameters::Get<Parameters::UseHybridNewton>())
105 return;
106
108
109 if (!configsLoaded_) {
110 std::string config_file = Parameters::Get<Parameters::HybridNewtonConfigFile>();
111 PropertyTree pt(config_file);
112 for (const auto& model_key : pt.get_child_keys()) {
113 HybridNewtonConfig config(pt.get_child(model_key));
115 configs_.push_back(std::move(config));
116 }
117 configsLoaded_ = true;
118 }
119
120 Scalar current_time = simulator_.time();
121 // Find and apply all models that should run at this time
122 for (const auto& config : configs_) {
123 if (shouldApplyHybridNewton(current_time, config)) {
124 runHybridNewton(config);
125 }
126 }
127 }
128
129private:
144 void runHybridNewton(const HybridNewtonConfig& config)
145 {
146 auto input = constructInputTensor(config);
147 auto output = constructOutputTensor(input, config);
148 updateInitialGuess(output, config);
149 }
150
151protected:
153 {
154 const auto& eclState = simulator_.vanguard().eclState();
155 const auto& phases = eclState.runspec().phases();
156
157 bool hasWater = phases.active(Phase::WATER);
158 bool hasGas = phases.active(Phase::GAS);
159 bool hasOil = phases.active(Phase::OIL);
160 bool hasSolvent = phases.active(Phase::SOLVENT);
161
162 // Check for three-phase black oil system
163 if (!(hasWater && hasOil && hasGas && !hasSolvent)) {
164 OPM_THROW(std::runtime_error,
165 "HybridNewton: Unsupported fluid system. Only three-phase black oil is supported.");
166 }
167 }
168
189 bool shouldApplyHybridNewton(Scalar current_time, const HybridNewtonConfig& config) const
190 {
191 if (config.apply_times.size() == 1) {
192 // Apply exactly at one point in time (with optional tolerance)
193 constexpr Scalar tolerance = 1e-6;
194 bool apply = std::abs(current_time - config.apply_times[0]) < tolerance;
195 return apply;
196 }
197
198 if (config.apply_times.size() == 2) {
199 Scalar start_time = config.apply_times[0];
200 Scalar end_time = config.apply_times[1];
201 bool apply = (current_time >= start_time) && (current_time <= end_time);
202 return apply;
203 }
204
205 // If apply_times has an unexpected number of entries
206 return false;
207 }
208
209
234 ML::Tensor<Evaluation>
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,
378 const HybridNewtonConfig& config)
379 {
380 const auto& features = config.output_features;
381 const int n_features = features.size();
382
383 ML::NNModel<Evaluation> model;
384 model.loadModel(config.model_path);
385
386 ML::Tensor<Evaluation> output(1, config.n_cells * n_features);
387 model.apply(input, output);
388
389 return output;
390 }
391
419 void updateInitialGuess(ML::Tensor<Evaluation>& output,
420 const HybridNewtonConfig& config)
421 {
422 const auto& features = config.output_features;
423
424 FeatureFlags flags = flagFeatures(features);
425
426 const auto& unitSyst = simulator_.vanguard().schedule().getUnits();
427
428 for (std::size_t i = 0; i < config.n_cells; ++i) {
429 const int cell_idx = config.cell_indices[i];
430 const auto& intQuants = simulator_.model().intensiveQuantities(cell_idx, /*timeIdx*/0);
431 auto fs = intQuants.fluidState();
432
433 int feature_idx = 0;
434
435 // Temp variables per cell
436 Scalar sw_val = -1.0;
437 Scalar so_val = -1.0;
438 Scalar sg_val = -1.0;
439 Scalar po_val = -1.0;
440
441 for (const auto& [name, spec] : features) {
442
443 auto scaled_value = getValue(output(feature_idx * config.n_cells + i));
444
445 // Inverse scaling
446 Scalar raw_value = spec.scaler.unscale(scaled_value);
447
448 // Inverse transform
449 raw_value = spec.transform.applyInverse(raw_value);
450
451 if (spec.is_delta) {
452 if (spec.actual_name == "PRESSURE") {
453 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::pressure, getValue(fs.pressure(oilPhaseIdx)));
454 } else if (spec.actual_name == "SWAT") {
455 raw_value += getValue(fs.saturation(waterPhaseIdx));
456 } else if (spec.actual_name == "SOIL") {
457 raw_value += getValue(fs.saturation(oilPhaseIdx));
458 } else if (spec.actual_name == "SGAS") {
459 raw_value += getValue(fs.saturation(gasPhaseIdx));
460 } else if (spec.actual_name == "RS") {
461 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::gas_oil_ratio, getValue(fs.Rs()));
462 } else if (spec.actual_name == "RV") {
463 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::oil_gas_ratio, getValue(fs.Rv()));
464 } else {
465 OPM_THROW(std::runtime_error, "Unknown delta feature: " + name);
466 }
467 }
468
469 if (spec.actual_name == "PRESSURE") {
470 po_val = unitSyst.to_si(UnitSystem::measure::pressure, raw_value);
471 } else if (spec.actual_name == "SWAT") {
472 sw_val = raw_value;
473 } else if (spec.actual_name == "SOIL") {
474 so_val = raw_value;
475 } else if (spec.actual_name == "SGAS") {
476 sg_val = raw_value;
477 } else if (spec.actual_name == "RS") {
478 if constexpr (compositionSwitchEnabled) {
479 raw_value = unitSyst.to_si(UnitSystem::measure::gas_oil_ratio, raw_value);
480 fs.setRs(raw_value);
481 }
482 } else if (spec.actual_name == "RV") {
483 if constexpr (compositionSwitchEnabled) {
484 raw_value = unitSyst.to_si(UnitSystem::measure::oil_gas_ratio, raw_value);
485 fs.setRv(raw_value);
486 }
487 } else {
488 OPM_THROW(std::runtime_error, "Unknown output feature: " + name);
489 }
490
491 ++feature_idx;
492 }
493
494 int sat_count = static_cast<int>(flags.has_SWAT) +
495 static_cast<int>(flags.has_SOIL) +
496 static_cast<int>(flags.has_SGAS);
497
498 if (sat_count >= 2) {
499 Scalar sw = sw_val;
500 Scalar so = so_val;
501 Scalar sg = sg_val;
502
503 if (!flags.has_SWAT) {
504 sw = 1.0 - so - sg;
505 } else if (!flags.has_SOIL) {
506 so = 1.0 - sw - sg;
507 } else if (!flags.has_SGAS) {
508 sg = 1.0 - sw - so;
509 }
510
511 sw = max(0.0, min(sw, 1.0));
512 so = max(0.0, min(so, 1.0));
513 sg = max(0.0, min(sg, 1.0));
514
515 Scalar sum = sw + so + sg;
516 if (sum <= 1e-12) {
517 OPM_THROW(std::runtime_error, "Saturation sum is zero in cell " + std::to_string(cell_idx));
518 }
519
520 fs.setSaturation(waterPhaseIdx, sw / sum);
521 fs.setSaturation(oilPhaseIdx, so / sum);
522 fs.setSaturation(gasPhaseIdx, sg / sum);
523 }
524
525 if (flags.has_PRESSURE) {
526 std::array<Evaluation, numPhases> pC;
527 const auto& materialParams = simulator_.problem().materialLawParams(cell_idx);
528 MaterialLaw::capillaryPressures(pC, materialParams, fs);
529
530 for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
531 if (!FluidSystem::phaseIsActive(phaseIdx)) continue;
532 if (phaseIdx == oilPhaseIdx) {
533 fs.setPressure(phaseIdx, po_val);
534 } else {
535 fs.setPressure(phaseIdx, po_val - pC[phaseIdx]);
536 }
537 }
538 }
539
540 auto& primaryVars = simulator_.model().solution(/*timeIdx*/0)[cell_idx];
541 primaryVars.assignNaive(fs);
542 }
543
544 simulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx*/0);
545 }
546
548 {
549 bool has_SWAT = false;
550 bool has_SOIL = false;
551 bool has_SGAS = false;
552 bool has_PRESSURE = false;
553 };
554
555 FeatureFlags flagFeatures(const std::vector<std::pair<std::string, FeatureSpec>>& features)
556 {
557 FeatureFlags flags;
558
559 for (const auto& [name, spec] : features) {
560
561 if (spec.actual_name == "SWAT") {
562 flags.has_SWAT = true;
563 } else if (spec.actual_name == "SOIL") {
564 flags.has_SOIL = true;
565 } else if (spec.actual_name == "SGAS") {
566 flags.has_SGAS = true;
567 } else if (spec.actual_name == "PRESSURE") {
568 flags.has_PRESSURE = true;
569 }
570 }
571
572 return flags;
573 }
574
575protected:
577 std::vector<HybridNewtonConfig> configs_;
579};
580
581} // namespace Opm
582
583#endif // HYBRID_NEWTON_CLASS_HPP
Hybrid Newton solver extension for the black-oil model.
Definition: HybridNewton.hpp:60
bool configsLoaded_
Definition: HybridNewton.hpp:578
GetPropType< TypeTag, Properties::Indices > Indices
Definition: HybridNewton.hpp:66
void tryApplyHybridNewton()
Attempt to apply the Hybrid Newton correction at the current timestep.
Definition: HybridNewton.hpp:101
FeatureFlags flagFeatures(const std::vector< std::pair< std::string, FeatureSpec > > &features)
Definition: HybridNewton.hpp:555
GetPropType< TypeTag, Properties::Evaluation > Evaluation
Definition: HybridNewton.hpp:63
BlackOilHybridNewton(Simulator &simulator)
Definition: HybridNewton.hpp:79
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:577
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:189
static constexpr bool compositionSwitchEnabled
Definition: HybridNewton.hpp:75
GetPropType< TypeTag, Properties::MaterialLaw > MaterialLaw
Definition: HybridNewton.hpp:65
@ gasPhaseIdx
Definition: HybridNewton.hpp:71
@ numPhases
Definition: HybridNewton.hpp:69
GetPropType< TypeTag, Properties::Scalar > Scalar
Definition: HybridNewton.hpp:67
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:62
GetPropType< TypeTag, Properties::FluidSystem > FluidSystem
Definition: HybridNewton.hpp:64
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:419
void validateFluidSystem()
Definition: HybridNewton.hpp:152
@ waterPhaseIdx
Definition: HybridNewton.hpp:73
ML::Tensor< Evaluation > constructInputTensor(const HybridNewtonConfig &config)
Construct the input feature tensor for the Hybrid Newton model.
Definition: HybridNewton.hpp:235
Simulator & simulator_
Definition: HybridNewton.hpp:576
@ oilPhaseIdx
Definition: HybridNewton.hpp:72
Configuration for a Hybrid Newton ML model.
Definition: HybridNewtonConfig.hpp:136
std::vector< double > apply_times
Definition: HybridNewtonConfig.hpp:142
std::vector< std::pair< std::string, FeatureSpec > > input_features
Definition: HybridNewtonConfig.hpp:143
std::vector< std::pair< std::string, FeatureSpec > > output_features
Definition: HybridNewtonConfig.hpp:144
void validateConfig(bool compositionSwitchEnabled) const
Validate feature compatibility with simulator settings.
std::size_t n_cells
Definition: HybridNewtonConfig.hpp:141
std::vector< int > cell_indices
Definition: HybridNewtonConfig.hpp:140
std::string model_path
Definition: HybridNewtonConfig.hpp:138
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:45
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:548
bool has_SOIL
Definition: HybridNewton.hpp:550
bool has_SWAT
Definition: HybridNewton.hpp:549
bool has_SGAS
Definition: HybridNewton.hpp:551
bool has_PRESSURE
Definition: HybridNewton.hpp:552
Metadata for a single feature (input or output).
Definition: HybridNewtonConfig.hpp:120
Transform transform
Definition: HybridNewtonConfig.hpp:121
std::string actual_name
Definition: HybridNewtonConfig.hpp:124
Scaler scaler
Definition: HybridNewtonConfig.hpp:122
double scale(double raw_value) const
Definition: HybridNewtonConfig.hpp:46
double apply(double raw_value) const
Definition: HybridNewtonConfig.hpp:91