Skip to content
Closed
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
2 changes: 2 additions & 0 deletions backend_py/primary/primary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from primary.routers.parameters.router import router as parameters_router
from primary.routers.polygons.router import router as polygons_router
from primary.routers.pvt.router import router as pvt_router
from primary.routers.relperm.router import router as relperm_router
from primary.routers.rft.router import router as rft_router
from primary.routers.seismic.router import router as seismic_router
from primary.routers.surface.router import router as surface_router
Expand Down Expand Up @@ -97,6 +98,7 @@ async def shutdown_event_async() -> None:
app.include_router(grid3d_router, prefix="/grid3d", tags=["grid3d"])
app.include_router(flow_network_router, prefix="/flow_network", tags=["flow_network"])
app.include_router(pvt_router, prefix="/pvt", tags=["pvt"])
app.include_router(relperm_router, prefix="/relperm", tags=["relperm"])
app.include_router(well_completions_router, prefix="/well_completions", tags=["well_completions"])
app.include_router(well_router, prefix="/well", tags=["well"])
app.include_router(seismic_router, prefix="/seismic", tags=["seismic"])
Expand Down
47 changes: 47 additions & 0 deletions backend_py/primary/primary/routers/relperm/converters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from primary.services.relperm_assembler.relperm_assembler import (
RelPermTableInfo,
RelPermSaturationAxis,
RelPermRealizationData,
CurveData,
)

from . import schemas


def to_api_relperm_table_info(table_info: RelPermTableInfo) -> schemas.RelPermTableInfo:

return schemas.RelPermTableInfo(
table_name=table_info.table_name,
saturation_axes=[to_api_relperm_saturation_axis(axis) for axis in table_info.saturation_axes],
satnums=table_info.satnums,
)


def to_api_relperm_saturation_axis(axis: RelPermSaturationAxis) -> schemas.RelPermSaturationAxis:

return schemas.RelPermSaturationAxis(
saturation_name=axis.saturation_name,
relperm_curve_names=axis.relperm_curve_names,
capillary_pressure_curve_names=axis.capillary_pressure_curve_names,
)


def to_api_curve_data(data: CurveData) -> schemas.CurveData:

return schemas.CurveData(
curve_name=data.curve_name,
curve_values=data.curve_values.tolist(),
)


def to_api_relperm_realization_data(
data: RelPermRealizationData,
) -> schemas.RelPermRealizationData:

return schemas.RelPermRealizationData(
curve_data_arr=[to_api_curve_data(curve_data) for curve_data in data.curve_data_arr],
realization_id=data.realization_id,
saturation_name=data.saturation_name,
saturation_values=data.saturation_values.tolist(),
saturation_number=data.saturation_number,
)
70 changes: 70 additions & 0 deletions backend_py/primary/primary/routers/relperm/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logging
from typing import Annotated, List

from fastapi import APIRouter, Depends, Query

from primary.auth.auth_helper import AuthHelper
from primary.services.sumo_access.relperm_access import RelPermAccess
from primary.services.relperm_assembler.relperm_assembler import RelPermAssembler
from primary.services.utils.authenticated_user import AuthenticatedUser
from primary.utils.query_string_utils import decode_uint_list_str

from . import schemas
from . import converters

LOGGER = logging.getLogger(__name__)

router = APIRouter()


