fmu-sumo 2.4.10__tar.gz → 2.6.0__tar.gz

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.
Files changed (70) hide show
  1. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/PKG-INFO +1 -1
  2. fmu_sumo-2.6.0/examples/explorer.ipynb +460 -0
  3. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/pyproject.toml +1 -1
  4. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/_version.py +16 -3
  5. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/_child.py +5 -2
  6. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/_document.py +3 -4
  7. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/_metrics.py +53 -0
  8. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/_search_context.py +294 -206
  9. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/ensemble.py +24 -1
  10. fmu_sumo-2.6.0/src/fmu/sumo/explorer/objects/ensembles.py +31 -0
  11. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/iteration.py +24 -1
  12. fmu_sumo-2.6.0/src/fmu/sumo/explorer/objects/iterations.py +31 -0
  13. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/realization.py +9 -1
  14. fmu_sumo-2.6.0/src/fmu/sumo/explorer/objects/realizations.py +42 -0
  15. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu_sumo.egg-info/PKG-INFO +1 -1
  16. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu_sumo.egg-info/SOURCES.txt +0 -1
  17. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/tests/test_explorer.py +34 -0
  18. fmu_sumo-2.4.10/examples/explorer.ipynb +0 -359
  19. fmu_sumo-2.4.10/examples/tables.ipynb +0 -619
  20. fmu_sumo-2.4.10/src/fmu/sumo/explorer/objects/ensembles.py +0 -67
  21. fmu_sumo-2.4.10/src/fmu/sumo/explorer/objects/iterations.py +0 -67
  22. fmu_sumo-2.4.10/src/fmu/sumo/explorer/objects/realizations.py +0 -59
  23. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/.github/pull_request_template.md +0 -0
  24. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/.github/workflows/build_docs.yaml +0 -0
  25. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/.github/workflows/check_formatting.yml +0 -0
  26. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/.github/workflows/publish_release.yaml +0 -0
  27. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/.github/workflows/run_tests.yaml +0 -0
  28. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/.gitignore +0 -0
  29. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/.pre-commit-config.yaml +0 -0
  30. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/.readthedocs.yml +0 -0
  31. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/CONTRIBUTING.md +0 -0
  32. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/LICENSE +0 -0
  33. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/README.md +0 -0
  34. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/SECURITY.md +0 -0
  35. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/docs/_static/equinor-logo.png +0 -0
  36. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/docs/_static/equinor-logo2.jpg +0 -0
  37. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/docs/_static/equinor_logo.jpg +0 -0
  38. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/docs/_static/equinor_logo_only.jpg +0 -0
  39. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/docs/_templates/layout.html +0 -0
  40. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/docs/conf.py +0 -0
  41. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/docs/explorer.rst +0 -0
  42. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/docs/index.rst +0 -0
  43. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/examples/explorer2.ipynb +0 -0
  44. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/examples/grids-and-properties.ipynb +0 -0
  45. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/examples/metrics.ipynb +0 -0
  46. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/examples/table-aggregation.ipynb +0 -0
  47. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/setup.cfg +0 -0
  48. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/__init__.py +0 -0
  49. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/__init__.py +0 -0
  50. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/__init__.py +0 -0
  51. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/cache.py +0 -0
  52. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/explorer.py +0 -0
  53. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/filters.py +0 -0
  54. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/__init__.py +0 -0
  55. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/case.py +0 -0
  56. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/cases.py +0 -0
  57. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/cpgrid.py +0 -0
  58. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/cpgrid_property.py +0 -0
  59. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/cube.py +0 -0
  60. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/dictionary.py +0 -0
  61. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/polygons.py +0 -0
  62. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/surface.py +0 -0
  63. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/objects/table.py +0 -0
  64. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu/sumo/explorer/timefilter.py +0 -0
  65. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu_sumo.egg-info/dependency_links.txt +0 -0
  66. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu_sumo.egg-info/requires.txt +0 -0
  67. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/src/fmu_sumo.egg-info/top_level.txt +0 -0
  68. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/tests/conftest.py +0 -0
  69. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/tests/context.py +0 -0
  70. {fmu_sumo-2.4.10 → fmu_sumo-2.6.0}/tests/test_objects_table.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmu-sumo
3
- Version: 2.4.10
3
+ Version: 2.6.0
4
4
  Summary: Python package for interacting with Sumo in an FMU setting
5
5
  Author: Equinor
6
6
  License: Apache License
