Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for grids and grid properties #355

Merged
merged 5 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/fmu/sumo/explorer/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ def put(self, key, value):

def has(self, key):
return key in self.cache

def clear(self):
with self.lock:
self.cache.clear()
self.access.clear()
2 changes: 2 additions & 0 deletions src/fmu/sumo/explorer/objects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from fmu.sumo.explorer.objects.surface import Surface
from fmu.sumo.explorer.objects.polygons import Polygons
from fmu.sumo.explorer.objects.table import Table
from fmu.sumo.explorer.objects.cpgrid import CPGrid
from fmu.sumo.explorer.objects.cpgrid_property import CPGridProperty
from fmu.sumo.explorer.objects.iteration import Iteration
from fmu.sumo.explorer.objects.iterations import Iterations
from fmu.sumo.explorer.objects.realization import Realization
Expand Down
127 changes: 90 additions & 37 deletions src/fmu/sumo/explorer/objects/_search_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@
from fmu.sumo.explorer.cache import LRUCache


_CASE_FIELDS = {"include": [], "exclude": []}

_CHILD_FIELDS = {
"include": [],
"exclude": ["data.spec.columns", "fmu.realization.parameters"],
}


def _gen_filter_none():
def _fn(value):
return None, None
Expand Down Expand Up @@ -289,6 +281,9 @@ def __init__(
self._hits = None
self._cache = LRUCache(capacity=200)
self._length = None
self._select = {
"excludes": ["fmu.realization.parameters"],
}
return

@property
Expand Down Expand Up @@ -324,10 +319,12 @@ def _to_sumo(self, obj, blob=None):
"polygons": objects.Polygons,
"surface": objects.Surface,
"table": objects.Table,
"cpgrid": objects.CPGrid,
"cpgrid_property": objects.CPGridProperty
}.get(cls)
if constructor is None:
warnings.warn(f"No constructor for class {cls}")
constructor = objects.Child
warnings.warn(f"No constructor for class {cls}")
constructor = objects.Child
return constructor(self._sumo, obj, blob)

def __len__(self):
Expand Down Expand Up @@ -490,7 +487,44 @@ async def getitem_async(self, index):
uuid = self._hits[index]
return await self.get_object_async(uuid)

def get_object(self, uuid: str, select: List[str] = None) -> Dict:
def select(self, sel):
"""Specify what should be returned from elasticsearch.
Has the side effect of clearing the lru cache.
sel is either a single string value, a list of string value,
or a dictionary with keys "includes" and/or "excludes" and
the values are lists of strings. The string values are nested
property names.

Args:
sel (str | List(str) | Dict(str, List[str]): select specification
Returns:
None
"""

required = set(["class"])
def extreq(lst):
if isinstance(lst, str):
lst = [lst]
return list(set(lst) | required)
if isinstance(sel, str):
self._select = extreq([sel])
elif isinstance(sel, list):
self._select = extreq(sel)
elif isinstance(sel, dict):
inc = sel.get("includes")
exc = sel.get("excludes")
slct = {}
if inc is not None:
slct["includes"] = extreq(inc)
pass
if exc is not None:
slct["excludes"] = exc
pass
self._select = slct
pass
self._cache.clear()

def get_object(self, uuid: str) -> Dict:
"""Get metadata object by uuid

Args:
Expand All @@ -505,11 +539,9 @@ def get_object(self, uuid: str, select: List[str] = None) -> Dict:
query = {
"query": {"ids": {"values": [uuid]}},
"size": 1,
"_source": self._select
}

if select is not None:
query["_source"] = select

res = self._sumo.post("/search", json=query)
hits = res.json()["hits"]["hits"]

Expand All @@ -520,9 +552,7 @@ def get_object(self, uuid: str, select: List[str] = None) -> Dict:

return self._to_sumo(obj)

async def get_object_async(
self, uuid: str, select: List[str] = None
) -> Dict:
async def get_object_async(self, uuid: str) -> Dict:
"""Get metadata object by uuid