@router.get("/relperm_table_names")
async def get_relperm_table_names(
authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)],
case_uuid: Annotated[str, Query(description="Sumo case uuid")],
ensemble_name: Annotated[str, Query(description="Ensemble name")],
) -> List[str]:
access = RelPermAccess.from_iteration_name(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
return await access.get_relperm_table_names_async()


@router.get("/relperm_table_info")
async def get_relperm_table_info(
authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)],
case_uuid: Annotated[str, Query(description="Sumo case uuid")],
ensemble_name: Annotated[str, Query(description="Ensemble name")],
table_name: Annotated[str, Query(description="Table name")],
) -> schemas.RelPermTableInfo:
access = RelPermAccess.from_iteration_name(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
assembler = RelPermAssembler(access)
relperm_table_info = await assembler.get_relperm_table_info_async(table_name)

return converters.to_api_relperm_table_info(relperm_table_info)


@router.get("/relperm_realizations_curve_data")
async def get_relperm_realizations_curve_data(
authenticated_user: Annotated[AuthenticatedUser, Depends(AuthHelper.get_authenticated_user)],
case_uuid: Annotated[str, Query(description="Sumo case uuid")],
ensemble_name: Annotated[str, Query(description="Ensemble name")],
table_name: Annotated[str, Query(description="Table name")],
saturation_axis_name: Annotated[str, Query(description="Saturation axis name")],
curve_names: Annotated[List[str], Query(description="Curve names")],
satnum: Annotated[int, Query(description="Satnum")],
realizations_encoded_as_uint_list_str: Annotated[
str | None,
Query(
description="Optional list of realizations encoded as string to include. If not specified, all realizations will be included."
),
] = None,
) -> List[schemas.RelPermRealizationData]:
realizations: list[int] | None = None
if realizations_encoded_as_uint_list_str:
realizations = decode_uint_list_str(realizations_encoded_as_uint_list_str)

access = RelPermAccess.from_iteration_name(authenticated_user.get_sumo_access_token(), case_uuid, ensemble_name)
assembler = RelPermAssembler(access)
relperm_data = await assembler.get_relperm_realization_data_async(
table_name, saturation_axis_name, curve_names, satnum, realizations
)

return [converters.to_api_relperm_realization_data(data) for data in relperm_data]
29 changes: 29 additions & 0 deletions backend_py/primary/primary/routers/relperm/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from enum import StrEnum
from typing import List, Dict

from pydantic import BaseModel


class RelPermSaturationAxis(BaseModel):
saturation_name: str
relperm_curve_names: List[str]
capillary_pressure_curve_names: List[str]


class RelPermTableInfo(BaseModel):
table_name: str
saturation_axes: List[RelPermSaturationAxis]
satnums: List[int]


class CurveData(BaseModel):
curve_name: str
curve_values: List[float]


class RelPermRealizationData(BaseModel):
curve_data_arr: List[CurveData]
realization_id: int
saturation_name: str
saturation_values: List[float]
saturation_number: int
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
from enum import Enum
from typing import List, Optional, Sequence
import logging
from dataclasses import dataclass
import numpy as np
import polars as pl
from primary.services.sumo_access.relperm_access import RelPermAccess
from primary.services.service_exceptions import (
Service,
NoDataError,
InvalidDataError,
)

LOGGER = logging.getLogger(__name__)


class RelPermFamily(str, Enum):
"""Enumeration of relative permeability keyword families"""

FAMILY_1 = "family_1" # SWOF, SGOF, SLGOF family
FAMILY_2 = "family_2" # SWFN, SGFN, SOF3 family


RELPERM_FAMILIES = {
1: ["SWOF", "SGOF", "SLGOF"],
2: ["SWFN", "SGFN", "SOF3"],
}


@dataclass
class RelPermSaturationAxis:
saturation_name: str
relperm_curve_names: List[str]
capillary_pressure_curve_names: List[str]


@dataclass
class RelPermTableInfo:
table_name: str
saturation_axes: List[RelPermSaturationAxis]
satnums: List[int]


@dataclass
class CurveData:
curve_name: str
curve_values: np.ndarray


@dataclass
class RelPermRealizationData:
curve_data_arr: List[CurveData]
realization_id: int
saturation_name: str
saturation_values: np.ndarray
saturation_number: int


class RelPermAssembler:
def __init__(self, relperm_access: RelPermAccess):
self._relperm_access = relperm_access

async def get_relperm_table_info_async(self, relperm_table_name: str) -> RelPermTableInfo:
single_realization_table = await self._relperm_access.get_single_realization_table_async(relperm_table_name)
table_columns = single_realization_table.columns
satnums = extract_satnums_from_relperm_table(single_realization_table)
all_keywords = extract_keywords_from_relperm_table(single_realization_table)
family = extract_familiy_info_from_keywords(all_keywords)
saturation_infos = extract_saturation_axes_from_relperm_table(table_columns, family)

return RelPermTableInfo(
table_name=relperm_table_name, saturation_axes=saturation_infos, satnums=sorted(satnums)
)

async def get_relperm_realization_data_async(
self,
relperm_table_name: str,
saturation_axis_name: str,
curve_names: List[str],
satnum: int,
realizations: Optional[Sequence[int]],
) -> List[RelPermRealizationData]:

realizations_table: pl.DataFrame = await self._relperm_access.get_relperm_table_async(
relperm_table_name, realizations
)

table_columns = realizations_table.columns

if saturation_axis_name not in table_columns:
raise NoDataError(
f"Saturation axis {saturation_axis_name} not found in table {relperm_table_name}",
Service.GENERAL,
)

for curve_name in curve_names:
if curve_name not in table_columns:
raise NoDataError(
f"Curve {curve_name} not found in saturation axis {saturation_axis_name} in table {relperm_table_name}",
Service.GENERAL,
)

columns_to_use = [saturation_axis_name] + curve_names + ["REAL", "SATNUM"]

filtered_table = (
realizations_table.select(columns_to_use)
.filter((realizations_table["SATNUM"].cast(pl.Int32) == satnum))
.drop_nulls()
.sort(saturation_axis_name)
)

real_data: List[RelPermRealizationData] = []

for _real, real_table in filtered_table.group_by("REAL"):

curve_data_arr: List[CurveData] = []
for curve_name in curve_names:
curve_values = real_table[curve_name].to_numpy()
curve_data_arr.append(CurveData(curve_name=curve_name, curve_values=curve_values))

realization = real_table["REAL"][0]
saturation_values = real_table[saturation_axis_name].to_numpy()

real_data.append(
RelPermRealizationData(
curve_data_arr=curve_data_arr,
saturation_name=saturation_axis_name,
saturation_values=saturation_values,
realization_id=realization,
saturation_number=satnum,
)
)

return real_data


def extract_keywords_from_relperm_table(relperm_table: pl.DataFrame) -> List[str]:
return relperm_table["KEYWORD"].unique().to_list()


def extract_satnums_from_relperm_table(relperm_table: pl.DataFrame) -> List[int]:
return relperm_table["SATNUM"].cast(pl.Int32).unique().to_list()


def extract_familiy_info_from_keywords(keywords: List[str]) -> RelPermFamily:

if any(keyword in RELPERM_FAMILIES[1] for keyword in keywords):
if any(keyword in RELPERM_FAMILIES[2] for keyword in keywords):
raise InvalidDataError(
"Mix of keyword family 1 and 2, currently only support one family at this time.",
Service.GENERAL,
)
return RelPermFamily.FAMILY_1

elif not all(keyword in RELPERM_FAMILIES[2] for keyword in keywords):
raise InvalidDataError(
"Unrecognized saturation table keyword in data. This should not occur unless "
"there has been changes to res2df. Update of this plugin might be required.",
Service.GENERAL,
)
else:
return RelPermFamily.FAMILY_2


def extract_saturation_axes_from_relperm_table(
relperm_table_columns: List[str], relperm_family: RelPermFamily
) -> List[RelPermSaturationAxis]:
saturation_infos = []
if relperm_family == RelPermFamily.FAMILY_1:
if "SW" in relperm_table_columns:
saturation_infos.append(
RelPermSaturationAxis(
saturation_name="SW",
relperm_curve_names=[
curve_name for curve_name in ["KROW", "KRW"] if curve_name in relperm_table_columns
],
capillary_pressure_curve_names=[
curve_name for curve_name in ["PCOW"] if curve_name in relperm_table_columns
],
)
)
if "SG" in relperm_table_columns:
saturation_infos.append(
RelPermSaturationAxis(
saturation_name="SG",
relperm_curve_names=[
curve_name for curve_name in ["KRG", "KROG"] if curve_name in relperm_table_columns
],
capillary_pressure_curve_names=[
curve_name for curve_name in ["PCOG"] if curve_name in relperm_table_columns
],
)
)

if relperm_family == RelPermFamily.FAMILY_2:
if "SW" in relperm_table_columns:
saturation_infos.append(
RelPermSaturationAxis(
saturation_name="SW",
relperm_curve_names=[curve_name for curve_name in ["KRW"] if curve_name in relperm_table_columns],
capillary_pressure_curve_names=[
curve_name for curve_name in ["PCOW"] if curve_name in relperm_table_columns
],
)
)
if "SG" in relperm_table_columns:
saturation_infos.append(
RelPermSaturationAxis(
saturation_name="SG",
relperm_curve_names=[curve_name for curve_name in ["KRG"] if curve_name in relperm_table_columns],
capillary_pressure_curve_names=[
curve_name for curve_name in ["PCOG"] if curve_name in relperm_table_columns
],
)
)
if "SO" in relperm_table_columns:
saturation_infos.append(
RelPermSaturationAxis(
saturation_name="SO",
relperm_curve_names=[
curve_name for curve_name in ["KROW", "KROG"] if curve_name in relperm_table_columns
],
capillary_pressure_curve_names=[],
)
)
return saturation_infos
Loading
Loading