Hi,
Apologies for the delay in response – July is usually holiday here in Norway.
The approach you have set up seems correct (i.e. inherit the class and make sure that the dimensions get correct). You will double evaluate some things, but that is not so important for getting things to work. The main issue you are running into is that you need to get ADI types out from solverGeochem_CW2. The ADI types wrap both the values (which are probably already what you want) and the derivatives, which need to be implemented. This can get fairly complicated, but you can see examples of how this is done in the ADI class (for primitive operations like multiplication and addition) and EquationOfStateModel in the compositional model (for some manual tricks for implicit differentiation). The free second MRST textbook also goes into some detail on this (how evaluations are structured, and how to extended them), as well as later parts of the first textbook (for the AD concept in general).
A first question may be if you have the option to generate derivatives in your solverGeochem (i.e. shrinkage factors or densities with respect to pressure, and any other values that are used). If so, the problem is then how to most easily get the values into the right AD format.
Best regards,
Olav
--
You received this message because you are subscribed to the Google Groups "MRST-users: The Matlab Reservoir Simulation Toolbox User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to
sintef-mrst...@googlegroups.com.
To view this discussion visit
https://groups.google.com/d/msgid/sintef-mrst/85551f9e-efac-4a80-a6a1-abac4120c2b6n%40googlegroups.com.
Hi Olav,
Thank you for the helpful suggestions; never mind the delay.
I attempted to generate derivatives for the new PVT values in solverGeochem, as you suggested. In the function (assigngGeochemPVT), I generate derivatives only as a function of the ADI phase pressure (compute slopes and multiply with Jacobian: you may inspect that in the helper function makeADfromValSlope).
As usual, that’s succeeded by solving for the masses in the discretization function:
[state = assigngGeochemPVT(state, model);
mass = model.getProps(state, 'ComponentTotalMass');
mass0 = model.getProps(state0, 'ComponentTotalMass');
flowState = fd.buildFlowState(model, state, state0, dt);].
Now, again, I am running into the error:
“Warning:
Matrix is close to singular or badly scaled. Results may be inaccurate. RCOND
= 8.066229e-31.
> In BackslashSolverAD/solveLinearSystem (line 21)
In LinearSolverAD/solveLinearProblem (line 361)
In PhysicalModel/stepFunction (line 739)
In ReservoirModel/stepFunction (line 307)
In NonLinearSolver/solveMinistep (line 374)
In NonLinearSolver/solveTimestep (line 210)
In simulateScheduleAD
(line 295)
In seq5_oneQgrid (line 281)”.
Thus, besides these fixes, I also tried inserting just one PVT parameter, such as density, and I still ran into long iterations. So, I really can’t tell if the issue is with the new PVT structure or values. I will note that not assigning any of the new PVTs, as shown in the screenshot, it just runs. Additionally, examining the derivatives, I noticed a difference in my oleic phase shrinkage factor with the normal, suggesting an inconsistency/inaccuracy in my derivative approach (Jacobians).
I would appreciate any comments on the assignment of parameters (generation of derivatives) in the function below or otherwise.
Thank you.
Best regards,
Angelo
----------------------------
function state = assigngGeochemPVT(state, model)
% Build ADI PVT properties from geochem arrays in state.*_x
% Uses phase pressures pO/pG when available; falls back to total pressure.
%% 0) Phase pressures (ADI) and numeric copies
p = []; pO = []; pG = [];
PO = []; PG = [];
if nargin >= 2 && ~isempty(model)
% Try to get per-phase pressures
try
pp = model.getProps(state, 'PhasePressures'); % 1xNph cell of ADI
catch
pp = [];
end
if ~isempty(pp)
% Phase indices (brine/"O" and gas/"G")
try
iO = model.getPhaseIndex('O');
iG = model.getPhaseIndex('G');
catch
% Fallback: assume [O,G] ordering
iO = 1; iG = 2;
end
pO = pp{iO}; PO = value(pO);
pG = pp{iG}; PG = value(pG);
else
% Fallback: single pressure
p = model.getProp(state, 'pressure');
PO = value(p); pO = p; % use same for both
PG = PO; pG = p;
end
else
% No model passed in: use state.pressure
p = state.pressure;
PO = value(p); pO = p;
PG = PO; pG = p;
end
N = numel(PO);
%% 1) Pack geochem vectors (cellwise doubles)
names = {'bO_array','bG_array', ...
'muO_array','muG_array', ...
'rhoO_array','rhoG_array', ...
'rs_array'};
vals = {state.bO_x(:), state.bG_x(:), ...
state.muO_x(:), state.muG_x(:), ...
state.rhoO_x(:), state.rhoG_x(:), ...
state.rs_x(:)};
pvt_val = cell2struct(vals, names, 2);
% Basic size sanity
for k = 1:numel(names)
assert(numel(pvt_val.(names{k})) == N, ...
'assigngGeochemPVT: %s must be length %d (got %d).', ...
names{k}, N, numel(pvt_val.(names{k})));
end
%% 2) Slopes w.r.t. the matching phase pressure
% Brine/"O" props use pO; Gas/"G" props use pG; Rs uses pO.
d_bO_dPO = dvaldP(pvt_val.bO_array , PO);
d_bG_dPG = dvaldP(pvt_val.bG_array , PG);
d_muO_dPO = dvaldP(pvt_val.muO_array , PO);
d_muG_dPG = dvaldP(pvt_val.muG_array , PG);
d_rhoO_dPO = dvaldP(pvt_val.rhoO_array, PO);
d_rhoG_dPG = dvaldP(pvt_val.rhoG_array, PG);
d_rs_dPO = dvaldP(pvt_val.rs_array , PO);
%% 3) Build ADI variables using the *same* phase pressure blocks
bO = makeADfromValSlope(pO, pvt_val.bO_array , d_bO_dPO);
bG = makeADfromValSlope(pG, pvt_val.bG_array , d_bG_dPG);
muO = makeADfromValSlope(pO, pvt_val.muO_array , d_muO_dPO);
muG = makeADfromValSlope(pG, pvt_val.muG_array , d_muG_dPG);
rhoO = makeADfromValSlope(pO, pvt_val.rhoO_array, d_rhoO_dPO);
rhoG = makeADfromValSlope(pG, pvt_val.rhoG_array, d_rhoG_dPG);
rs = makeADfromValSlope(pO, pvt_val.rs_array , d_rs_dPO); %#ok<NASGU>
% ^ keep as local unless you explicitly want to override state.rs (often
% rs also depends on other primaries, so replacing it can change Jacobian
% block structure). We persist the double copy below as before.
%% 4) Store in state (mirror MRST PVTProps layout you actually use)
% state.PVTProps.ShrinkageFactors = {bO, bG};
% state.PVTProps.Viscosity = {muO, muG};
state.PVTProps.Density = {rhoO, rhoG};
% state.rs = rs; % ADI rs for downstream use
% Keep doubles as well if you want downstream diagnostics
state.rs_x = pvt_val.rs_array;
end
% ===== helpers ============================================================
function d = dvaldP(val, P)
% Safe ∂val/∂P for possibly non-uniform / constant P
if numel(unique(P)) <= 1
d = zeros(numel(val),1);
else
d = gradient(val(:), P(:));
end
d = sanitizeSlope(d);
end