Args:
Expand All @@ -538,11 +568,9 @@ async def get_object_async(
query = {
"query": {"ids": {"values": [uuid]}},
"size": 1,
"_source": self._select
}

if select is not None:
query["_source"] = select

res = await self._sumo.post_async("/search", json=query)
hits = res.json()["hits"]["hits"]

Expand All @@ -557,13 +585,11 @@ def _maybe_prefetch(self, index):
uuid = self._hits[index]
if self._cache.has(uuid):
return
uuids = self._hits[index : min(index + 100, len(self._hits))]
uuids = self._hits[index:min(index + 100, len(self._hits))]
uuids = [uuid for uuid in uuids if not self._cache.has(uuid)]
hits = self.__search_all(
{"ids": {"values": uuids}},
select={
"excludes": ["fmu.realization.parameters"],
},
select=self._select,
)
if len(hits) == 0:
return
Expand All @@ -575,13 +601,11 @@ async def _maybe_prefetch_async(self, index):
uuid = self._hits[index]
if self._cache.has(uuid):
return
uuids = self._hits[index : min(index + 100, len(self._hits))]
uuids = self._hits[index:min(index + 100, len(self._hits))]
uuids = [uuid for uuid in uuids if not self._cache.has(uuid)]
hits = await self.__search_all_async(
{"ids": {"values": uuids}},
select={
"excludes": ["fmu.realization.parameters"],
},
select=self._select,
)
if len(hits) == 0:
return
Expand Down Expand Up @@ -809,17 +833,38 @@ def all(self):
@property
def cases(self):
"""Cases from current selection."""
return objects.Cases(self)
uuids = self._get_field_values("fmu.case.uuid.keyword")
return objects.Cases(self, uuids)

@property
async def cases_async(self):
"""Cases from current selection."""
uuids = await self._get_field_values_async("fmu.case.uuid.keyword")
return objects.Cases(self, uuids)

@property
def iterations(self):
"""Iterations from current selection."""
return objects.Iterations(self)
uuids = self._get_field_values("fmu.iteration.uuid.keyword")
return objects.Iterations(self, uuids)

@property
async def iterations_async(self):
"""Iterations from current selection."""
uuids = await self._get_field_values_async("fmu.iteration.uuid.keyword")
return objects.Iterations(self, uuids)

@property
def realizations(self):
"""Realizations from current selection."""
return objects.Realizations(self)
uuids = self._get_field_values("fmu.realization.uuid.keyword")
return objects.Realizations(self, uuids)

@property
async def realizations_async(self):
"""Realizations from current selection."""
uuids = await self._get_field_values_async("fmu.realization.uuid.keyword")
return objects.Realizations(self, uuids)

@property
def template_paths(sc):
Expand Down Expand Up @@ -958,6 +1003,14 @@ def polygons(self):
def dictionaries(self):
return self._context_for_class("dictionary")

@property
def grids(self):
return self._context_for_class("cpgrid")

@property
def grid_properties(self):
return self._context_for_class("cpgrid_property")

def _get_object_by_class_and_uuid(self, cls, uuid):
obj = self.get_object(uuid)
if obj.metadata["class"] != cls:
Expand Down Expand Up @@ -1100,7 +1153,7 @@ def get_surface_by_uuid(self, uuid: str):
Returns:
Surface: surface object
"""
metadata = self.get_object(uuid, _CHILD_FIELDS)
metadata = self.get_object(uuid)
return objects.Surface(self._sumo, metadata)

async def get_surface_by_uuid_async(self, uuid: str):
Expand All @@ -1112,7 +1165,7 @@ async def get_surface_by_uuid_async(self, uuid: str):
Returns:
Surface: surface object
"""
metadata = await self.get_object_async(uuid, _CHILD_FIELDS)
metadata = await self.get_object_async(uuid)
return objects.Surface(self._sumo, metadata)

def get_polygons_by_uuid(self, uuid: str):
Expand All @@ -1124,7 +1177,7 @@ def get_polygons_by_uuid(self, uuid: str):
Returns:
Polygons: polygons object
"""
metadata = self.get_object(uuid, _CHILD_FIELDS)
metadata = self.get_object(uuid)
return objects.Polygons(self._sumo, metadata)

async def get_polygons_by_uuid_async(self, uuid: str):
Expand All @@ -1136,7 +1189,7 @@ async def get_polygons_by_uuid_async(self, uuid: str):
Returns:
Polygons: polygons object
"""
metadata = await self.get_object_async(uuid, _CHILD_FIELDS)
metadata = await self.get_object_async(uuid)
return objects.Polygons(self._sumo, metadata)

def get_table_by_uuid(self, uuid: str):
Expand All @@ -1148,7 +1201,7 @@ def get_table_by_uuid(self, uuid: str):
Returns:
Table: table object
"""
metadata = self.get_object(uuid, _CHILD_FIELDS)
metadata = self.get_object(uuid)
return objects.Table(self._sumo, metadata)

async def get_table_by_uuid_async(self, uuid: str):
Expand All @@ -1160,7 +1213,7 @@ async def get_table_by_uuid_async(self, uuid: str):
Returns:
Table: table object
"""
metadata = await self.get_object_async(uuid, _CHILD_FIELDS)
metadata = await self.get_object_async(uuid)
return objects.Table(self._sumo, metadata)

def _verify_aggregation_operation(self):
Expand Down
42 changes: 8 additions & 34 deletions src/fmu/sumo/explorer/objects/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,19 @@
from fmu.sumo.explorer.objects._search_context import SearchContext

class Cases(SearchContext):
def __init__(self, sc):
super().__init__(sc._sumo, sc._must, sc._must_not)
def __init__(self, sc, uuids):
super().__init__(sc._sumo, must=[{"terms": {"fmu.case.uuid.keyword": uuids}}])
self._hits = uuids
return

def __len__(self):
if self._length is None:
if self._hits is None:
self._hits = self._search_all(select=False)
pass
self._length = len(self._hits)
pass
return self._length

async def length_async(self):
if self._length is None:
if self._hits is None:
self._hits = self._search_all(select=False)
pass
self._length = len(self._hits)
pass
return self._length

def _search_all(self, select=False):
uuids = self._get_field_values("fmu.case.uuid.keyword")
if select is False:
return uuids
# ELSE
return SearchContext(must=[{"ids": {"values": uuids}}])._search_all(select=select)

async def _search_all_async(self, select=False):
uuids = await self._get_field_values_async("fmu.case.uuid.keyword")
if select is False:
return uuids
# ELSE
return await SearchContext(must=[{"ids": {"values": uuids}}])._search_all_async(select=select)

def _maybe_prefetch(self, index):
return

async def _maybe_prefetch_async(self, index):
return

def filter(self, **kwargs):
sc = super().filter(**kwargs)
uuids = sc.uuids
return Cases(self, uuids)

46 changes: 46 additions & 0 deletions src/fmu/sumo/explorer/objects/cpgrid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Module containing class for cpgrid"""

from typing import Dict
from sumo.wrapper import SumoClient
from fmu.sumo.explorer.objects._child import Child

class CPGrid(Child):
"""Class representing a cpgrid object in Sumo."""

def __init__(self, sumo: SumoClient, metadata: Dict, blob=None) -> None:
"""
Args:
sumo (SumoClient): connection to Sumo
metadata (dict): dictionary metadata
blob: data object
"""
super().__init__(sumo, metadata, blob)

def to_cpgrid(self):
"""Get cpgrid object as a Grid
Returns:
Grid: A Grid object
"""
try:
from xtgeo import grid_from_file
except ModuleNotFoundError:
raise RuntimeError("Unable to import xtgeo; probably not installed.")
try:
return grid_from_file(self.blob)
except TypeError as type_err:
raise TypeError(f"Unknown format: {self.format}") from type_err

async def to_cpgrid_async(self):
"""Get cpgrid object as a Grid
Returns:
Grid: A Grid object
"""
try:
from xtgeo import grid_from_file
except ModuleNotFoundError:
raise RuntimeError("Unable to import xtgeo; probably not installed.")

try:
return grid_from_file(await self.blob_async)
except TypeError as type_err:
raise TypeError(f"Unknown format: {self.format}") from type_err
Loading