@@ -0,0 +1,460 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "These are basic examples of how the `fmu-sumo` package can be used for consuming FMU results via the Python API.\n",
8
+ "\n",
9
+ "To install fmu-sumo, use `pip install fmu-sumo`\n",
10
+ "\n",
11
+ "We are always grateful for feedback and any question you might have. See the <a href=\"https://doc-sumo-doc-prod.radix.equinor.com/\">Sumo Documentation</a> for how to get in touch."
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": null,
17
+ "metadata": {},
18
+ "outputs": [],
19
+ "source": [
20
+ "from fmu.sumo.explorer import Explorer"
21
+ ]
22
+ },
23
+ {
24
+ "cell_type": "code",
25
+ "execution_count": null,
26
+ "metadata": {},
27
+ "outputs": [],
28
+ "source": [
29
+ "%%html\n",
30
+ "<style>table {margin-left: 0 !important;}</style>"
31
+ ]
32
+ },
33
+ {
34
+ "attachments": {},
35
+ "cell_type": "markdown",
36
+ "metadata": {},
37
+ "source": [
38
+ "### Terminology\n",
39
+ "\n",
40
+ "Hiearchy of FMU results:\n",
41
+ "| term | description | disk equivalent | examples of data in this context |\n",
42
+ "| -- | -- | -- | -- |\n",
43
+ "| model | The workflow that, when realized, created these results | /project/.../model/**revision**/ | |\n",
44
+ "| | | | |\n",
45
+ "| case | A collection of _ensembles_ | /scratch/field/user/**case** | Observations |\n",
46
+ "| ensemble | A collection of _realizations_ | /scratch/field/user/case/realization-*/**ensemble**/ | Aggregated results |\n",
47
+ "| realization | A collection of data objects produced by the same realization | /scratch/field/user/case/**realization**/ensemble/ | Exported results |\n",
48
+ "| | | | |\n",
49
+ "| entity | The array of objects that are representations of the same result | iter-\\*/realization-\\*/**share/results/type/myfile.xyz | |\n",
50
+ "\n",
51
+ "For more details, refer to https://doc-sumo-doc-prod.radix.equinor.com/fmu_results\n",
52
+ "\n",
53
+ "### Principles\n",
54
+ "The basic entry point for consuming FMU results is the _case_. On disk, we are used to the case concept as a folder with a name. A name, however, is not unique. Therefore, cases in terms of FMU results are assigned unique IDs allowing us to refer explicitly to a specific case regardless of its name.\n",
55
+ "\n",
56
+ "When working with FMU results, we mostly operate within the bounds of a specific _ensemble_. Note, however, that FMU results exists both in the case context, the ensemble context and in the realization context."
57
+ ]
58
+ },
59
+ {
60
+ "attachments": {},
61
+ "cell_type": "markdown",
62
+ "metadata": {},
63
+ "source": [
64
+ "### Initialize Explorer\n",
65
+ "Initializing the explorer will establish a connection to Sumo. If you don't have a valid access token stored, you will be taken through the authentication process."
66
+ ]
67
+ },
68
+ {
69
+ "cell_type": "code",
70
+ "execution_count": null,
71
+ "metadata": {},
72
+ "outputs": [],
73
+ "source": [
74
+ "sumo = Explorer()"
75
+ ]
76
+ },
77
+ {
78
+ "cell_type": "code",
79
+ "execution_count": null,
80
+ "metadata": {},
81
+ "outputs": [],
82
+ "source": [
83
+ "# Get Drogon cases\n",
84
+ "myassetname = \"Drogon\" # Must be a valid asset on Sumo\n",
85
+ "cases = sumo.cases.filter(asset=myassetname)\n",
86
+ "\n",
87
+ "# Filter on user\n",
88
+ "cases = cases.filter(user=\"peesv\")\n",
89
+ "\n",
90
+ "# Iterate over results\n",
91
+ "print(f\"\\nFound {len(cases)} cases:\")\n",
92
+ "\n",
93
+ "for case in cases:\n",
94
+ " print(f\"Case: {case.name} | {case.uuid}\")\n",
95
+ " for ensemble in case.ensembles:\n",
96
+ " print(f\" Ensemble: {ensemble.name} (n={len(ensemble.realizations)})\")\n",
97
+ "\n",
98
+ "\n",
99
+ "# Option 1 (recommended): Get case by uuid\n",
100
+ "mycaseuuid = cases[0].uuid # for sake of example\n",
101
+ "unique_cases = sumo.cases.filter(uuid=mycaseuuid)[0]\n",
102
+ "\n",
103
+ "# Option 2: Get case by name (name is not guaranteed to be unique)\n",
104
+ "mycasename = cases[0].name # for sake of example\n",
105
+ "named_cases = sumo.cases.filter(name=mycasename)\n",
106
+ "\n",
107
+ "if len(named_cases) > 1:\n",
108
+ " raise ValueError(f\"More than one case exist with name {mycasename}, please use UUID.\")\n",
109
+ "elif len(named_cases) == 0:\n",
110
+ " raise ValueError(f\"No case with name {mycasename} exist.\")\n",
111
+ "else:\n",
112
+ " mycase = named_cases[0]"
113
+ ]
114
+ },
115
+ {
116
+ "cell_type": "code",
117
+ "execution_count": null,
118
+ "metadata": {},
119
+ "outputs": [],
120
+ "source": [
121
+ "# Select case\n",
122
+ "mycase = cases[-1] # for sake of the example we pick the last - you might want to select your case differently\n",
123
+ "print(\"Selected case: \")\n",
124
+ "print(f\" -> {mycase.name} ({mycase.uuid}) [{mycase.status}]\")\n",
125
+ "\n",
126
+ "# Select ensemble\n",
127
+ "myensemble = mycase.ensembles[-1] # for sake of the example - you might want to be more explitic on which ensemble you select\n",
128
+ "print(\"Selected ensemble:\")\n",
129
+ "print(f\" -> {myensemble.name}\")"
130
+ ]
131
+ },
132
+ {
133
+ "attachments": {},
134
+ "cell_type": "markdown",
135
+ "metadata": {},
136
+ "source": [
137
+ "At this point, we have identified our case and the ensemble we want to work with. Now we will consume data:\n",
138
+ "\n",
139
+ "<div class=\"alert alert-block alert-info\">\n",
140
+ "As opposed to on the disk, FMU results stored in Sumo are stored in a flat structure and we use metadata to identify individual data objects and arrays of data across an ensemble. The Explorer is fairly versitale, and backed by a powerful search engine. This means that there are usually multiple ways of getting to the same data, depending on your use case. In these examples, we show a few of these patterns.\n",
141
+ "</div>\n",
142
+ "\n",
143
+ "### Search context\n",
144
+ "An important concept in the Explorer is the _SearchContext_. This is a collection of data, filtered according to your needs. Here are some examples of SearchContexts:\n"
145
+ ]
146
+ },
147
+ {
148
+ "cell_type": "code",
149
+ "execution_count": null,
150
+ "metadata": {},
151
+ "outputs": [],
152
+ "source": [
153
+ "# all _surfaces_ in our ensemble\n",
154
+ "myensemble.surfaces\n",
155
+ "\n",
156
+ "# all _tables_ in a case\n",
157
+ "myensemble.tables"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "markdown",
162
+ "metadata": {},
163
+ "source": [
164
+ "## Filtering\n",
165
+ "The Search Context allows for further filtering:"
166
+ ]
167
+ },
168
+ {
169
+ "cell_type": "code",
170
+ "execution_count": null,
171
+ "metadata": {},
172
+ "outputs": [],
173
+ "source": [
174
+ "# list all names available for these surfaces\n",
175
+ "myensemble.surfaces.names\n",
176
+ "\n",
177
+ "# Filter on a specific name\n",
178
+ "myensemble.surfaces.filter(name=\"Therys Fm. Top\")\n",
179
+ "\n",
180
+ "# list all tagnames available for surfaces with this name\n",
181
+ "myensemble.surfaces.filter(name=\"Therys Fm. Top\").tagnames\n",
182
+ "\n",
183
+ "# Filter on a specific name and tagname\n",
184
+ "myensemble.surfaces.filter(name=\"Therys Fm. Top\", tagname=\"DS_extract_geogrid\")\n",
185
+ "\n",
186
+ "# Filter on a specific name, tagname and realization\n",
187
+ "myensemble.surfaces.filter(name=\"Therys Fm. Top\", tagname=\"DS_extract_geogrid\", realization=0)\n",
188
+ "\n",
189
+ "# In the Drogon example, this has now filtered down to exactly 1 surface object.\n",
190
+ "# This may not be the situation for your data, and more filters might be required."
191
+ ]
192
+ },
193
+ {
194
+ "cell_type": "markdown",
195
+ "metadata": {},
196
+ "source": [
197
+ "Now we go from \"finding data\" to \"using data\". For the Surface example, we recommend using XTgeo:"
198
+ ]
199
+ },
200
+ {
201
+ "cell_type": "code",
202
+ "execution_count": null,
203
+ "metadata": {},
204
+ "outputs": [],
205
+ "source": [
206
+ "mysurfs = myensemble.surfaces.filter(name=\"Therys Fm. Top\", tagname=\"DS_extract_geogrid\", realization=0)\n",
207
+ "\n",
208
+ "if len(mysurfs) != 1:\n",
209
+ " print(f\"Warning! The collection has {len(mysurfs)} surfaces, which is not exactly 1 surface object.\")\n",
210
+ "\n",
211
+ "mysurf = mysurfs[0].to_regular_surface() # `mysurf` is now a RegularSurface object\n",
212
+ "\n",
213
+ "mysurf.quickplot(title=\"A surface!\")"
214
+ ]
215
+ },
216
+ {
217
+ "attachments": {},
218
+ "cell_type": "markdown",
219
+ "metadata": {},
220
+ "source": [
221
+ "### Statistical aggregations\n",
222
+ "A key feature of FMU is it's ability to represent uncertainty, by realizing the same result many times with different parameters. As opposed to classical model workflows that create a single, atomic instance of each result - FMU workflows produce results in the form of distributions.\n",
223
+ "\n",
224
+ "To analyze such distributions, it is frequently useful to create statistical aggregations:"
225
+ ]
226
+ },
227
+ {
228
+ "cell_type": "code",
229
+ "execution_count": null,
230
+ "metadata": {},
231
+ "outputs": [],
232
+ "source": [
233
+ "# Perform statistical aggregation on SurfaceCollection\n",
234
+ "\n",
235
+ "surfs = myensemble.surfaces.filter(name=\"Therys Fm. Top\", tagname=\"DS_extract_geogrid\", realization=True)\n",
236
+ "\n",
237
+ "print(f\"There are {len(myensemble.realizations)} realizations, and the Search Context has {len(surfs)} individual surface objects.\")\n",
238
+ "\n",
239
+ "mean = surfs.aggregation(operation=\"mean\") # operations: max, mean, std, p10, p90, p50\n",
240
+ "mean.to_regular_surface().quickplot(title=\"Mean surface!\")"
241
+ ]
242
+ },
243
+ {
244
+ "cell_type": "markdown",
245
+ "metadata": {},
246
+ "source": [
247
+ "Through the <a href=\"https://fmu-sumo.app.radix.equinor.com/\">Sumo web interface</a>, you can also call bulk aggregation on all surfaces in your ensemble. When aggregated surfaces are made, you can also access them directly with a filter, as shown below:"
248
+ ]
249
+ },
250
+ {
251
+ "cell_type": "code",
252
+ "execution_count": null,
253
+ "metadata": {},
254
+ "outputs": [],
255
+ "source": [
256
+ "mymean = myensemble.surfaces.filter(name=\"Therys Fm. Top\", tagname=\"DS_extract_geogrid\", aggregation=\"mean\")\n",
257
+ "mymean[0].to_regular_surface().quickplot(title=\"Still the mean\")"
258
+ ]
259
+ },
260
+ {
261
+ "attachments": {},
262
+ "cell_type": "markdown",
263
+ "metadata": {},
264
+ "source": [
265
+ "### Time filtration\n",
266
+ "The `TimeFilter` class can be used to construct a time filter which can be passed to the `SurfaceCollection.filter` method."
267
+ ]
268
+ },
269
+ {
270
+ "cell_type": "code",
271
+ "execution_count": null,
272
+ "metadata": {},
273
+ "outputs": [],
274
+ "source": [
275
+ "from fmu.sumo.explorer import TimeFilter, TimeType\n",
276
+ "\n",
277
+ "# get surfaces with timestamps\n",
278
+ "time = TimeFilter(time_type=TimeType.TIMESTAMP)\n",
279
+ "surfs = myensemble.surfaces.filter(time=time)\n",
280
+ "print(\"Timestamp:\", len(surfs))\n",
281
+ "\n",
282
+ "# get surfaces with time intervals\n",
283
+ "time = TimeFilter(time_type=TimeType.INTERVAL)\n",
284
+ "surfs = myensemble.surfaces.filter(time=time)\n",
285
+ "print(\"Interval:\", len(surfs))\n",
286
+ "\n",
287
+ "\n",
288
+ "# get surfaces with time data (timestamp or interval)\n",
289
+ "time = TimeFilter(time_type=TimeType.ALL)\n",
290
+ "surfs = myensemble.surfaces.filter(time=time)\n",
291
+ "print(\"Time data:\", len(surfs))\n",
292
+ "\n",
293
+ "\n",
294
+ "# get surfaces without time data\n",
295
+ "time = TimeFilter(time_type=TimeType.NONE)\n",
296
+ "surfs = myensemble.surfaces.filter(time=time)\n",
297
+ "print(\"No time data:\", len(surfs))\n",
298
+ "\n",
299
+ "\n",
300
+ "# get avaiable timestamps\n",
301
+ "timestamps = myensemble.surfaces.timestamps\n",
302
+ "print(\"\\nTimestamps:\", timestamps)\n",
303
+ "\n",
304
+ "# get available intervals\n",
305
+ "intervals = myensemble.surfaces.intervals\n",
306
+ "print(\"Intervals:\", intervals)\n",
307
+ "\n",
308
+ "\n",
309
+ "# get surfaces with timestamp in range\n",
310
+ "time = TimeFilter(\n",
311
+ " time_type=TimeType.TIMESTAMP, start=\"2018-01-01\", end=\"2022-01-01\"\n",
312
+ ")\n",
313
+ "surfs = myensemble.surfaces.filter(time=time)\n",
314
+ "\n",
315
+ "# get surfaces with time intervals in range\n",
316
+ "time = TimeFilter(\n",
317
+ " time_type=TimeType.INTERVAL, start=\"2018-01-01\", end=\"2022-01-01\"\n",
318
+ ")\n",
319
+ "surfs = myensemble.surfaces.filter(time=time)\n",
320
+ "\n",
321
+ "# get surfaces where intervals overlap with range\n",
322
+ "time = TimeFilter(\n",
323
+ " time_type=TimeType.INTERVAL,\n",
324
+ " start=\"2018-01-01\",\n",
325
+ " end=\"2022-01-01\",\n",
326
+ " overlap=True,\n",
327
+ ")\n",
328
+ "surfs = myensemble.surfaces.filter(time=time)\n",
329
+ "\n",
330
+ "# get surfaces with exact timestamp matching (t0 == start)\n",
331
+ "time = TimeFilter(time_type=TimeType.TIMESTAMP, start=\"2018-01-01\", exact=True)\n",
332
+ "surfs = myensemble.surfaces.filter(time=time)\n",
333
+ "\n",
334
+ "# get surfaces with exact interval matching (t0 == start AND t1 == end)\n",
335
+ "time = TimeFilter(\n",
336
+ " time_type=TimeType.INTERVAL,\n",
337
+ " start=\"2018-01-01\",\n",
338
+ " end=\"2022-01-01\",\n",
339
+ " exact=True,\n",
340
+ ")\n",
341
+ "surfs = myensemble.surfaces.filter(time=time)"
342
+ ]
343
+ },
344
+ {
345
+ "cell_type": "markdown",
346
+ "metadata": {},
347
+ "source": [
348
+ "## Tables\n",
349
+ "FMU produces results across almost all types of data, including a significant amount of tables. While most tables are relatively small, some tables - such as the `UNSMRY` - can be very large. To deal with these large tables, Sumo is doing some data transformation. In short, we flip the data from being realization-oriented, to being vector-oriented.\n",
350
+ "\n",
351
+ "<div class=\"alert alert-block alert-info\">\n",
352
+ " Refer to the <a href=\"https://doc-sumo-doc-prod.radix.equinor.com/\">Sumo documentation</a> for more information about data transformation of tables.</div>\n",
353
+ "\n",
354
+ "In this example, we will use the same ensemble as before. We will first find a summary file for a specific realization, and cast that to a Pandas dataframe.\n",
355
+ "\n",
356
+ "### Getting a single realization of a table\n",
357
+ "Example: Inplace volumes"
358
+ ]
359
+ },
360
+ {
361
+ "cell_type": "code",
362
+ "execution_count": null,
363
+ "metadata": {},
364
+ "outputs": [],
365
+ "source": [
366
+ "mysinglevolumes = myensemble.tables.filter(tagname=\"vol\", name=\"geogrid\", realization=0)\n",
367
+ "if len(mysinglevolumes) != 1:\n",
368
+ " raise ValueError(f\"Got {len(mysinglevolumes)} which is not exactly 1.\")\n",
369
+ "print(\"Inplace volumes (geogrid) for realization 0:\")\n",
370
+ "df = mysinglevolumes[0].to_pandas()\n",
371
+ "df"
372
+ ]
373
+ },
374
+ {
375
+ "cell_type": "markdown",
376
+ "metadata": {},
377
+ "source": [
378
+ "Example: UNSMRY"
379
+ ]
380
+ },
381
+ {
382
+ "cell_type": "code",
383
+ "execution_count": null,
384
+ "metadata": {},
385
+ "outputs": [],
386
+ "source": [
387
+ "mysinglesummary = myensemble.tables.filter(tagname=\"summary\", realization=0)\n",
388
+ "if len(mysinglesummary) != 1:\n",
389
+ " raise ValueError(f\"Got {len(mysinglesummary)} which is not exactly 1.\")\n",
390
+ "print(\"Summary for realization 0:\")\n",
391
+ "df = mysinglesummary[0].to_pandas()\n",
392
+ "df"
393
+ ]
394
+ },
395
+ {
396
+ "cell_type": "markdown",
397
+ "metadata": {},
398
+ "source": [
399
+ "Commonly, however, we don't want the Summary for a single realization but rather a specific set of columns for the ensemble. It might be tempting to start looping through all realizations, but this is not recommended! Rather, you can call for some data transformation (we call this \"aggregation\" as well) to provide easy access to single columns across an ensemble of large tables:\n",
400
+ "\n",
401
+ "### Getting a column across many realizations"
402
+ ]
403
+ },
404
+ {
405
+ "cell_type": "code",
406
+ "execution_count": null,
407
+ "metadata": {},
408
+ "outputs": [],
409
+ "source": [
410
+ "df = myensemble.tables.filter(tagname=\"summary\").aggregation(operation=\"collection\", column=\"FOPT\").to_pandas()\n",
411
+ "print(\"FOPT across all realizations\")\n",
412
+ "df"
413
+ ]
414
+ },
415
+ {
416
+ "cell_type": "markdown",
417
+ "metadata": {},
418
+ "source": [
419
+ "Note that in addition to the \"FOPT\" column, you also get the **DATE** and **REAL** columns as categorical columns.\n",
420
+ "\n",
421
+ "Data transformations of large tables can be tedious. However, when data has been transformed, they are also stored in Sumo and you can access them quicker:"
422
+ ]
423
+ },
424
+ {
425
+ "cell_type": "code",
426
+ "execution_count": null,
427
+ "metadata": {},
428
+ "outputs": [],
429
+ "source": [
430
+ "myensemble.tables.filter(tagname=\"summary\", aggregation=\"collection\", column=\"FOPT\")[0].to_pandas()"
431
+ ]
432
+ }
433
+ ],
434
+ "metadata": {
435
+ "kernelspec": {
436
+ "display_name": "Python 3 (ipykernel)",
437
+ "language": "python",
438
+ "name": "python3"
439
+ },
440
+ "language_info": {
441
+ "codemirror_mode": {
442
+ "name": "ipython",
443
+ "version": 3
444
+ },
445
+ "file_extension": ".py",
446
+ "mimetype": "text/x-python",
447
+ "name": "python",
448
+ "nbconvert_exporter": "python",
449
+ "pygments_lexer": "ipython3",
450
+ "version": "3.12.10"
451
+ },
452
+ "vscode": {
453
+ "interpreter": {
454
+ "hash": "886a3bf2c68989186a10fcef5692724f98e2557aca959586571e913a0aec80e7"
455
+ }
456
+ }
457
+ },
458
+ "nbformat": 4,
459
+ "nbformat_minor": 4
460
+ }
@@ -55,7 +55,7 @@ platforms = ["any"]
55
55
  where = ["src"]
