Skip to content

MICP

PetroScript models microbial induced calcite precipitation as a semantic physics package. Users describe permeability reduction, biological parameters, aqueous species diffusion, and optional initial state fields; lowerers own the Flow/ECL keyword names and row layout.

MICP couples microbes, oxygen, urea, biofilm, calcite, and permeability reduction inside the water phase. The public API keeps those concepts grouped as reservoir physics instead of asking authors to memorize table-card spellings.

MICP physics intent is a ps.micp.Package(...) in the model’s physics=:

import petroscript as ps
micp = ps.micp.Package(
permeability=ps.micp.PermeabilityFactor(
rows=[(0.0, 0.005), (0.4, 0.005), (0.6, 0.01), (0.9, 0.1), (1.0, 1.0)]
),
biology=ps.micp.Biology(rows=[
{
"biofilm_density": 35.0,
"death_rate": 0.0275,
"growth_rate": 3.6,
"half_velocity": 2.0e-5,
"yield_growth": 0.5,
"yield_oxygen": 0.5,
"attachment_rate": 0.0735,
"detachment_rate": 2.6e-13,
"detachment_exponent": 0.58,
"urea_saturation": 1391.0,
"oxygen_half_saturation": 21.3,
"calcite_density": 2710.0,
"yield_urea_to_calcite": 1.67,
}
]),
diffusion=ps.micp.Diffusion(microbes=2e-4, oxygen=2e-4, urea=1e-4),
initial_state=ps.micp.InitialState(
microbes=0.0,
oxygen=0.0,
urea=0.0,
biofilm=0.0,
calcite=0.0,
),
)
# ps.Model(..., physics=[micp])

ps.micp.PermeabilityFactor(rows=...) is a physical reduction curve with (porosity_factor, permeability_factor) points. It is not a public constructor for a PERMFACT card.

One curve is shared by every saturation-function region. PERMFACT is dimensioned by NTSFUN like BIOFPARA beside it, so a run with more than one region states the table once and then an empty body per further region — the spelling that says “the same as the region before”. Importing one reads the shared table back, and rendering states it that way again; a run with a single region states the table alone.

ps.micp.Biology(rows=...) states each row as a mapping of named biological fields. This is verbose by design: each name documents reservoir meaning and avoids positional keyword rows in user code.

A package states one biological-parameter row per saturation-function region, and the rows may differ. BIOFPARA is dimensioned by NTSFUN, so those rows are what a run’s saturation regions select; a single-phase MICP run may state no saturation function at all and still declare two of them, which is what opm-tests/micp/MICP.DATA does. Each region is written as its own terminated record, and importing one reads it back the same way.

ps.micp.InitialState(...) accepts scalar values or (nx, ny, nz) tensors for:

  • microbes;
  • oxygen;
  • urea;
  • biofilm;
  • calcite.

A dated ps.wells.InjectedStream names the injected concentrations; users should not author WMICP rows directly. Here each injection lasts two report steps, 0.001 and 0.049 days, so the next change is dated 0.05 days (01:12) later:

schedule = ps.Schedule(
report=[ps.Every(0.001, 0.049, 0.001, 0.049)],
events=[
ps.Event("2020-01-01", ps.wells.InjectedStream(
well="INJ1", microbe_concentration=0.01, oxygen_concentration=0.0,
urea_concentration=0.0)),
ps.Event("2020-01-01T01:12", ps.wells.InjectedStream(
well="INJ1", microbe_concentration=0.0, oxygen_concentration=0.04,
urea_concentration=0.0)),
ps.Event("2020-01-01T02:24", ps.wells.InjectedStream(
well="INJ1", microbe_concentration=0.0, oxygen_concentration=0.0,
urea_concentration=0.0)),
],
)

Every concentration at zero stops the injection. Model validation checks that MICP controls name wells the model declares and that a MICP physics package is attached.

MICP summary requests name each component’s quantity in ps.Summary’s scopes rather than raw summary-vector strings: microbes_in_place and microbes_injection_rate, for example.

Supported components are microbes, oxygen, urea, biofilm, and calcite. Rate/total requests are supported for the mobile components used by the target corpus. The compiler owns vector spellings such as FMMIP, CMMIR, and CMMIRL.

  • MICP requires the water phase to be active.
  • MICP controls require declared wells and non-negative concentrations.
  • Table values and diffusion coefficients must be finite and non-negative.
  • Initial state values may be scalars or full cell tensors matching the model grid shape.
  • If a target deck also needs molecular diffusion or dispersion, state ps.pvt.Diffusion on the fluid and dispersivity= on ps.Rock beside the ps.micp.Package(...); MICP does not create a generic transport framework.
  • Do not pass raw keyword strings such as "PERMFACT", "BIOFPARA", or "SMICR" to public constructors.
  • Use named fields when authoring biology rows; positional 13-tuples are accepted for compact fixtures but should not be the default user experience.
  • Initial state tensors are checked against grid dimensions at model validation time and report stable issue codes such as micp.initial_state.microbes.shape.
  • MICP controls naming a well the model does not declare report schedule.well.unknown; controls without a MICP package report micp.controls.package_missing.
  • Summary requests name a component’s quantity in ps.Summary’s scopes, as ps.Summary(field=["microbes_in_place"]), never the raw vector (FMMIP).

For the OPM Flow target path, current lowerers emit:

  • MICP in RUNSPEC when a MICP package is attached;
  • PERMFACT, BIOFPARA, and DIFFMICP in PROPS;
  • SMICR, SOXYG, SUREA, SBIOF, and SCALC in SOLUTION when initial state fields are supplied;
  • WMICP in SCHEDULE for MICP well controls;
  • MICP summary vectors in SUMMARY, including field, region, block, well, completion, and local-completion forms.

The MICP examples under tests/fixtures/examples/g9/micp.py and tests/fixtures/examples/g9/micp_ad_micp_mrst_comparison.py are backed by native fixture Flow evidence and generated PetroScript Flow evidence.

PetroScript is licensed under the Apache License 2.0. © 2026 Blauweiss EDV LLC. PetroScript by Blauweiss AI.For AI agents: llms.txt