pygcd 0.3__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygcd/__init__.py +21 -0
- pygcd/__version__.py +16 -0
- pygcd/dataset.py +203 -0
- pygcd/drivers/__init__.py +12 -0
- pygcd/drivers/_cells.py +43 -0
- pygcd/drivers/_utils.py +9 -0
- pygcd/drivers/geopandas_driver.py +148 -0
- pygcd/drivers/lasio_driver.py +74 -0
- pygcd/drivers/pyvista_driver.py +143 -0
- pygcd/objects/__init__.py +6 -0
- pygcd/objects/abstract.py +154 -0
- pygcd/objects/grid.py +29 -0
- pygcd/objects/mesh.py +55 -0
- pygcd/objects/well.py +84 -0
- pygcd/readers/__init__.py +31 -0
- pygcd/readers/_utils.py +40 -0
- pygcd/readers/grid.py +2 -0
- pygcd/readers/header.py +80 -0
- pygcd/readers/mesh.py +101 -0
- pygcd/readers/well.py +299 -0
- pygcd-0.3.dist-info/LICENSE +661 -0
- pygcd-0.3.dist-info/METADATA +858 -0
- pygcd-0.3.dist-info/RECORD +25 -0
- pygcd-0.3.dist-info/WHEEL +6 -0
- pygcd-0.3.dist-info/top_level.txt +1 -0
pygcd/readers/well.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from functools import cached_property
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, List
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
DT = np.dtype(">f4")
|
|
9
|
+
|
|
10
|
+
from ._utils import safesplit
|
|
11
|
+
|
|
12
|
+
"""The Well ASCII format contains 4 sections:
|
|
13
|
+
(in addition to the sections relative to an Object definition)
|
|
14
|
+
- a header section
|
|
15
|
+
- a wellpath section
|
|
16
|
+
- a well curves (log) section
|
|
17
|
+
- a zone/marker section.
|
|
18
|
+
Curves data can be externalized in ASCII/BINARY_DATA_FILE (big-endian float 32).
|
|
19
|
+
Well path bounded by 2 endpoints can also be externalized in WP_CATALOG_FILE (useless in our case ?)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
########################
|
|
23
|
+
# WELL DATA STRUCTURES #
|
|
24
|
+
########################
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class WellMarker:
|
|
29
|
+
name: str # marker (non unique) identifier
|
|
30
|
+
zm: float # depth along the path (from collar)
|
|
31
|
+
unit: str = ""
|
|
32
|
+
feature: str = ""
|
|
33
|
+
horizon: str = ""
|
|
34
|
+
dirdip: tuple[float, float] = () # (azimuth, dip) all in °
|
|
35
|
+
norm: tuple[float, float, float] = () # (x,y,z)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class WellZone:
|
|
40
|
+
name: str # marker (non unique) identifier
|
|
41
|
+
zfrom: float # depth along the path (from collar)
|
|
42
|
+
zto: float # depth along the path (from collar)
|
|
43
|
+
unit: str = ""
|
|
44
|
+
feature: str = ""
|
|
45
|
+
horizon: str = ""
|
|
46
|
+
dip: tuple[float, float] = () # (azimuth, dip)
|
|
47
|
+
norm: tuple[float, float, float] = () # (x,y,z)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class WellCurve:
|
|
52
|
+
name: str
|
|
53
|
+
zm: list = field(default_factory=list)
|
|
54
|
+
values: list = field(default_factory=list)
|
|
55
|
+
na: float = np.nan
|
|
56
|
+
z_unit: str = ""
|
|
57
|
+
v_unit: str = ""
|
|
58
|
+
|
|
59
|
+
def __len__(self):
|
|
60
|
+
return len(self.values)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
################################
|
|
64
|
+
# EXTERNAL CURVES DATA STRUCTS #
|
|
65
|
+
################################
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class _InternalData:
|
|
70
|
+
|
|
71
|
+
file: str = None
|
|
72
|
+
curves: list = field(default_factory=list)
|
|
73
|
+
|
|
74
|
+
def append(self, name: str = ""):
|
|
75
|
+
assert name not in self.curves
|
|
76
|
+
self.curves.append(WellCurve(name))
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def current(self) -> WellCurve:
|
|
80
|
+
return self.curves[-1] if self.curves else None
|
|
81
|
+
|
|
82
|
+
def load(self) -> List[WellCurve]:
|
|
83
|
+
return self.curves
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class _ExternalAsciiData(_InternalData):
|
|
88
|
+
|
|
89
|
+
ncolumns: int = -1
|
|
90
|
+
nrows: int = -1
|
|
91
|
+
depth_column: int = 0
|
|
92
|
+
columns: list = field(default_factory=list)
|
|
93
|
+
|
|
94
|
+
def __setattr__(self, name: str, value: Any):
|
|
95
|
+
if name == "column":
|
|
96
|
+
assert self.columns
|
|
97
|
+
self.columns[-1] = value
|
|
98
|
+
else:
|
|
99
|
+
super().__setattr__(name, value)
|
|
100
|
+
|
|
101
|
+
def append(self, name: str = "", col: int = -1):
|
|
102
|
+
super().append(name)
|
|
103
|
+
self.columns.append(col)
|
|
104
|
+
|
|
105
|
+
@cached_property
|
|
106
|
+
def data(self) -> np.ndarray:
|
|
107
|
+
assert (self.nrows > 0) or (self.ncolumns > 0)
|
|
108
|
+
return np.fromfile(self.file, sep=" ").reshape(self.nrows, self.ncolumns)
|
|
109
|
+
|
|
110
|
+
def load(self) -> List[WellCurve]:
|
|
111
|
+
zm = self.data[:, self.depth_column]
|
|
112
|
+
for curve, column in zip(self.curves, self.columns):
|
|
113
|
+
assert curve.name and 0 <= column < self.ncolumns
|
|
114
|
+
curve.zm = zm
|
|
115
|
+
curve.values = self.data[:, column]
|
|
116
|
+
return self.curves
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class _ExternalBinaryData(_InternalData):
|
|
121
|
+
|
|
122
|
+
offsets: list = field(default_factory=list)
|
|
123
|
+
nb_pts: list = field(default_factory=list)
|
|
124
|
+
|
|
125
|
+
def __setattr__(self, name: str, value: Any):
|
|
126
|
+
if name == "seek":
|
|
127
|
+
assert self.offsets
|
|
128
|
+
self.offsets[-1] = int(value)
|
|
129
|
+
elif name == "npts":
|
|
130
|
+
assert self.nb_pts
|
|
131
|
+
self.nb_pts[-1] = int(value)
|
|
132
|
+
else:
|
|
133
|
+
super().__setattr__(name, value)
|
|
134
|
+
|
|
135
|
+
def append(self, name: str = "", nbp: int = 0, off: int = 0):
|
|
136
|
+
super().append(name)
|
|
137
|
+
self.nb_pts.append(nbp)
|
|
138
|
+
self.offsets.append(off)
|
|
139
|
+
|
|
140
|
+
def load(self) -> List[WellCurve]:
|
|
141
|
+
for curve, off, n in zip(self.curves, self.offsets, self.nb_pts):
|
|
142
|
+
assert curve.name and n
|
|
143
|
+
zv = np.fromfile(self.file, offset=off, count=2 * n, dtype=DT).astype(float)
|
|
144
|
+
curve.zm = zv[:n]
|
|
145
|
+
curve.values = zv[n:]
|
|
146
|
+
return self.curves
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
###############
|
|
150
|
+
# WELL READER #
|
|
151
|
+
##############
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def read_well(block, filename: str = ".", *args, **kwargs):
|
|
155
|
+
collar, path = (), []
|
|
156
|
+
kb = 0.0
|
|
157
|
+
markers, zones, curves = [], [], []
|
|
158
|
+
in_marker, in_curve = False, False
|
|
159
|
+
curve_data, wp_catalog = _InternalData(), []
|
|
160
|
+
path_curve = [None, None, None]
|
|
161
|
+
|
|
162
|
+
for line in block.splitlines():
|
|
163
|
+
line = line.strip()
|
|
164
|
+
if not line:
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
what, _, rest = line.partition(" ")
|
|
168
|
+
stuff = safesplit(rest)
|
|
169
|
+
|
|
170
|
+
if in_marker:
|
|
171
|
+
if what == "UNIT":
|
|
172
|
+
markers[-1].unit = rest
|
|
173
|
+
continue
|
|
174
|
+
elif what == "FEATURE":
|
|
175
|
+
markers[-1].feature = str(*stuff)
|
|
176
|
+
continue
|
|
177
|
+
elif what == "MREF":
|
|
178
|
+
markers[-1].horizon = str(*stuff)
|
|
179
|
+
continue
|
|
180
|
+
elif what == "NORM":
|
|
181
|
+
markers[-1].norm = tuple(float(el) for el in stuff)
|
|
182
|
+
continue
|
|
183
|
+
elif what == "DIP": # WARNING : The DIP information is given in Grads.
|
|
184
|
+
markers[-1].dip = tuple(float(el) * 180 / 200 for el in stuff)
|
|
185
|
+
continue
|
|
186
|
+
elif what == "DIPDEG":
|
|
187
|
+
markers[-1].dip = tuple(float(el) for el in stuff)
|
|
188
|
+
continue
|
|
189
|
+
else:
|
|
190
|
+
in_marker = False
|
|
191
|
+
|
|
192
|
+
if in_curve:
|
|
193
|
+
curve = curve_data.current
|
|
194
|
+
assert curve is not None
|
|
195
|
+
if what == "END_CURVE":
|
|
196
|
+
in_curve = False
|
|
197
|
+
continue
|
|
198
|
+
elif what == "PROPERTY":
|
|
199
|
+
assert not curve.name
|
|
200
|
+
curve.name = str(*stuff)
|
|
201
|
+
continue
|
|
202
|
+
elif what == "UNITS":
|
|
203
|
+
curve.z_unit, curve.v_unit = stuff
|
|
204
|
+
elif what in "PROP_UNIT":
|
|
205
|
+
curve.v_unit = str(*stuff)
|
|
206
|
+
elif what in "ZM_UNIT":
|
|
207
|
+
curve.z_unit = str(*stuff)
|
|
208
|
+
elif what == "PROP_NO_DATA_VALUE":
|
|
209
|
+
curve.na = float(*stuff)
|
|
210
|
+
elif what == "REC":
|
|
211
|
+
zm, val = (float(e) for e in stuff)
|
|
212
|
+
curve.zm.append(zm)
|
|
213
|
+
curve.values.append(val)
|
|
214
|
+
continue
|
|
215
|
+
elif what == "HOLE":
|
|
216
|
+
continue
|
|
217
|
+
elif what == "NPTS":
|
|
218
|
+
curve_data.npts = int(*stuff)
|
|
219
|
+
continue
|
|
220
|
+
elif what == "SEEK":
|
|
221
|
+
curve_data.seek = int(*stuff)
|
|
222
|
+
continue
|
|
223
|
+
elif what == "COLUMN":
|
|
224
|
+
curve_data.column = int(*stuff)
|
|
225
|
+
continue
|
|
226
|
+
|
|
227
|
+
if what in ("WREF", "VRTX"):
|
|
228
|
+
x, y, z = (float(e) for e in stuff)
|
|
229
|
+
if what == "WREF":
|
|
230
|
+
collar = [x, y, z]
|
|
231
|
+
else:
|
|
232
|
+
if len(path) == 0:
|
|
233
|
+
zm = 0
|
|
234
|
+
else:
|
|
235
|
+
zm = np.linalg.norm(np.asarray(path[-1]) - np.array(collar))
|
|
236
|
+
path.append((x, y, z, zm))
|
|
237
|
+
continue
|
|
238
|
+
elif what == "KB":
|
|
239
|
+
kb = float(*stuff)
|
|
240
|
+
if kb:
|
|
241
|
+
assert collar, "Well path must start with collar !"
|
|
242
|
+
collar[2] = kb
|
|
243
|
+
continue
|
|
244
|
+
elif what in ("PATH", "TVSS_PATH", "TVD_PATH"):
|
|
245
|
+
assert collar, "Well path must start with collar !"
|
|
246
|
+
zm, z, dx, dy = (float(e) for e in stuff)
|
|
247
|
+
x, y = collar[0] + dx, collar[1] + dy
|
|
248
|
+
if what.startswith("TVD"):
|
|
249
|
+
z -= collar[2]
|
|
250
|
+
path.append((x, y, z, zm))
|
|
251
|
+
continue
|
|
252
|
+
elif what == "MRKR":
|
|
253
|
+
label, _, zm = stuff
|
|
254
|
+
markers.append(WellMarker(label, zm=float(zm)))
|
|
255
|
+
in_marker = True # marker extra info can be multiline ...
|
|
256
|
+
continue
|
|
257
|
+
elif what == "ZONE":
|
|
258
|
+
label, za, zb, i = stuff
|
|
259
|
+
markers.append(WellZone(label, zfrom=float(za), zto=float(zb)))
|
|
260
|
+
continue
|
|
261
|
+
elif what.startswith("PATH_CURVE_"):
|
|
262
|
+
if what.endswith("X"):
|
|
263
|
+
path_curve[0] = stuff
|
|
264
|
+
elif what.endswith("Y"):
|
|
265
|
+
path_curve[1] = stuff
|
|
266
|
+
elif what.endswith("Y"):
|
|
267
|
+
path_curve[2] = stuff
|
|
268
|
+
else:
|
|
269
|
+
assert False
|
|
270
|
+
elif what in ("WP_CATALOG_FILE", "BINARY_DATA_FILE", "ASCII_DATA_FILE"):
|
|
271
|
+
# external data are declared BEFORE curve headers
|
|
272
|
+
# we need to store the content and process it later
|
|
273
|
+
file = Path(filename).parent / str(*stuff)
|
|
274
|
+
if what == "WP_CATALOG_FILE":
|
|
275
|
+
assert not wp_catalog
|
|
276
|
+
# wp_catalog = np.fromfile(file, dtype=DT)
|
|
277
|
+
# FIXME: external data is a list of ZM_NPTS
|
|
278
|
+
# Zm values along the path ... really necessary ?
|
|
279
|
+
continue
|
|
280
|
+
else:
|
|
281
|
+
assert not curve_data.current
|
|
282
|
+
if what.startswith("BINARY"):
|
|
283
|
+
curve_data = _ExternalBinaryData(file=file)
|
|
284
|
+
else:
|
|
285
|
+
curve_data = _ExternalAsciiData(file=file)
|
|
286
|
+
elif what == "WELL_CURVE":
|
|
287
|
+
assert not in_curve
|
|
288
|
+
curve_data.append()
|
|
289
|
+
in_curve = True
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
curves = curve_data.load()
|
|
293
|
+
if all(path_curve):
|
|
294
|
+
assert not path
|
|
295
|
+
path = np.column_stack(
|
|
296
|
+
[next(c.values for c in curves if c.name == n) for n in path_curve]
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
return collar, path, markers, zones, curves
|