56
56
 
57
57
  [tool.ruff]
58
- exclude = [".env", ".git", ".github", ".venv", "venv"]
58
+ exclude = [".env", ".git", ".github", ".venv", "venv", "examples"]
59
59
 
60
60
  line-length = 79
61
61
 
@@ -1,7 +1,14 @@
1
1
  # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
3
 
4
- __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
5
12
 
6
13
  TYPE_CHECKING = False
7
14
  if TYPE_CHECKING:
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
9
16
  from typing import Union
10
17
 
11
18
  VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
12
20
  else:
13
21
  VERSION_TUPLE = object
22
+ COMMIT_ID = object
14
23
 
15
24
  version: str
16
25
  __version__: str
17
26
  __version_tuple__: VERSION_TUPLE
18
27
  version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
19
30
 
20
- __version__ = version = '2.4.10'
21
- __version_tuple__ = version_tuple = (2, 4, 10)
31
+ __version__ = version = '2.6.0'
32
+ __version_tuple__ = version_tuple = (2, 6, 0)
33
+
34
+ __commit_id__ = commit_id = 'g7c93d8bdc'
@@ -21,7 +21,7 @@ class Child(Document):
21
21
  self._sumo = sumo
22
22
  self._blob = blob
23
23
 
24
- def __repr__(self):
24
+ def __str__(self):
25
25
  if self.stage == "case" and self.__class__.__name__ != "Case":
