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/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