examples.logo

A logo: the saturation field of a reservoir shaped like a smiley.

No physics is illustrated here that the other examples do not; the goal is a picture. But it does show how little a shape costs on the rectangular grid: an outline is a boolean expression in the mesh coordinates, assigned to minires.ResSim.active (ref examples.inactive_cells), holes included, and the wells and the aquifer are records like any other's.

The face is a disc, with the smile cut out of it (inactive). The eyes are the injectors -- one well, two completions -- whose water pools around them, and the nose the producer, drawing it down. Along the bottom lies an aquifer (ref examples.aquifer), whose water rises around the smile to meet the producer -- which is set to take more than is injected, the aquifer supplying the rest. Set aquifer = False to remove it: the rates are then balanced, so the picture can be compared with and without. The aquifer's contact is stroked in blue (minires.plotting.Plot2D.plt_faces).

The figure is the plain plt_field (oil saturation: water in teal, oil in coral), stripped of axes, colorbar, and labels; the well markers remain (the eyes and nose are the markers) -- each coloured individually, by name (and by completion, for the two eyes), with the little centre dot turned off (wells=dict(color=..., dot=False), ref minires.plotting.Plot2D.well_scatter). The stroke of the aquifer contact runs along the boundary faces of the contact cells, so it has no width but its lw, and the contours stop half a cell short of it; cellwise=True would instead paint the cells flat, up to those faces, with a pixelated outline.

Logo
Logo
 1"""A logo: the saturation field of a reservoir shaped like a smiley.
 2
 3No physics is illustrated here that the other examples do not; the goal is a
 4picture. But it does show how little a shape costs on the rectangular grid:
 5an outline is a boolean expression in the mesh coordinates, assigned to
 6`minires.ResSim.active` (ref `examples.inactive_cells`), holes included,
 7and the wells and the aquifer are records like any other's.
 8
 9The face is a disc, with the smile *cut out* of it (inactive). The eyes are
10the injectors -- one well, two completions -- whose water pools around them,
11and the nose the producer, drawing it down. Along the bottom lies an
12**aquifer** (ref `examples.aquifer`), whose water rises *around* the smile to
13meet the producer -- which is set to take more than is injected, the aquifer
14supplying the rest. Set `aquifer = False` to remove it: the rates are then
15balanced, so the picture can be compared with and without. The aquifer's
16contact is stroked in blue (`minires.plotting.Plot2D.plt_faces`).
17
18The figure is the plain `plt_field` (oil saturation: water in teal, oil in
19coral), stripped of axes, colorbar, and labels; the well markers remain (the
20eyes and nose are the markers) -- each coloured individually, by name
21(and by completion, for the two eyes), with the little centre dot turned off
22(`wells=dict(color=..., dot=False)`, ref `minires.plotting.Plot2D.well_scatter`). The stroke of the aquifer contact runs
23along the boundary *faces* of the contact cells, so it has no width but its `lw`,
24and the contours stop half a cell short of it; `cellwise=True` would instead paint
25the cells flat, up to those faces, with a pixelated outline.
26"""
27
28from typing import Any
29
30import matplotlib.pyplot as plt
31import numpy as np
32from minires import ResSim
33from minires.plotting import show
34from minires.wells import boundary_faces
35
36aquifer = True  # toggle: water beyond the bottom boundary, at pressure 1
37p_aq = 1.
38q_aq = .5 if aquifer else 0  # the deficit of injection, which the aquifer covers
39y_aq = .18  # the aquifer lies below this
40
41## The outline: a 32² unit square cut to a disc, less the smile
42model = ResSim(Lx=1, Ly=1, Nx=32, Ny=32)
43X, Y = model.mesh
44face = (X - .5)**2 + (Y - .5)**2 <= .46**2
45r, angle = np.hypot(X - .5, Y - .52), np.arctan2(Y - .52, X - .5)  # polar about the nose
46smile = (abs(r - .27) <= .03) & (-.75 * np.pi < angle) & (angle < -.25 * np.pi)
47model.active = face & ~smile
48
49## The wells (`Any`, so that the type checker sees `model.wells` as the `Wells` that
50## `__setattr__` makes of these records, rather than as the records)
51wells: Any = [dict(name="Nose", xy=[.5, .5], rate=-(1 + q_aq)),
52              dict(name="Eyes", xy=[[.25, .62], [.75, .62]], rate=+1)]  # 1 well, 2 compl.
53if aquifer:
54    # The contact: the boundary cells (those with a face to an inactive cell
55    # or off-grid) of the lower part of the outline.
56    xy = np.column_stack([X[model.active], Y[model.active]])
57    contact = xy[boundary_faces(model, xy).any(-1) & (xy[:, 1] < y_aq)]
58    wells = wells + [dict(name="Aq", xy=contact, aquifer=True, bhp=p_aq)]
59model.wells = wells
60
61S0 = np.zeros(model.Nxy)
62SS, PP = model.sim(.004, 55, S0, pbar=False)
63
64# The aquifer supplies exactly the deficit (incompressible), and the inactive cells stay inert.
65assert np.allclose(model.wells.rates_by_well.sum(0), 0)
66if aquifer:
67    assert np.allclose(model.wells.rates_by_well[-1], q_aq)
68assert (SS[:, ~model.active.ravel()] == 0).all()
69
70## Plot
71# A colour per marker: keyed by well name, and, the eyes being one well of two
72# completions, a colour per *completion* for them (left eye first, ref
73# `minires.wells.Wells.xy`).
74fig, ax = plt.subplots(num="Logo", clear=True, figsize=(5, 5))
75model.plt_field(ax, SS[-1], "oil", colorbar=False, labels=False, finalize=False, title="",
76                wells=dict(exclude=["Aq"], size=1.5, text=False, dot=False))
77if "Aq" in model.wells.names:
78    contact = model.wells.xy[model.wells.group == model.wells.nWell - 1]
79    model.plt_faces(ax, contact, lw=12)
80ax.axis("off")
81fig.tight_layout()
82
83# Regression values, checked by `tests/test_examples.py`.
84__digest__ = dict(smiley = SS[-1][model.active.ravel()])
85
86if __name__ == "__main__":
87    show()