26
26
  return (
27
27
  f"<{self.__class__.__name__}: {self.name} {self.uuid}(uuid) "
@@ -46,7 +46,10 @@ class Child(Document):
46
46
  f"in asset {self.asset}>"
47
47
  )
48
48
  else:
49
- return super().__repr__()
49
+ return super().__str__()
50
+
51
+ def __repr__(self):
52
+ return self.__str__()
50
53
 
51
54
  @property
52
55
  def blob(self) -> BytesIO:
@@ -1,6 +1,5 @@
1
1
  """Contains class for one document"""
2
2
 
3
- import json
4
3
  import re
5
4
  from typing import Any, Dict, List, Union
6
5
 
@@ -20,14 +19,14 @@ class Document:
20
19
  self._metadata = metadata["_source"]
21
20
 
22
21
  def __str__(self):
23
- return f"{json.dumps(self.metadata, indent=4)}"
24
-
25
- def __repr__(self):
26
22
  return (
27
23
  f"<{self.__class__.__name__}: {self.name} {self.uuid}(uuid) "
28
24
  f"in asset {self.asset}>"
29
25
  )
30
26
 
27
+ def __repr__(self):
28
+ return self.__str__()
29
+
31
30
  @property
32
31
  def uuid(self):
33
32
  """Return uuid
@@ -272,3 +272,56 @@ class Metrics:
272
272
  "percentiles", field=field, percents=percents
273
273
  )
274
274
  )["values"]
275
+
276
+ def _fnv1a_script(self, field):
277
+ return {
278
+ "init_script": """
279
+ state.h = state.count = state.total = 0L;
280
+ """,
281
+ "map_script": f"""
282
+ state.total++;
283
+ if (doc['{field}'].size() == 0) return;
284
+ def s = doc.get('{field}').value;
285
+ long h = -3750763034362895579L;
286
+ for (int i = 0; i < s.length(); i++) {{
287
+ h ^= (long) s.charAt(i);
288
+ h *= 1099511628211L;
289
+ }}
290
+ state.h ^= h;
291
+ state.count++;
292
+ """,
293
+ "combine_script": """
294
+ return state;
295
+ """,
296
+ "reduce_script": """
297
+ long h = 0, c = 0, t = 0;
298
+ for (st in states) {
299
+ h ^= st.h; c += st.count; t += st.total
300
+ }
301
+ return ['checksum': Long.toHexString(h), 'docs_in_checksum': c, 'docs_total': t];
302
+ """,
303
+ }
304
+
305
+ def fnv1a(self, field):
306
+ """Compute the 64-bit FNV-1a checksum for field over the current set of objects.
307
+
308
+ Arguments:
309
+ - field (str): the name of a property in the metadata.
310
+
311
+ Returns:
312
+ - a dict with the keys "docs_all", "docs_seen" and "xor_fnv64_hex".
313
+ """
314
+ return self._aggregate("scripted_metric", **self._fnv1a_script(field))
315
+
316
+ async def fnv1a_async(self, field):
317
+ """Compute the 64-bit FNV-1a checksum for field over the current set of objects.
318
+
319
+ Arguments:
320
+ - field (str): the name of a property in the metadata.
321
+
322
+ Returns:
323
+ - a dict with the keys "docs_all", "docs_seen" and "xor_fnv64_hex".
324
+ """
325
+ return await self._aggregate_async(
326
+ "scripted_metric", **self._fnv1a_script(field)
327
+ )