Skip to content

Commit

Permalink
Merge remote-tracking branch 'ert-storage/main' into merge-ert-storage
Browse files Browse the repository at this point in the history
  • Loading branch information
pinkwah committed Sep 22, 2023
2 parents 8a15332 + fe5ec40 commit aff25dc
Show file tree
Hide file tree
Showing 22 changed files with 459 additions and 74 deletions.
69 changes: 40 additions & 29 deletions src/ert/dark_storage/app.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
from ert_storage.app import JSONResponse
from ert_storage.app import app as ert_storage_app
from ert_storage.exceptions import ErtStorageError
from fastapi import FastAPI, Request, status
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.responses import HTMLResponse, RedirectResponse
import json
from enum import Enum
from typing import Any

from fastapi import FastAPI, Request, Response, status
from fastapi.responses import RedirectResponse

from ert.dark_storage.endpoints import router as endpoints_router
from ert.dark_storage.exceptions import ErtStorageError
from ert.shared import __version__


class JSONEncoder(json.JSONEncoder):
"""
Custom JSON encoder with support for Python 3.4 enums
"""

def default(self, obj: Any) -> Any:
if isinstance(obj, Enum):
return obj.name
return super().default(obj)


class JSONResponse(Response):
"""A replacement for Starlette's JSONResponse that permits NaNs."""

media_type = "application/json"

def render(self, content: Any) -> bytes:
return (
JSONEncoder(
ensure_ascii=False,
allow_nan=True,
indent=None,
separators=(",", ":"),
)
.encode(content)
.encode("utf-8")
)


app = FastAPI(
title=ert_storage_app.title,
version=ert_storage_app.version,
title="ERT Storage API (dark storage)",
version=__version__,
debug=True,
default_response_class=JSONResponse,
# Disable documentation so we can replace it with ERT Storage's later
openapi_url=None,
docs_url=None,
redoc_url=None,
)


