diff --git a/examples/explorer.ipynb b/examples/explorer.ipynb index 2d4680ce..ec7ab59e 100644 --- a/examples/explorer.ipynb +++ b/examples/explorer.ipynb @@ -16,7 +16,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Initialize Explorer" + "### TMP prototyping Iteration and Realization virtual objects" ] }, { @@ -25,7 +25,44 @@ "metadata": {}, "outputs": [], "source": [ - "sumo = Explorer()" + "from fmu.sumo.explorer import Explorer\n", + "sumo = Explorer()\n", + "\n", + "# idea: From fmu-sumo, should be able to utilize the Realization and the\n", + "# Iteration concepts directly as objects. Since they don't exist as explicit\n", + "# objects in Sumo, they are virtualized. Idea is that they should look and feel\n", + "# as if they were there (similar to the Case object).\n", + "\n", + "# Pattern 1: Drilling down from the case\n", + "\n", + "mycase = sumo.get_case_by_uuid(\"1ec9bfb0-8d82-4924-a74f-0a0f25054395\")\n", + "\n", + "print(mycase.iterations) # iterations under this case\n", + "\n", + "# pick an iteration\n", + "myiteration = mycase.iterations[\"iter-0\"]\n", + "print(myiteration)\n", + "print(myiteration.uuid)\n", + "\n", + "# pick a realization\n", + "realizations = myiteration.realizations\n", + "print(realizations)\n", + "\n", + "# The indexing pattern below is too confusing. It looks like a list index,\n", + "# i.e. \"give me the 10th realization\" but in fact it is a dict, and we are\n", + "# asking for realization-10 specifically. I.e. if realization 10 is not there,\n", + "# it will give KeyError.\n", + "\n", + "myrealization = realizations[10]\n", + "print(myrealization)\n", + "print(myrealization.surfaces)\n", + "\n", + "\n", + "# Pattern 2: Direct initialization from uuid\n", + "from fmu.sumo.explorer.objects._virtual_objects import Realization, Iteration\n", + "mydirectrealization = Realization(sumo, uuid=\"bdc4deb7-c3d9-64e1-198a-6b89a03e116a\")\n", + "print(mydirectrealization.surfaces)\n", + "\n" ] }, { @@ -47,7 +84,7 @@ "cases = sumo.cases.filter(asset=myassetname)\n", "\n", "# Get available status filters\n", - "print(\"Statuses:\",cases.statuses)\n", + "print(\"Statuses:\", cases.statuses)\n", "\n", "# Filter on status\n", "cases = cases.filter(status=\"keep\")\n", @@ -89,6 +126,33 @@ "print(\"Tables:\", len(case.polygons))" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Find Iterations (ensembles) and Realizations\n", + "_Realization_ and _iteration_ (_ensemble_) are well known concepts in the FMU context, but not explicitly represented by objects in Sumo (metadata not created by `fmu-dataio`)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "surfs = case.surfaces\n", + "print(surfs.iterations)\n", + "\n", + "case.iterations" + ] + }, { "attachments": {}, "cell_type": "markdown", diff --git a/src/fmu/sumo/explorer/objects/_virtual_objects.py b/src/fmu/sumo/explorer/objects/_virtual_objects.py new file mode 100644 index 00000000..02a417b0 --- /dev/null +++ b/src/fmu/sumo/explorer/objects/_virtual_objects.py @@ -0,0 +1,191 @@ +"""Module containing class for virtual objects. + +Virtual objects are objects that exist conceptually, but not as specific +data objects in Sumo, such as Realization and Iteration (ensembles).""" + +from sumo.wrapper import SumoClient +from fmu.sumo.explorer.objects.surface_collection import SurfaceCollection +from fmu.sumo.explorer._utils import Utils +from fmu.sumo.explorer.pit import Pit + +# from fmu.sumo.explorer.objects.case import Case + + +class Iteration: + """Representation of the virtual Iteration object.""" + + def __init__( + self, + sumo: SumoClient, + uuid: str, + case=None, + name: str = None, + pit: Pit = None, + ) -> None: + + self._sumo = sumo + self._case = case + self._uuid = uuid + self._name = name + self._utils = Utils(sumo) + self._realizations = None + self._pit = pit + + def __repr__(self): + return str(self) + + def __str__(self): + """String representation of this instance.""" + return f"{self.name} ({self.uuid})" + + @property + def uuid(self): + """The fmu.iteration.uuid for this iteration.""" + return self._uuid + + @property + def name(self): + """The fmu.iteration.name for this iteration.""" + if self._name is None: + pass # TODO get name + return self._name + + @property + def case(self): + """Case instance representing the case that this iter belongs to.""" + if self._case is None: + pass # TODO get_case + return self._case + + @property + def realizations(self): + """The realizations of this iteration.""" + if self._realizations is None: + self._realizations = self._get_realizations() + return self._realizations + + def _get_realizations(self): + """Get all realizations of this iteration/ensemble.""" + + must = [ + { + "term": { + "_sumo.parent_object.keyword": self._case.uuid, + } + }, + { + "term": { + "fmu.iteration.uuid.keyword": self.uuid, + }, + }, + ] + + print("query realizations") + + query = { + "query": { + "match": { + "fmu.iteration.uuid": "447992a4-b9c4-8619-6992-5d7d65b73309" + } + }, + "aggs": { + "id": { + "terms": { + "field": "fmu.realization.id", + "size": 1000, # usually (!) less than 1000 + }, + "aggs": { + "uuid": { + "terms": { + "field": "fmu.realization.uuid.keyword", + "size": 10, # should be only one + } + }, + "name": { + "terms": { + "field": "fmu.realization.name.keyword", + "size": 10, # should be only one + } + }, + }, + } + }, + "size": 0, + } + + res = self._sumo.post("/search", json=query) + buckets = res.json()["aggregations"]["id"]["buckets"] + + realizations = {} + + for bucket in buckets: + _realization = Realization( + sumo=self._sumo, + id=bucket["key"], + name=bucket["name"]["buckets"][0]["key"], + uuid=bucket["uuid"]["buckets"][0]["key"], + case=self.case, + ) + realizations[_realization.id] = _realization + + return realizations + + +class Realization: + """Representation of the virtual Realization object.""" + + def __init__( + self, + sumo: SumoClient, + uuid: str, + id: str = None, + name: str = None, + pit: Pit = None, + case=None, + ) -> None: + self._sumo = sumo + self._uuid = uuid + self._id = id + self._name = name + self._pit = pit + self._case = case + + def __repr__(self): + return str(self) + + def __str__(self): + """String representation of this instance.""" + return f"Realization {self.id} ({self.uuid})" + + @property + def uuid(self): + """The fmu.realization.uuid for this realization.""" + return self._uuid + + @property + def id(self): + """The fmu.realization.id for this realization.""" + return self._id + + @property + def case(self): + """Case instance representing the case this realization belongs to.""" + # needed for the class-specific methods, e.g. .surfaces + if self._case is None: + pass # TODO get_case + return self._case + + @property + def name(self): + """The fmu.realization.name for this realization.""" + if self._name is None: + pass # TODO get_name + return self._name + + @property + def surfaces(self) -> SurfaceCollection: + """List of surfaces under this realization.""" + query = {"match": {"fmu.realization.uuid": self.uuid}} + return SurfaceCollection( + self._sumo, self._uuid, pit=self._pit, query=query + ) diff --git a/src/fmu/sumo/explorer/objects/case.py b/src/fmu/sumo/explorer/objects/case.py index 61f51cb5..b119b378 100644 --- a/src/fmu/sumo/explorer/objects/case.py +++ b/src/fmu/sumo/explorer/objects/case.py @@ -1,4 +1,5 @@ """Module containing case class""" + from typing import Dict, List from sumo.wrapper import SumoClient from fmu.sumo.explorer.objects._document import Document @@ -9,6 +10,7 @@ from fmu.sumo.explorer.objects.dictionary_collection import ( DictionaryCollection, ) +from fmu.sumo.explorer.objects._virtual_objects import Iteration from fmu.sumo.explorer._utils import Utils from fmu.sumo.explorer.pit import Pit @@ -16,8 +18,9 @@ class Case(Document): """Class for representing a case in Sumo""" - def __init__(self, sumo: SumoClient, metadata: Dict, overview: Dict, - pit: Pit = None): + def __init__( + self, sumo: SumoClient, metadata: Dict, overview: Dict, pit: Pit = None + ): super().__init__(metadata) self._overview = overview self._pit = pit @@ -88,17 +91,19 @@ def iterations(self) -> List[Dict]: res = self._sumo.post("/search", json=query) buckets = res.json()["aggregations"]["uuid"]["buckets"] - iterations = [] + iterations = {} for bucket in buckets: - iterations.append( - { - "uuid": bucket["key"], - "name": bucket["name"]["buckets"][0]["key"], - "realizations": bucket["realizations"]["value"], - } + _iteration = Iteration( + sumo=self._sumo, + case=self, + uuid=bucket["key"], + name=bucket["name"]["buckets"][0]["key"], + pit=self._pit, ) + iterations[_iteration.name] = _iteration + self._iterations = iterations return self._iterations @@ -133,17 +138,25 @@ async def iterations_async(self) -> List[Dict]: res = await self._sumo.post_async("/search", json=query) buckets = res.json()["aggregations"]["id"]["buckets"] - iterations = [] + iterations = {} for bucket in buckets: - iterations.append( - { - "id": bucket["key"], - "name": bucket["name"]["buckets"][0]["key"], - "realizations": len(bucket["realizations"]["buckets"]), - } + _iteration = Iteration( + sumo=self._sumo, + case=self, + uuid=bucket["key"], + name=bucket["name"]["buckets"][0]["key"], ) + iterations[_iteration.name] = _iteration + # iterations.append( + # { + # "id": bucket["key"], + # "name": bucket["name"]["buckets"][0]["key"], + # "realizations": len(bucket["realizations"]["buckets"]), + # } + # ) + self._iterations = iterations return self._iterations