Skip to content

Commit

Permalink
Fix bubblepoint function and unit inconsistency in OLI API (#1388)
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-a-a authored Jun 10, 2024
1 parent ca5c6ec commit aefad78
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 64 deletions.
30 changes: 26 additions & 4 deletions watertap/tools/oli_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,17 @@ def generate_dbs_file(
dbs_file_inputs["modelName"] = "OLI_analysis"

# TODO: unknown bug where only "liquid1" phase is found in Flash analysis
valid_phases = ["liquid1", "vapor", "solid", "liquid2"]
self.valid_phases = ["liquid1", "vapor", "solid", "liquid2"]
if phases is not None:
invalid_phases = [p for p in phases if p not in valid_phases]
invalid_phases = [p for p in phases if p not in self.valid_phases]
if invalid_phases:
raise RuntimeError(
"Failed DBS file generation. "
+ f"Unexpected phase(s): {invalid_phases}"
)
dbs_file_inputs["phases"] = phases
else:
dbs_file_inputs["phases"] = ["liquid1", "solid"]
dbs_file_inputs["phases"] = ["liquid1", "solid", "vapor"]

valid_databanks = ["XSC"]
if databanks is not None:
Expand Down Expand Up @@ -333,7 +333,29 @@ def _get_flash_mode(self, dbs_file_id, flash_method, burst_job_tag=None):
headers = self.credential_manager.headers
base_url = self.credential_manager.engine_url
valid_get_flashes = ["corrosion-contact-surface", "chemistry-info"]
valid_post_flashes = ["isothermal", "corrosion-rates", "wateranalysis"]
valid_post_flashes = [
"isothermal",
"corrosion-rates",
"wateranalysis",
"bubblepoint",
]

if flash_method in [
"bubblepoint",
# TODO: uncomment the methods below only after trying and testing
# "dewpoint",
# "vapor-amount",
# "vapor-fraction",
# "isochoric",
]:
dbs_summary = self.get_dbs_file_summary(dbs_file_id)
phase_list = dbs_summary["chemistry_info"]["result"]["phases"]

if "vapor" not in phase_list:
raise RuntimeError(
"A vapor function ('{flash_method}') was called without included 'vapor' as a phase in the model"
)

if flash_method in valid_get_flashes:
mode = "GET"
url = f"{base_url}/file/{dbs_file_id}/{flash_method}"
Expand Down
32 changes: 32 additions & 0 deletions watertap/tools/oli_api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
CredentialManager,
cryptography_available,
)
import re


@pytest.fixture(scope="session")
Expand Down Expand Up @@ -102,6 +103,37 @@ def oliapi_instance(
cred_file_path.unlink()


@pytest.fixture(scope="function")
def oliapi_instance_with_invalid_phase(
tmp_path: Path,
auth_credentials: dict,
local_dbs_file: Path,
source_water: dict,
) -> OLIApi:

if not cryptography_available:
pytest.skip(reason="cryptography module not available.")
cred_file_path = tmp_path / "pytest-credentials.txt"

credentials = {
**auth_credentials,
"config_file": cred_file_path,
}
credential_manager = CredentialManager(**credentials, test=True)
with OLIApi(credential_manager, interactive_mode=False) as oliapi:
oliapi.upload_dbs_file(str(local_dbs_file))
with pytest.raises(
RuntimeError,
match=re.escape(
"Failed DBS file generation. Unexpected phase(s): ['invalid_phase']"
),
):
oliapi.generate_dbs_file(source_water, phases=["invalid_phase"])
yield oliapi
with contextlib.suppress(FileNotFoundError):
cred_file_path.unlink()


@pytest.fixture
def flash_instance(scope="session"):
flash = Flash()
Expand Down
31 changes: 28 additions & 3 deletions watertap/tools/oli_api/flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
# derivative works, incorporate into other computer software, distribute, and sublicense such enhancements
# or derivative works thereof, in binary and source code form.
###############################################################################
__author__ = "Oluwamayowa Amusat, Alexander Dudchenko, Paul Vecchiarelli"
__author__ = "Oluwamayowa Amusat, Alexander Dudchenko, Paul Vecchiarelli, Adam Atia"


import logging
Expand All @@ -66,6 +66,8 @@
output_unit_set,
)

from numpy import reshape, sqrt

_logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
formatter = logging.Formatter(
Expand Down Expand Up @@ -469,6 +471,7 @@ def configure_flash_analysis(
"vapor-fraction",
"isochoric",
]:

if calculated_variable is not None:
if calculated_variable not in ["temperature", "pressure"]:
raise RuntimeError(
Expand Down Expand Up @@ -1062,6 +1065,7 @@ def _find_props(data, path=None):
raise RuntimeError(f"Unexpected type for data: {type(data)}")

def _get_nested_data(data, keys):

for key in keys:
data = data[key]
return data
Expand All @@ -1080,6 +1084,7 @@ def _extract_values(data, keys):
if "unit" in values:
unit = values["unit"] if values["unit"] else "dimensionless"
extracted_values.update({"units": unit})

elif all(k in values for k in ["found", "phase"]):
extracted_values = values
else:
Expand All @@ -1089,14 +1094,31 @@ def _extract_values(data, keys):
"units": unit,
"values": values["value"],
}
else:
elif "values" in values:
extracted_values = {
k: {
"units": unit,
"values": values["values"][k],
}
for k, v in values["values"].items()
}
elif "data" in values:
# intended for vaporDiffusivityMatrix
mat_dim = int(sqrt(len(values["data"])))
diffmat = reshape(values["data"], newshape=(mat_dim, mat_dim))

extracted_values = {
f'({values["speciesNames"][i]},{values["speciesNames"][j]})': {
"units": values["unit"],
"values": diffmat[i][j],
}
for i in range(len(diffmat))
for j in range(i, len(diffmat))
}
else:
raise NotImplementedError(
f"results structure not accounted for. results:\n{values}"
)
else:
raise RuntimeError(f"Unexpected type for data: {type(values)}")
return extracted_values
Expand Down Expand Up @@ -1126,7 +1148,10 @@ def _create_input_dict(props, result):
if isinstance(prop[-1], int):
prop_tag = _get_nested_data(result, prop)["name"]
else:
_logger.warning(f"Unexpected result in result")
_logger.warning(
f"Unexpected result:\n{result}\n\ninput_dict:\n{input_dict}"
)

label = f"{prop_tag}_{phase_tag}" if phase_tag else prop_tag
input_dict[k][label] = _extract_values(result, prop)
return input_dict
Expand Down
12 changes: 12 additions & 0 deletions watertap/tools/oli_api/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,15 @@ def test_get_dbs_file_summary(oliapi_instance: OLIApi, local_dbs_file: Path):
oliapi_instance.get_user_dbs_file_ids()
dbs_file_id = oliapi_instance.upload_dbs_file(local_dbs_file)
oliapi_instance.get_dbs_file_summary(dbs_file_id)


@pytest.mark.unit
def test_valid_phases(oliapi_instance: OLIApi):
valid_phases = ["liquid1", "vapor", "solid", "liquid2"]
for v in oliapi_instance.valid_phases:
assert v in valid_phases


@pytest.mark.unit
def test_invalid_phases(oliapi_instance_with_invalid_phase: OLIApi):
oliapi_instance_with_invalid_phase
30 changes: 30 additions & 0 deletions watertap/tools/oli_api/tests/test_flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,33 @@ def test_isothermal_flash_survey(
dbs_file_id,
isothermal_input,
)


@pytest.mark.unit
def test_bubble_point(
flash_instance: Flash, source_water: dict, oliapi_instance: OLIApi, tmp_path: Path
):
dbs_file_id = oliapi_instance.session_dbs_files[-1]

stream_input = flash_instance.configure_water_analysis(source_water)
inflows = flash_instance.get_apparent_species_from_true(
stream_input,
oliapi_instance,
dbs_file_id,
)
bubblepoint_input = flash_instance.configure_flash_analysis(
inflows=inflows,
flash_method="bubblepoint",
calculated_variable="pressure",
)

saturation_pressure = flash_instance.run_flash(
"bubblepoint",
oliapi_instance,
dbs_file_id,
bubblepoint_input,
)

pytest.approx(
saturation_pressure["result"]["calculatedVariables"]["values"][0], rel=1e-3
) == 32.04094
Loading

0 comments on commit aefad78

Please sign in to comment.