-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimulation_master.py
executable file
·225 lines (178 loc) · 7.05 KB
/
simulation_master.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
"""
This is the master file for all simulations related to RN-34. The module contains to
parts:
1) The class Simulator, which gives the high-level setup of the different stages in
the simulation.
2) The main run script (see bottom of the file), which contains instructions to the
Simulator object.
The detailed simulator setup is given in the module models.
A note on terminology: The code, and the documentation, is inconsistent on the usage of
'fault' and 'fracture'; the former is the geologically meaningful term, while the latter
is standard terminology in simulation development. Please bear with us.
"""
import numpy as np
import porepy as pp
import logging
import pickle
import os
from models import FlowModel, BiotMechanicsModel
logger = logging.getLogger(__name__)
class Simulator:
def __init__(self, params=None):
if params is None:
params = {}
# Different grid realizations with increasing resolution in the vertical
# direction. The 2d mesh is the same for all cases. In the vertical direction,
# the grid is refined around the depths where injection takes place.
# Case 1 is used for the simulations reported in the paper, however, grids with
# different resolution were used for experimentation runs.
case = 1
if case == 0:
self.z_coord = np.array([0, -100, -1500, -2200, -3000, -4000])
elif case == 1:
self.z_coord = np.array(
[0, -1000, -1500, -1800, -2100, -2400, -2700, -3000, -3500, -4000]
)
elif case == 2:
self.z_coord = np.array(
[
0,
-500,
-1000,
-1500,
-1900,
-2200,
-2400,
-2500,
-2600,
-2700,
-3000,
-3500,
-4000,
]
)
elif case == 3:
self.z_coord = np.array(
[
0,
-1000,
-1700,
-2100,
-2200,
-2300,
-2350,
-2400,
-2450,
-2500,
-2550,
-2600,
-2650,
-2700,
-2800,
-3000,
-3300,
-4000,
]
)
# Fractures to include in the simulation.
self.included_fractures = [
"Fault_1",
"Fault_2",
"Fault_3a",
"Fault_3b",
"Fault_4",
"Fault_8",
]
self.num_fracs = len(self.included_fractures)
def simulate_leak_off_test(self, params=None):
""" Tune the fracture and matrix permeability, as well as matrix porosity
using data from the
"""
print("\n\n")
print("-------- Start permeability calibration -------------")
print("\n\n")
if params is None:
params = {}
logger.setLevel(logging.CRITICAL)
# Create a flow
solver = FlowModel(self._standard_parameters())
target = solver.calibration_run(
return_values=True, do_plot=False, do_export=False
)
print("Found the target value to be:", target)
logger.setLevel(logging.INFO)
def _standard_parameters(self):
# utility function.
return {
"z_coordinates": self.z_coord,
"fracture_files": self.included_fractures,
}
def initial_state_poro_mech(self):
print("\n\n")
print("-------- Poro-mechanical initialization -------------")
print("\n\n")
model_params = self._standard_parameters()
# model_params["initial_mechanics_state"] = self.initial_mechanics_state
# The injection lasts in total 6 hours. Also do half an hour of relaxation after this
model_params["end_time"] = 1000 * pp.YEAR
model_params["time_step"] = model_params["end_time"] / 5
model_params["num_loadsteps"] = 1
model_params["export_folder"] = "model_initialization"
model_params["export_file_name"] = "model_init"
poro_model = BiotMechanicsModel(model_params)
pp.run_time_dependent_model(
poro_model, {"max_iterations": 500, "nl_convergence_tol": 1e-14}
)
poro_model.store_contact_state("reference")
poro_model.store_contact_state("previous")
logger.info(f"\n\n Model initialization completed\n\n")
self.model = poro_model
def simulate_march_29(self, params=None):
if params is None:
params = {}
poro_model = self.model
# Switch on the sources
poro_model.activate_sources()
time_step = 15 * pp.MINUTE
poro_model.time = 0
poro_model.set_time_step(time_step)
poro_model.init_time_step = time_step
poro_model.end_time = 10.5 * pp.HOUR
export_folder = "march_29"
poro_model.export_folder = export_folder
poro_model.export_file_name = "stimulation"
poro_model.prev_export_time = 0
poro_model.set_export()
if not os.path.exists(export_folder):
os.mkdir(export_folder)
pickle.dump(poro_model.gb, open(export_folder + "/grid_bucket.grid", "wb"))
pp.run_time_dependent_model(
poro_model,
{
"prepare_simulation": False,
"max_iterations": 500,
"nl_convergence_tol": 1e-14,
},
)
if __name__ == "__main__":
# Set the random seed. This ensures that the local coordinate system in the
# fractures are always the same, which has been useful for debugging.
# This is not really a necessary step, but has been kept for legacy / convenience
# reasons.
np.random.seed(0)
# Initialize simulation object
sim = Simulator()
# Simulate the leak-off test on the morning of 29 March 2015. This is mainly done
# to check that the difference in pressure (measured at the injection cell, which
# is presumed to be proportional to the observed pressure at the measurement point
# at 1400m depth) between the steady state and the plateau reached during injection
# is consistent. On a more detailed level, the simulated and measured pressure are
# not in agreement - for this, a more elaborate parameter calibration would have
# been needed, but this was deemed not warrented given the scarcity of data.
sim.simulate_leak_off_test()
# Initialize the poro-mechanical simulation. This simulates the poro-mechanical
# system to steady state, using zero injection rate, but with the remaining
# parameters set to the same values as used for the stimulation.
sim.initial_state_poro_mech()
# Simulate the stimulation event on 29 March 2015
sim.simulate_march_29()