resfo-utilities 0.3.0b0__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.

Potentially problematic release.


This version of resfo-utilities might be problematic. Click here for more details.

@@ -0,0 +1,568 @@
1
+ """
2
+ Implements a hypothesis strategy for unified summary files
3
+ (.SMSPEC and .UNSMRY)
4
+ See https://opm-project.org/?page_id=955
5
+ """
6
+
7
+ from dataclasses import astuple, dataclass
8
+ from datetime import datetime, timedelta
9
+ from enum import Enum, unique
10
+ from typing import Any, Self, IO
11
+ from itertools import zip_longest
12
+ from os import PathLike
13
+
14
+ import hypothesis.strategies as st
15
+ import numpy as np
16
+ import resfo
17
+ from hypothesis import assume
18
+ from hypothesis.extra.numpy import from_dtype
19
+ from pydantic import PositiveInt, conint
20
+ from collections.abc import Iterator
21
+ import numpy.typing as npt
22
+
23
+ from ._egrid_generator import GrdeclKeyword
24
+ from resfo_utilities._summary_keys import SPECIAL_KEYWORDS
25
+ from resfo_utilities import make_summary_key
26
+
27
+ _inter_region_summary_variables = [
28
+ "RGFR",
29
+ "RGFR+",
30
+ "RGFR-",
31
+ "RGFT",
32
+ "RGFT+",
33
+ "RGFT-",
34
+ "RGFTG",
35
+ "RGFTL",
36
+ "ROFR",
37
+ "ROFR+",
38
+ "ROFR-",
39
+ "ROFT",
40
+ "ROFT+",
41
+ "ROFT-",
42
+ "ROFTG",
43
+ "ROFTL",
44
+ "RWFR",
45
+ "RWFR+",
46
+ "RWFR-",
47
+ "RWFT",
48
+ "RWFT+",
49
+ "RWFT-",
50
+ "RCFT",
51
+ "RSFT",
52
+ "RNFT",
53
+ ]
54
+
55
+
56
+ @st.composite
57
+ def _root_memnonic(draw: st.DrawFn) -> str:
58
+ first_character = draw(st.sampled_from("ABFGRWCS"))
59
+ if first_character == "A":
60
+ second_character = draw(st.sampled_from("ALN"))
61
+ third_character = draw(st.sampled_from("QL"))
62
+ fourth_character = draw(st.sampled_from("RT"))
63
+ return first_character + second_character + third_character + fourth_character
64
+ else:
65
+ second_character = draw(st.sampled_from("OWGVLPT"))
66
+ third_character = draw(st.sampled_from("PIF"))
67
+ fourth_character = draw(st.sampled_from("RT"))
68
+ local = draw(st.sampled_from(["", "L"])) if first_character in "BCW" else ""
69
+ return (
70
+ local
71
+ + first_character
72
+ + second_character
73
+ + third_character
74
+ + fourth_character
75
+ )
76
+
77
+
78
+ @st.composite
79
+ def summary_variables(draw: st.DrawFn) -> str:
80
+ """Generator for valid summary variables.
81
+
82
+ See the OPM Flow manual section 11.1.
83
+ """
84
+ kind = draw(
85
+ st.sampled_from(
86
+ [
87
+ "special",
88
+ "network",
89
+ "exceptions",
90
+ "directional",
91
+ "up_or_down",
92
+ "mnemonic",
93
+ "segment",
94
+ "well",
95
+ "region2region",
96
+ "mnemonic",
97
+ "region",
98
+ ]
99
+ )
100
+ )
101
+ if kind == "special":
102
+ return draw(st.sampled_from(SPECIAL_KEYWORDS))
103
+ if kind == "exceptions":
104
+ return draw(
105
+ st.sampled_from(
106
+ ["BAPI", "BOSAT", "BPR", "FAQR", "FPR", "FWCT", "WBHP", "WWCT", "ROFR"]
107
+ )
108
+ )
109
+ elif kind == "directional":
110
+ direction = draw(st.sampled_from("IJK"))
111
+ return (
112
+ draw(st.sampled_from(["FLOO", "VELG", "VELO", "FLOW", "VELW"])) + direction
113
+ )
114
+ elif kind == "up_or_down":
115
+ dimension = draw(st.sampled_from("XYZRT"))
116
+ direction = draw(st.sampled_from(["", "-"]))
117
+ return draw(st.sampled_from(["GKR", "OKR", "WKR"])) + dimension + direction
118
+ elif kind == "network":
119
+ root = draw(_root_memnonic())
120
+ return "N" + root
121
+ elif kind == "segment":
122
+ return draw(
123
+ st.sampled_from(["SALQ", "SFR", "SGFR", "SGFRF", "SGFRS", "SGFTA", "SGFT"])
124
+ )
125
+ elif kind == "well":
126
+ return draw(
127
+ st.one_of(
128
+ st.builds(lambda r: "W" + r, _root_memnonic()),
129
+ st.sampled_from(
130
+ [
131
+ "WBHP",
132
+ "WBP5",
133
+ "WBP4",
134
+ "WBP9",
135
+ "WBP",
136
+ "WBHPH",
137
+ "WBHPT",
138
+ "WPIG",
139
+ "WPIL",
140
+ "WPIO",
141
+ "WPI5",
142
+ ]
143
+ ),
144
+ )
145
+ )
146
+ elif kind == "region2region":
147
+ return draw(st.sampled_from(_inter_region_summary_variables))
148
+ elif kind == "region":
149
+ return draw(st.builds(lambda r: "R" + r, _root_memnonic()))
150
+ else:
151
+ return draw(_root_memnonic())
152
+
153
+
154
+ _unit_names = st.sampled_from(
155
+ ["SM3/DAY", "BARSA", "SM3/SM3", "FRACTION", "DAYS", "HOURS", "SM3"]
156
+ )
157
+
158
+ _names = st.text(
159
+ min_size=8,
160
+ max_size=8,
161
+ alphabet=st.characters(
162
+ min_codepoint=65,
163
+ max_codepoint=90,
164
+ ),
165
+ )
166
+
167
+
168
+ @unique
169
+ class UnitSystem(Enum):
170
+ """The unit system used for summary values."""
171
+
172
+ METRIC = 1
173
+ FIELD = 2
174
+ LAB = 3
175
+
176
+ def to_smry(self) -> int:
177
+ return self.value
178
+
179
+
180
+ @unique
181
+ class Simulator(Enum):
182
+ """The simulator used to generate the summary."""
183
+
184
+ ECLIPSE_100 = 100
185
+ ECLIPSE_300 = 300
186
+ ECLIPSE_300_THERMAL = 500
187
+ INTERSECT = 700
188
+ FRONTSIM = 800
189
+
190
+ def to_smry(self) -> int:
191
+ return self.value
192
+
193
+
194
+ @dataclass
195
+ class SmspecIntehead(GrdeclKeyword):
196
+ """The values in the INTEHEAD array"""
197
+
198
+ unit: UnitSystem
199
+ simulator: Simulator
200
+
201
+ def to_smry(self) -> list[Any]:
202
+ return [value.to_smry() for value in astuple(self)]
203
+
204
+
205
+ @dataclass
206
+ class Date:
207
+ """The date given by the STARTDAT array"""
208
+
209
+ day: conint(ge=1, le=31) # type: ignore
210
+ month: conint(ge=1, le=12) # type: ignore
211
+ year: conint(gt=1901, lt=2038) # type: ignore
212
+ hour: conint(ge=0, lt=24) # type: ignore
213
+ minutes: conint(ge=0, lt=60) # type: ignore
214
+ micro_seconds: conint(ge=0, lt=60000000) # type: ignore
215
+
216
+ def to_smry(self) -> tuple[int, ...]:
217
+ return astuple(self)
218
+
219
+ def to_datetime(self) -> datetime:
220
+ return datetime(
221
+ year=self.year,
222
+ month=self.month,
223
+ day=self.day,
224
+ hour=self.hour,
225
+ minute=self.minutes,
226
+ second=self.micro_seconds // 10**6,
227
+ microsecond=self.micro_seconds % 10**6,
228
+ )
229
+
230
+ @classmethod
231
+ def from_datetime(cls, dt: datetime) -> Self:
232
+ return cls(
233
+ year=dt.year,
234
+ month=dt.month,
235
+ day=dt.day,
236
+ hour=dt.hour,
237
+ minutes=dt.minute,
238
+ micro_seconds=dt.second * 10**6 + dt.microsecond,
239
+ )
240
+
241
+
242
+ @dataclass
243
+ class Smspec:
244
+ """The contents of the .SMSPEC file"""
245
+
246
+ intehead: SmspecIntehead
247
+ restart: str
248
+ num_keywords: PositiveInt
249
+ nx: PositiveInt
250
+ ny: PositiveInt
251
+ nz: PositiveInt
252
+ restarted_from_step: PositiveInt
253
+ keywords: list[str]
254
+ well_names: list[str]
255
+ region_numbers: list[int]
256
+ units: list[str]
257
+ start_date: Date
258
+ lgrs: list[str] | None = None
259
+ numlx: list[PositiveInt] | None = None
260
+ numly: list[PositiveInt] | None = None
261
+ numlz: list[PositiveInt] | None = None
262
+ use_names: bool = False # whether to use the alias NAMES for WGNAMES
263
+
264
+ def to_smry(self) -> list[tuple[str, Any]]:
265
+ # The restart field contains 9 strings of length 8 which
266
+ # should contain the name of the file restarted from.
267
+ # If shorter than 72 characters (most likely), the rest
268
+ # are spaces. (opm manual table F.44, keyword name RESTART)
269
+ restart = self.restart.ljust(72, " ")
270
+ restart_list = [restart[i * 8 : i * 8 + 8] for i in range(9)]
271
+ return (
272
+ [
273
+ ("INTEHEAD", np.array(self.intehead.to_smry(), dtype=np.int32)),
274
+ ("RESTART ", restart_list),
275
+ (
276
+ "DIMENS ",
277
+ np.array(
278
+ [
279
+ self.num_keywords,
280
+ self.nx,
281
+ self.ny,
282
+ self.nz,
283
+ 0,
284
+ self.restarted_from_step,
285
+ ],
286
+ dtype=np.int32,
287
+ ),
288
+ ),
289
+ ("KEYWORDS", [kw.ljust(8) for kw in self.keywords]),
290
+ (
291
+ ("NAMES ", self.well_names)
292
+ if self.use_names
293
+ else ("WGNAMES ", self.well_names)
294
+ ),
295
+ ("NUMS ", np.array(self.region_numbers, dtype=np.int32)),
296
+ ("UNITS ", self.units),
297
+ ("STARTDAT", np.array(self.start_date.to_smry(), dtype=np.int32)),
298
+ ]
299
+ + ([("LGRS ", self.lgrs)] if self.lgrs is not None else [])
300
+ + ([("NUMLX ", self.numlx)] if self.numlx is not None else [])
301
+ + ([("NUMLY ", self.numly)] if self.numly is not None else [])
302
+ + ([("NUMLZ ", self.numlz)] if self.numlz is not None else [])
303
+ )
304
+
305
+ def summary_keys(self) -> Iterator[str]:
306
+ """All summary keys in the SMSPEC file."""
307
+
308
+ def optional(maybe_list: list[Any] | None) -> list[Any]:
309
+ if maybe_list is None:
310
+ return []
311
+ else:
312
+ return maybe_list
313
+
314
+ for var, num, name, lgr, lx, ly, lz in zip_longest(
315
+ self.keywords,
316
+ self.region_numbers,
317
+ self.well_names,
318
+ optional(self.lgrs),
319
+ optional(self.numlx),
320
+ optional(self.numly),
321
+ optional(self.numlz),
322
+ ):
323
+ yield make_summary_key(var, num, name, self.nx, self.ny, lgr, lx, ly, lz)
324
+
325
+ def to_file(
326
+ self,
327
+ filelike: str | PathLike[str] | IO[Any],
328
+ file_format: resfo.Format = resfo.Format.UNFORMATTED,
329
+ ) -> None:
330
+ """Writes the Smspec to a file"""
331
+ resfo.write(filelike, self.to_smry(), file_format)
332
+
333
+
334
+ _small_ints = from_dtype(np.dtype(np.int32), min_value=1, max_value=10)
335
+
336
+ _summary_keys = st.lists(summary_variables(), min_size=1)
337
+
338
+
339
+ @st.composite
340
+ def smspecs(
341
+ draw: st.DrawFn,
342
+ sum_keys: st.SearchStrategy[list[str]] | None = None,
343
+ start_date: st.SearchStrategy[Date] | None = None,
344
+ use_days: st.SearchStrategy[bool] | None = None,
345
+ well_names: st.SearchStrategy[str] | None = None,
346
+ lgr_names: st.SearchStrategy[str] | None = None,
347
+ restart_names: st.SearchStrategy[str] | None = None,
348
+ ) -> Smspec:
349
+ """Hypothesis strategy for ``Smspec`` s."""
350
+ use_days = st.booleans() if use_days is None else use_days
351
+ use_locals = draw(st.booleans())
352
+ sum_keys_ = draw(sum_keys or _summary_keys)
353
+ if any(sk.startswith("L") for sk in sum_keys_):
354
+ use_locals = True
355
+ n = len(sum_keys_) + 1
356
+ nx = draw(_small_ints)
357
+ ny = draw(_small_ints)
358
+ nz = draw(_small_ints)
359
+ keywords = ["TIME "] + sum_keys_
360
+ if draw(use_days):
361
+ units = [
362
+ "DAYS ",
363
+ *draw(st.lists(_unit_names, min_size=n - 1, max_size=n - 1)),
364
+ ]
365
+ else:
366
+ units = [
367
+ "HOURS ",
368
+ *draw(st.lists(_unit_names, min_size=n - 1, max_size=n - 1)),
369
+ ]
370
+ well_names_ = [
371
+ ":+:+:+:+",
372
+ *draw(st.lists(well_names or _names, min_size=n - 1, max_size=n - 1)),
373
+ ]
374
+ if use_locals: # use local
375
+ lgrs = draw(st.lists(lgr_names or _names, min_size=n, max_size=n))
376
+ numlx = draw(st.lists(_small_ints, min_size=n, max_size=n))
377
+ numly = draw(st.lists(_small_ints, min_size=n, max_size=n))
378
+ numlz = draw(st.lists(_small_ints, min_size=n, max_size=n))
379
+ else:
380
+ lgrs = None
381
+ numlx = None
382
+ numly = None
383
+ numlz = None
384
+ region_numbers = [-32676] + draw(
385
+ st.lists(
386
+ from_dtype(np.dtype(np.int32), min_value=1, max_value=nx * ny * nz),
387
+ min_size=len(sum_keys_),
388
+ max_size=len(sum_keys_),
389
+ )
390
+ )
391
+ return draw(
392
+ st.builds(
393
+ Smspec,
394
+ nx=st.just(nx),
395
+ ny=st.just(ny),
396
+ nz=st.just(nz),
397
+ # restarted_from_step is hardcoded to 0 because
398
+ # of a bug in enkf_obs where it assumes that
399
+ # ecl_sum_get_first_report_step is always 1
400
+ restarted_from_step=st.just(0),
401
+ num_keywords=st.just(n),
402
+ restart=restart_names or _names,
403
+ keywords=st.just(keywords),
404
+ well_names=st.just(well_names_),
405
+ lgrs=st.just(lgrs),
406
+ numlx=st.just(numlx),
407
+ numly=st.just(numly),
408
+ numlz=st.just(numlz),
409
+ region_numbers=st.just(region_numbers),
410
+ units=st.just(units),
411
+ start_date=start_date or st.datetimes().map(Date.from_datetime),
412
+ use_names=st.booleans(),
413
+ )
414
+ )
415
+
416
+
417
+ @dataclass
418
+ class SummaryMiniStep:
419
+ """One ministep section in a summary file."""
420
+
421
+ mini_step: int
422
+ params: list[float]
423
+
424
+ def to_smry(self) -> list[tuple[str, npt.NDArray[Any]]]:
425
+ return [
426
+ ("MINISTEP", np.array([self.mini_step], dtype=np.int32)),
427
+ ("PARAMS ", np.array(self.params, dtype=np.float32)),
428
+ ]
429
+
430
+
431
+ @dataclass
432
+ class SummaryStep:
433
+ """One step section in a summary file.
434
+
435
+ Is just one SEQHDR followed by one or more ministeps.
436
+ """
437
+
438
+ seqnum: int
439
+ ministeps: list[SummaryMiniStep]
440
+
441
+ def to_smry(self) -> list[tuple[str, npt.NDArray[Any]]]:
442
+ return [("SEQHDR ", np.array([self.seqnum], dtype=np.int32))] + [
443
+ i for ms in self.ministeps for i in ms.to_smry()
444
+ ]
445
+
446
+
447
+ @dataclass
448
+ class Unsmry:
449
+ """The contents of a unified summary file."""
450
+
451
+ steps: list[SummaryStep]
452
+
453
+ def to_smry(self) -> list[tuple[str, npt.NDArray[Any]]]:
454
+ a = [i for step in self.steps for i in step.to_smry()]
455
+ return a
456
+
457
+ def to_file(
458
+ self,
459
+ filelike: str | PathLike[str] | IO[Any],
460
+ file_format: resfo.Format = resfo.Format.UNFORMATTED,
461
+ ) -> None:
462
+ resfo.write(filelike, self.to_smry(), file_format)
463
+
464
+
465
+ _positive_floats = from_dtype(
466
+ np.dtype(np.float32),
467
+ min_value=np.float32(0.1), # type: ignore
468
+ max_value=np.float32(1e19), # type: ignore
469
+ allow_nan=False,
470
+ allow_infinity=False,
471
+ )
472
+
473
+
474
+ _start_dates = st.datetimes(
475
+ min_value=datetime.strptime("1969-1-1", "%Y-%m-%d"),
476
+ max_value=datetime.strptime("2100-1-1", "%Y-%m-%d"),
477
+ )
478
+
479
+ _time_delta_lists = st.lists(
480
+ st.floats(
481
+ min_value=0.1,
482
+ max_value=2500.0, # in days ~= 6.8 years
483
+ allow_nan=False,
484
+ allow_infinity=False,
485
+ ),
486
+ min_size=2,
487
+ max_size=100,
488
+ unique=True,
489
+ )
490
+
491
+
492
+ @st.composite
493
+ def summaries(
494
+ draw: st.DrawFn,
495
+ start_date: st.SearchStrategy[datetime] = _start_dates,
496
+ time_deltas: st.SearchStrategy[list[float]] = _time_delta_lists,
497
+ summary_keys: st.SearchStrategy[list[str]] = _summary_keys,
498
+ use_days: st.SearchStrategy[bool] | None = None,
499
+ report_step_only: bool = False,
500
+ ) -> tuple[Smspec, Unsmry]:
501
+ """Generator of a smspec and unsmry pair.
502
+
503
+ The strategy ensures that the files matches in number of keywords.
504
+ """
505
+ sum_keys = draw(summary_keys)
506
+ first_date = draw(start_date)
507
+ days = draw(use_days if use_days is not None else st.booleans())
508
+ smspec = draw(
509
+ smspecs(
510
+ sum_keys=st.just(sum_keys),
511
+ start_date=st.just(Date.from_datetime(first_date)),
512
+ use_days=st.just(days),
513
+ )
514
+ )
515
+ # The smspec should be unique up to summary_keys.
516
+ # This just mimics the behavior of simulators.
517
+ assume(len(set(smspec.summary_keys())) == len(smspec.keywords))
518
+ dates = [0.0] + draw(time_deltas)
519
+ try:
520
+ if days:
521
+ _ = first_date + timedelta(days=max(dates))
522
+ else:
523
+ _ = first_date + timedelta(hours=max(dates))
524
+ except (ValueError, OverflowError): # datetime has a max year
525
+ assume(False)
526
+
527
+ ds = sorted(dates, reverse=True)
528
+ steps = []
529
+ i = 0
530
+ j = 0
531
+ while len(ds) > 0:
532
+ minis = []
533
+ max_val = 1 if report_step_only else len(ds)
534
+ for _ in range(draw(st.integers(min_value=1, max_value=max_val))):
535
+ minis.append(
536
+ SummaryMiniStep(
537
+ i,
538
+ [
539
+ ds.pop(),
540
+ *draw(
541
+ st.lists(
542
+ _positive_floats,
543
+ min_size=len(sum_keys),
544
+ max_size=len(sum_keys),
545
+ )
546
+ ),
547
+ ],
548
+ )
549
+ )
550
+ i += 1
551
+ steps.append(SummaryStep(j, minis))
552
+ j += 1
553
+ return smspec, Unsmry(steps)
554
+
555
+
556
+ __all__ = [
557
+ "summary_variables",
558
+ "UnitSystem",
559
+ "Simulator",
560
+ "SmspecIntehead",
561
+ "Date",
562
+ "Smspec",
563
+ "smspecs",
564
+ "SummaryMiniStep",
565
+ "SummaryStep",
566
+ "Unsmry",
567
+ "summaries",
568
+ ]
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: resfo-utilities
3
+ Version: 0.3.0b0
4
+ Summary: A utility library for working with the output of reservoir simulators.
5
+ Author-email: Equinor <fg_sib-scout@equinor.com>
6
+ Maintainer-email: Eivind Jahren <ejah@equinor.com>, Håkon Steinkopf Søhoel <hsoho@equinor.com>
7
+ License: LGPL-3.0
8
+ Project-URL: Homepage, https://github.com/equinor/resfo-utilities
9
+ Project-URL: Repository, https://github.com/equinor/resfo-utilities
10
+ Project-URL: Documentation, https://resfo-utilities.readthedocs.io/en/stable/
11
+ Project-URL: Bug Tracker, https://github.com/equinor/resfo-utilities/issues
12
+ Classifier: Development Status :: 1 - Planning
13
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: <=3.14,>=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE.md
21
+ Requires-Dist: numpy
22
+ Requires-Dist: resfo>=5.0.0b1
23
+ Requires-Dist: scipy
24
+ Requires-Dist: natsort
25
+ Provides-Extra: testing
26
+ Requires-Dist: hypothesis; extra == "testing"
27
+ Requires-Dist: pydantic; extra == "testing"
28
+ Provides-Extra: doc
29
+ Requires-Dist: sphinx; extra == "doc"
30
+ Requires-Dist: sphinx-rtd-theme; extra == "doc"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest; extra == "dev"
33
+ Requires-Dist: pre-commit; extra == "dev"
34
+ Requires-Dist: mypy; extra == "dev"
35
+ Requires-Dist: pytest-benchmark; extra == "dev"
36
+ Requires-Dist: scipy-stubs; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ resfo-utilities
40
+ ===============
41
+
42
+
43
+ resfo-utilities is a library for working with output from
44
+ several reservoir simulators such as [opm
45
+ flow](https://github.com/OPM/opm-simulators).
46
+
47
+ Installation
48
+ ============
49
+
50
+ `pip install resfo-utilities`
51
+
52
+
53
+ How to contribute
54
+ =================
55
+
56
+ We use uv to have one synchronized development environment for all packages.
57
+ See [installing uv](https://docs.astral.sh/uv/getting-started/installation/). We
58
+ recommend either installing uv using your systems package manager, or creating
59
+ a small virtual environment you intall base packages into (such as `uv` and `pre-commit`).
60
+
61
+ Once uv is installed, you can get a development environment by running:
62
+
63
+ ```sh
64
+ git clone https://github.com/equinor/resfo-utilities
65
+ cd resfo-utilities
66
+ uv sync --all-extras
67
+ ```
68
+
69
+
70
+ You should set up `pre-commit` to ensure style checks are done as you commit:
71
+
72
+ ```bash
73
+ uv run pre-commit install --hook-type pre-push
74
+ ```
@@ -0,0 +1,12 @@
1
+ resfo_utilities/__init__.py,sha256=Qg5znqkYEp11hn7Y-D3jTuUZnf19viG150qHsXPqT_s,615
2
+ resfo_utilities/_cornerpoint_grid.py,sha256=I453AgeOqQbsBpqtgxpatQ6op4D8Wzk9xp7emB9cVGY,21690
3
+ resfo_utilities/_summary_keys.py,sha256=8QZZwAQlJs3yOj9IG1iUxd3LU1CxZsjBYEWHIgN4iG4,13086
4
+ resfo_utilities/_summary_reader.py,sha256=BHJ-vifjoUXRBteyVdAr9asTJPUdiICtDTdOwXDE3pQ,20580
5
+ resfo_utilities/testing/__init__.py,sha256=z28oj49eVYt4B1DDWv4ZtzB2CL0KAEl-OnwRSIO9ilE,1834
6
+ resfo_utilities/testing/_egrid_generator.py,sha256=nUYrs2qLzyLpH7wVgoG1rcsila6Klpd3DiAbC32zkbY,11194
7
+ resfo_utilities/testing/_summary_generator.py,sha256=XzgUdPMUDJAuNqN0bFH6-w8j69SgBG-Orp1Rjb2l4Po,16570
8
+ resfo_utilities-0.3.0b0.dist-info/licenses/LICENSE.md,sha256=3IXI3x1RN4jDR2PonpWF1guc33zagcTgpq_VnboE3UU,7653
9
+ resfo_utilities-0.3.0b0.dist-info/METADATA,sha256=Uar86s8_gBDVoKnJAL-n-USAqMEl713X8Jbv8kAZN54,2515
10
+ resfo_utilities-0.3.0b0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ resfo_utilities-0.3.0b0.dist-info/top_level.txt,sha256=VjItoaJHqsDLhHEvCjEI5bN2sZy55tA-zlkl-CtggEU,16
12
+ resfo_utilities-0.3.0b0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+