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:
151 {
152 const auto& eclState = simulator_.vanguard().eclState();
153 const auto& phases = eclState.runspec().phases();
154
155 bool hasWater = phases.active(Phase::WATER);
156 bool hasGas = phases.active(Phase::GAS);
157 bool hasOil = phases.active(Phase::OIL);
158 bool hasSolvent = phases.active(Phase::SOLVENT);
159
160 // Check for three-phase black oil system
161 if (!(hasWater && hasOil && hasGas && !hasSolvent)) {
162 OPM_THROW(std::runtime_error,
163 "HybridNewton: Unsupported fluid system. Only three-phase black oil is supported.");
164 }
165 }
166
187 bool shouldApplyHybridNewton(Scalar current_time, const HybridNewtonConfig& config) const
188 {
189 if (config.apply_times.size() == 1) {
190 // Apply exactly at one point in time (with optional tolerance)
191 constexpr Scalar tolerance = 1e-6;
192 bool apply = std::abs(current_time - config.apply_times[0]) < tolerance;
193 return apply;
194 }
195
196 if (config.apply_times.size() == 2) {
197 Scalar start_time = config.apply_times[0];
198 Scalar end_time = config.apply_times[1];
199 bool apply = (current_time >= start_time) && (current_time <= end_time);
200 return apply;
201 }
202
203 // If apply_times has an unexpected number of entries
204 return false;
205 }
206
207
232 ML::Tensor<Evaluation>
234 {
235 const auto& features = config.input_features;
236
237 // compute total length directly
238 std::size_t input_tensor_length = 0;
239 for (const auto& feature : features) {
240 const FeatureSpec& spec = feature.second;
241 if (spec.actual_name == "TIMESTEP") {
242 input_tensor_length += 1;
243 } else {
244 input_tensor_length += config.n_cells;
245 }
246 }
247
248 ML::Tensor<Evaluation> input(input_tensor_length);
249 std::size_t offset = 0;
250
251 // fill in exact feature order from config
252 for (const auto& feature: features) {
253 const FeatureSpec& spec = feature.second;
254
255 if (spec.actual_name == "TIMESTEP") {
256 auto value = static_cast<Evaluation>(getScalarFeatureValue(spec));
257 input(offset++) = value;
258 } else {
259 for (std::size_t i = 0; i < config.n_cells; ++i) {
260 auto value = static_cast<Evaluation>(
261 getPerCellFeatureValue(spec, config.cell_indices[i])
262 );
263 input(offset + i) = value;
264 }
265 offset += config.n_cells;
266 }
267 }
268
269 return input;
270 }
271
287 {
288 Scalar value = 0.0;
289
290 if (spec.actual_name == "TIMESTEP") {
291 value = simulator_.timeStepSize();
292 } else {
293 OPM_THROW(std::runtime_error, "Unknown scalar feature: " + spec.actual_name);
294 }
295
296 value = spec.transform.apply(value);
297 return spec.scaler.scale(value);
298 }
299
317 Scalar getPerCellFeatureValue(const FeatureSpec& spec, int cell_index)
318 {
319 const auto& intQuants = simulator_.model().intensiveQuantities(cell_index, 0);
320 const auto& fs = intQuants.fluidState();
321 const auto& unitSyst = simulator_.vanguard().schedule().getUnits();
322
323 Scalar value = 0.0;
324
325 if (spec.actual_name == "PRESSURE") {
326 value = getValue(fs.pressure(oilPhaseIdx));
327 value = unitSyst.from_si(UnitSystem::measure::pressure, value);
328 } else if (spec.actual_name == "SWAT") {
329 value = getValue(fs.saturation(waterPhaseIdx));
330 } else if (spec.actual_name == "SGAS") {
331 value = getValue(fs.saturation(gasPhaseIdx));
332 } else if (spec.actual_name == "SOIL") {
333 value = getValue(fs.saturation(oilPhaseIdx));
334 } else if (spec.actual_name == "RS") {
335 value = getValue(fs.Rs());
336 value = unitSyst.from_si(UnitSystem::measure::gas_oil_ratio, value);
337 } else if (spec.actual_name == "RV") {
338 value = getValue(fs.Rv());
339 value = unitSyst.from_si(UnitSystem::measure::oil_gas_ratio, value);
340 } else if (spec.actual_name == "PERMX") {
341 const auto& eclState = simulator_.vanguard().eclState();
342 const auto& fp = eclState.fieldProps();
343 auto permX = fp.get_double("PERMX");
344 value = permX[cell_index];
345 value = unitSyst.from_si(UnitSystem::measure::permeability, value);
346 } else {
347 OPM_THROW(std::runtime_error, "Unknown per-cell feature: " + spec.actual_name);
348 }
349
350 Scalar transformed = spec.transform.apply(value);
351 Scalar scaled = spec.scaler.scale(transformed);
352 return scaled;
353 }
354
355
374 ML::Tensor<Evaluation>
375 constructOutputTensor(const ML::Tensor<Evaluation>& input,
376 const HybridNewtonConfig& config)
377 {
378 const auto& features = config.output_features;
379 const int n_features = features.size();
380
381 ML::NNModel<Evaluation> model;
382 model.loadModel(config.model_path);
383
384 ML::Tensor<Evaluation> output(1, config.n_cells * n_features);
385 model.apply(input, output);
386
387 return output;
388 }
389
417 void updateInitialGuess(ML::Tensor<Evaluation>& output,
418 const HybridNewtonConfig& config)
419 {
420 const auto& features = config.output_features;
421
422 FeatureFlags flags = flagFeatures(features);
423
424 const auto& unitSyst = simulator_.vanguard().schedule().getUnits();
425
426 for (std::size_t i = 0; i < config.n_cells; ++i) {
427 const int cell_idx = config.cell_indices[i];
428 const auto& intQuants = simulator_.model().intensiveQuantities(cell_idx, /*timeIdx*/0);
429 auto fs = intQuants.fluidState();
430
431 int feature_idx = 0;
432
433 // Temp variables per cell
434 Scalar sw_val = -1.0;
435 Scalar so_val = -1.0;
436 Scalar sg_val = -1.0;
437 Scalar po_val = -1.0;
438
439 for (const auto& [name, spec] : features) {
440
441 auto scaled_value = getValue(output(feature_idx * config.n_cells + i));
442
443 // Inverse scaling
444 Scalar raw_value = spec.scaler.unscale(scaled_value);
445
446 // Inverse transform
447 raw_value = spec.transform.applyInverse(raw_value);
448
449 if (spec.is_delta) {
450 if (spec.actual_name == "PRESSURE") {
451 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::pressure, getValue(fs.pressure(oilPhaseIdx)));
452 } else if (spec.actual_name == "SWAT") {
453 raw_value += getValue(fs.saturation(waterPhaseIdx));
454 } else if (spec.actual_name == "SOIL") {
455 raw_value += getValue(fs.saturation(oilPhaseIdx));
456 } else if (spec.actual_name == "SGAS") {
457 raw_value += getValue(fs.saturation(gasPhaseIdx));
458 } else if (spec.actual_name == "RS") {
459 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::gas_oil_ratio, getValue(fs.Rs()));
460 } else if (spec.actual_name == "RV") {
461 raw_value = raw_value + unitSyst.from_si(UnitSystem::measure::oil_gas_ratio, getValue(fs.Rv()));
462 } else {
463 OPM_THROW(std::runtime_error, "Unknown delta feature: " + name);
464 }
465 }
466
467 if (spec.actual_name == "PRESSURE") {
468 po_val = unitSyst.to_si(UnitSystem::measure::pressure, raw_value);
469 } else if (spec.actual_name == "SWAT") {
470 sw_val = raw_value;
471 } else if (spec.actual_name == "SOIL") {
472 so_val = raw_value;
473 } else if (spec.actual_name == "SGAS") {
474 sg_val = raw_value;
475 } else if (spec.actual_name == "RS") {
476 if constexpr (compositionSwitchEnabled) {
477 raw_value = unitSyst.to_si(UnitSystem::measure::gas_oil_ratio, raw_value);
478 fs.setRs(raw_value);
479 }
480 } else if (spec.actual_name == "RV") {
481 if constexpr (compositionSwitchEnabled) {
482 raw_value = unitSyst.to_si(UnitSystem::measure::oil_gas_ratio, raw_value);
483 fs.setRv(raw_value);
484 }
485 } else {
486 OPM_THROW(std::runtime_error, "Unknown output feature: " + name);
487 }
488
489 ++feature_idx;
490 }
491
492 int sat_count = static_cast<int>(flags.has_SWAT) +
493 static_cast<int>(flags.has_SOIL) +
494 static_cast<int>(flags.has_SGAS);
495
496 if (sat_count >= 2) {
497 Scalar sw = sw_val;
498 Scalar so = so_val;
499 Scalar sg = sg_val;
500
501 if (!flags.has_SWAT) {
502 sw = 1.0 - so - sg;
503 } else if (!flags.has_SOIL) {
504 so = 1.0 - sw - sg;
505 } else if (!flags.has_SGAS) {
506 sg = 1.0 - sw - so;
507 }
508
509 sw = max(0.0, min(sw, 1.0));
510 so = max(0.0, min(so, 1.0));
511 sg = max(0.0, min(sg, 1.0));
512
513 Scalar sum = sw + so + sg;
514 if (sum <= 1e-12) {
515 OPM_THROW(std::runtime_error, "Saturation sum is zero in cell " + std::to_string(cell_idx));
516 }
517
518 fs.setSaturation(waterPhaseIdx, sw / sum);
519 fs.setSaturation(oilPhaseIdx, so / sum);
520 fs.setSaturation(gasPhaseIdx, sg / sum);
521 }
522
523 if (flags.has_PRESSURE) {
524 std::array<Evaluation, numPhases> pC;
525 const auto& materialParams = simulator_.problem().materialLawParams(cell_idx);
526 MaterialLaw::capillaryPressures(pC, materialParams, fs);
527
528 for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
529 if (!FluidSystem::phaseIsActive(phaseIdx)) continue;
530 if (phaseIdx == oilPhaseIdx) {
531 fs.setPressure(phaseIdx, po_val);
532 } else {
533 fs.setPressure(phaseIdx, po_val - pC[phaseIdx]);
534 }
535 }
536 }
537
538 auto& primaryVars = simulator_.model().solution(/*timeIdx*/0)[cell_idx];
539 primaryVars.assignNaive(fs);
540 }
541
542 simulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx*/0);
543 }
544
546 {
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
573protected:
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
@ oilPhaseIdx
Definition: HybridNewton.hpp:71
void tryApplyHybridNewton()
Attempt to apply the Hybrid Newton correction at the current timestep.
Definition: HybridNewton.hpp:99
@ numPhases
Definition: HybridNewton.hpp:68
FeatureFlags flagFeatures(const std::vector< std::pair< std::string, FeatureSpec > > &features)
Definition: HybridNewton.hpp:553
@ gasPhaseIdx
Definition: HybridNewton.hpp:70
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:317
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:187
static constexpr bool compositionSwitchEnabled
Definition: HybridNewton.hpp:74
GetPropType< TypeTag, Properties::MaterialLaw > MaterialLaw
Definition: HybridNewton.hpp:64
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:375
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:286
void updateInitialGuess(ML::Tensor< Evaluation > &output, const HybridNewtonConfig &config)
Update the nonlinear solver's initial guess using ML predictions.
Definition: HybridNewton.hpp:417
void validateFluidSystem()
Definition: HybridNewton.hpp:150
@ waterPhaseIdx
Definition: HybridNewton.hpp:72
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: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: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: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