Expand Down Expand Up @@ -51,23 +79,6 @@ async def not_implemented_handler(
return JSONResponse({}, status_code=status.HTTP_501_NOT_IMPLEMENTED)


@app.get("/openapi.json", include_in_schema=False)
async def get_openapi() -> JSONResponse:
return JSONResponse(ert_storage_app.openapi())


@app.get("/docs", include_in_schema=False)
async def get_swagger(req: Request) -> HTMLResponse:
return get_swagger_ui_html(
openapi_url="/openapi.json", title=f"{app.title} - Swagger UI"
)


@app.get("/redoc", include_in_schema=False)
async def get_redoc(req: Request) -> HTMLResponse:
return get_redoc_html(openapi_url="/openapi.json", title=f"{app.title} - Redoc")


@app.get("/")
async def root() -> RedirectResponse:
return RedirectResponse("/docs")
Expand Down
1 change: 1 addition & 0 deletions src/ert/dark_storage/compute/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .misfits import calculate_misfits_from_pandas

Check failure on line 1 in src/ert/dark_storage/compute/__init__.py

View workflow job for this annotation

GitHub Actions / annotate-python-linting

'.misfits.calculate_misfits_from_pandas' imported but unused
42 changes: 42 additions & 0 deletions src/ert/dark_storage/compute/misfits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Mapping, Sequence

import numpy as np
import pandas as pd

if TYPE_CHECKING:
import numpy.typing as npt


def _calculate_misfit(
obs_value: npt.NDArray[Any],
response_value: npt.NDArray[Any],
obs_std: npt.NDArray[Any],
) -> Sequence[float]:
difference = response_value - obs_value
misfit = (difference / obs_std) ** 2
return (misfit * np.sign(difference)).tolist()


def calculate_misfits_from_pandas(
reponses_dict: Mapping[int, pd.DataFrame],
observation: pd.DataFrame,
summary_misfits: bool = False,
) -> pd.DataFrame:
"""
Compute misfits from reponses_dict (real_id, values in dataframe)
and observation
"""
misfits_dict = {}
for realization_index in reponses_dict:
misfits_dict[realization_index] = _calculate_misfit(
observation["values"],
reponses_dict[realization_index].loc[:, observation.index].values.flatten(),

Check failure on line 35 in src/ert/dark_storage/compute/misfits.py

View workflow job for this annotation

GitHub Actions / annotate-python-linting

line too long (88 > 79 characters)
observation["errors"],
)

df = pd.DataFrame(data=misfits_dict, index=observation.index)
if summary_misfits:
df = pd.DataFrame([df.abs().sum(axis=0)], columns=df.columns, index=[0])

Check failure on line 41 in src/ert/dark_storage/compute/misfits.py

View workflow job for this annotation

GitHub Actions / annotate-python-linting

line too long (80 > 79 characters)
return df.T
4 changes: 2 additions & 2 deletions src/ert/dark_storage/endpoints/compute/misfits.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

import pandas as pd
from dateutil.parser import parse
from ert_storage import exceptions as exc
from ert_storage.compute import calculate_misfits_from_pandas
from fastapi import APIRouter, Depends, status
from fastapi.responses import Response

from ert.dark_storage import exceptions as exc
from ert.dark_storage.common import data_for_key, observations_for_obs_keys
from ert.dark_storage.compute import calculate_misfits_from_pandas
from ert.dark_storage.enkf import LibresFacade, get_res, get_storage
from ert.storage import StorageReader

Expand Down
2 changes: 1 addition & 1 deletion src/ert/dark_storage/endpoints/ensembles.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Any, Mapping
from uuid import UUID

from ert_storage import json_schema as js
from fastapi import APIRouter, Body, Depends

from ert.dark_storage import json_schema as js
from ert.dark_storage.common import ensemble_parameter_names, get_response_names

Check failure on line 7 in src/ert/dark_storage/endpoints/ensembles.py

View workflow job for this annotation

GitHub Actions / annotate-python-linting

line too long (80 > 79 characters)
from ert.dark_storage.enkf import LibresFacade, get_res, get_storage
from ert.storage import StorageAccessor
Expand Down
2 changes: 1 addition & 1 deletion src/ert/dark_storage/endpoints/experiments.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Any, List, Mapping
from uuid import UUID

from ert_storage import json_schema as js
from fastapi import APIRouter, Body, Depends

from ert.dark_storage import json_schema as js
from ert.dark_storage.enkf import LibresFacade, get_res, get_storage
from ert.shared.storage.extraction import create_priors
from ert.storage import StorageReader
Expand Down
2 changes: 1 addition & 1 deletion src/ert/dark_storage/endpoints/observations.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Any, List, Mapping
from uuid import UUID

from ert_storage import json_schema as js
from fastapi import APIRouter, Body, Depends

from ert.dark_storage import json_schema as js
from ert.dark_storage.enkf import LibresFacade, get_res
from ert.shared.storage.extraction import create_observations

Expand Down
2 changes: 1 addition & 1 deletion src/ert/dark_storage/endpoints/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
from uuid import UUID, uuid4

import pandas as pd
from ert_storage import json_schema as js
from fastapi import APIRouter, Body, Depends, File, Header, Request, UploadFile, status
from fastapi.responses import Response

from ert.dark_storage import json_schema as js
from ert.dark_storage.common import (
data_for_key,
ensemble_parameters,
Expand Down
2 changes: 1 addition & 1 deletion src/ert/dark_storage/endpoints/updates.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from uuid import UUID

from ert_storage import json_schema as js
from fastapi import APIRouter, Depends

from ert.dark_storage import json_schema as js
from ert.dark_storage.enkf import LibresFacade, get_res, reset_res

router = APIRouter(tags=["ensemble"])
Expand Down
2 changes: 1 addition & 1 deletion src/ert/dark_storage/enkf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import os
from typing import Optional

from ert_storage.security import security
from fastapi import Depends

from ert.config import ErtConfig
from ert.dark_storage.security import security
from ert.enkf_main import EnKFMain
from ert.libres_facade import LibresFacade
from ert.storage import StorageReader, open_storage
Expand Down
30 changes: 30 additions & 0 deletions src/ert/dark_storage/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Any

from fastapi import status


class ErtStorageError(RuntimeError):
"""
Base error class for all the rest of errors
"""

__status_code__ = status.HTTP_200_OK

def __init__(self, message: str, **kwargs: Any):
super().__init__(message, kwargs)


class NotFoundError(ErtStorageError):
__status_code__ = status.HTTP_404_NOT_FOUND


class ConflictError(ErtStorageError):
__status_code__ = status.HTTP_409_CONFLICT


class ExpectationError(ErtStorageError):
__status_code__ = status.HTTP_417_EXPECTATION_FAILED


class UnprocessableError(ErtStorageError):
__status_code__ = status.HTTP_422_UNPROCESSABLE_ENTITY
11 changes: 11 additions & 0 deletions src/ert/dark_storage/json_schema/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from .ensemble import EnsembleIn, EnsembleOut
from .experiment import ExperimentIn, ExperimentOut
from .observation import (
ObservationIn,
ObservationOut,
ObservationTransformationIn,
ObservationTransformationOut,
)
from .prior import Prior
from .record import RecordOut
from .update import UpdateIn, UpdateOut
37 changes: 37 additions & 0 deletions src/ert/dark_storage/json_schema/ensemble.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Any, List, Mapping, Optional
from uuid import UUID

from pydantic import BaseModel, Field, root_validator


class _Ensemble(BaseModel):
size: int
parameter_names: List[str]
response_names: List[str]
active_realizations: List[int] = []


class EnsembleIn(_Ensemble):
update_id: Optional[UUID] = None
userdata: Mapping[str, Any] = {}

@root_validator
def _check_names_no_overlap(cls, values: Mapping[str, Any]) -> Mapping[str, Any]:
"""
Verify that `parameter_names` and `response_names` don't overlap. Ie, no
record can be both a parameter and a response.
"""
if not set(values["parameter_names"]).isdisjoint(set(values["response_names"])):
raise ValueError("parameters and responses cannot have a name in common")
return values


class EnsembleOut(_Ensemble):
id: UUID
children: List[UUID] = Field(alias="child_ensemble_ids")
parent: Optional[UUID] = Field(alias="parent_ensemble_id")
experiment_id: Optional[UUID] = None
userdata: Mapping[str, Any]

class Config:
orm_mode = True
24 changes: 24 additions & 0 deletions src/ert/dark_storage/json_schema/experiment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import Any, Mapping, Sequence
from uuid import UUID

from pydantic import BaseModel

from .prior import Prior


class _Experiment(BaseModel):
name: str


class ExperimentIn(_Experiment):
priors: Mapping[str, Prior] = {}


class ExperimentOut(_Experiment):
id: UUID
ensemble_ids: Sequence[UUID]
priors: Mapping[str, Mapping[str, Any]]
userdata: Mapping[str, Any]

class Config:
orm_mode = True
43 changes: 43 additions & 0 deletions src/ert/dark_storage/json_schema/observation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Any, List, Mapping, Optional
from uuid import UUID

from pydantic import BaseModel


class _ObservationTransformation(BaseModel):
name: str
active: List[bool]
scale: List[float]
observation_id: UUID


class ObservationTransformationIn(_ObservationTransformation):
pass


class ObservationTransformationOut(_ObservationTransformation):
id: UUID

class Config:
orm_mode = True


class _Observation(BaseModel):
name: str
errors: List[float]
values: List[float]
x_axis: List[Any]
records: Optional[List[UUID]] = None


class ObservationIn(_Observation):
pass


class ObservationOut(_Observation):
id: UUID
transformation: Optional[ObservationTransformationOut] = None
userdata: Mapping[str, Any] = {}

class Config:
orm_mode = True
Loading

0 comments on commit aff25dc

Please sign in to comment.