@@ -35,46 +35,71 @@ Running in parallel needs the following in addition:
3535An example script for a parallel run
3636------------------------------------
3737
38- .. code-block :: python
38+ The example builds the simulator from objects parsed in Python, instead of from
39+ the file name, so that the script can change well controls during the run
40+ through the ``Schedule `` object it passes in, for example with
41+ ``schedule.shut_well("PROD", step) ``. With the filename constructor the
42+ simulator builds its own ``Schedule ``, which the script cannot reach. Passing
43+ ``None `` for the ``EclipseState `` is what makes this work in parallel (see
44+ "Constructing the simulator" below). This is a workaround until the simulator
45+ offers a supported way to reach its own ``Schedule ``. In a parallel run, make
46+ every such change on every rank: each rank holds its own ``Schedule ``, and a
47+ change made only under ``if RANK == 0: `` is silently ignored.
3948
40- from opm.simulators import BlackOilSimulator
49+ .. code-block :: python
4150
42- # mpi4py owns MPI_Init/MPI_Finalize; importing it initializes MPI for the
43- # whole process, including the simulator underneath.
44- from mpi4py import MPI
51+ # Importing mpi4py initializes MPI for the whole process,
52+ # including the simulator underneath.
53+ from mpi4py import MPI
54+ from opm.simulators import BlackOilSimulator
55+ from opm.io.parser import Parser
56+ from opm.io.ecl_state import EclipseState
57+ from opm.io.schedule import Schedule
58+ from opm.io.summary import SummaryConfig
4559
46- COMM = MPI .COMM_WORLD
47- RANK = COMM .Get_rank()
60+ CASE = " SPE1CASE1.DATA"
4861
49- CASE = " SPE1CASE1.DATA"
62+ COMM = MPI .COMM_WORLD
63+ RANK = COMM .Get_rank()
5064
5165
52- def main ():
53- sim = BlackOilSimulator(filename = CASE )
66+ def main ():
67+ deck = Parser().parse(CASE )
68+ state = EclipseState(deck) # needed to build the Schedule only
69+ schedule = Schedule(deck, state)
70+ summary_config = SummaryConfig(deck, state, schedule)
5471
55- # init=False: MPI is already initialized by mpi4py.
56- # finalize=False: keep MPI alive until the script exits.
57- sim.setup_mpi(init = False , finalize = False )
72+ # Compared with the serial example in "Run OPM Flow from Python", the only
73+ # change is None in place of `state`: in parallel the simulator must build
74+ # the EclipseState itself (see "Constructing the simulator" below).
75+ sim = BlackOilSimulator(deck, None , schedule, summary_config)
76+ # init=False: MPI is already initialized by mpi4py.
77+ # finalize=False: keep MPI alive until the script exits.
78+ sim.setup_mpi(init = False , finalize = False )
5879
59- sim.step_init()
80+ sim.step_init()
6081
61- sim.step()
82+ # Change well controls through the Schedule passed to the constructor.
83+ # Every rank must make the same call: a change made only under
84+ # `if RANK == 0:` is silently ignored.
85+ schedule.shut_well(" PROD" , 3 )
6286
63- # The grid is distributed, so each rank sees only its own cells
64- # (owned + overlap).
65- poro = sim.get_porosity()
66- sim.set_porosity(poro * 0.95 )
87+ # The grid is distributed, so each rank sees only its own cells
88+ # (owned + overlap).
89+ poro = sim.get_porosity()
90+ sim.set_porosity(poro * 0.95 )
6791
68- sim.step()
92+ while not sim.check_simulation_finished():
93+ sim.step()
6994
70- sim.step_cleanup()
95+ sim.step_cleanup()
7196
72- if RANK == 0 :
73- print (" done -- results written to SPE1CASE1.PRT" , flush = True )
97+ if RANK == 0 :
98+ print (" done -- results written to SPE1CASE1.PRT" , flush = True )
7499
75100
76- if __name__ == " __main__" :
77- main()
101+ if __name__ == " __main__" :
102+ main()
78103
79104
80105 Run it with:
@@ -89,6 +114,29 @@ and confirm the rank count in the print file:
89114
90115 grep " Number of MPI processes" SPE1CASE1.PRT
91116
117+ To check that the well was shut, read the oil rate of ``PROD `` back from the
118+ summary file once the run has finished:
119+
120+ .. code-block :: python
121+
122+ from opm.io.ecl import ESmry
123+
124+ smry = ESmry(" SPE1CASE1.SMSPEC" )
125+ print (" TIME (days):" , [round (float (t), 1 ) for t in smry[" TIME" ]])
126+ print (" WOPR:PROD: " , [round (v) for v in smry[" WOPR:PROD" ]])
127+
128+ The output depends on the deck. With the ten-year ``SPE1CASE1.DATA `` from
129+ opm-tests, the lists are long, and the rate is 20000 until day 90 and 0 from
130+ then on. With the ten-day version used by the opm-simulators Python tests
131+ (`python/test_data/SPE1CASE1a
132+ <https://github.com/OPM/opm-simulators/tree/master/python/test_data/SPE1CASE1a> `_),
133+ the rate is 20000 until day 3 and 0 from then on:
134+
135+ .. code-block :: text
136+
137+ TIME (days): [1.0, 1.6, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
138+ WOPR:PROD: [20000, 20000, 20000, 20000, 0, 0, 0, 0, 0, 0, 0]
139+
92140
93141 Good to know
94142------------
@@ -124,13 +172,17 @@ Constructing the simulator
124172
125173.. warning ::
126174
127- In parallel, only the **filename constructor ** works:
175+ In parallel, the ``EclipseState `` argument of the four-argument
176+ constructor must be ``None ``:
128177
129178 .. code-block :: python
130179
131- sim = BlackOilSimulator(filename = " SPE1CASE1.DATA " )
180+ sim = BlackOilSimulator(deck, None , schedule, summary_config )
132181
133- The four-argument form documented for serial runs —
134- ``BlackOilSimulator(deck, state, schedule, summary_config) `` — cannot run on
135- more than one rank. It aborts with
182+ Passing an ``EclipseState `` object fails on more than one rank with
136183 ``Parallel simulator setup is incorrect as it does not use ParallelEclipseState ``.
184+ With ``None ``, the simulator builds the parallel state itself from the
185+ DATA file. The filename constructor, ``BlackOilSimulator(filename=...) ``,
186+ also works in parallel, but then the script has no access to the
187+ ``Schedule `` the simulator uses, so well controls cannot be changed from
188+ Python.
0 commit comments