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

Fix bubblepoint function and unit inconsistency in OLI API #1388

Merged
merged 20 commits into from
Jun 10, 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
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"]
Copy link
Contributor

@OOAmusat OOAmusat May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adam-a-a Is there a reason why we do not have "liquid2" here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, because we haven't experimented with it enough and I didn't want to open up more cans of worms until we get the more fundamental bits of code working, I think it is safer to add liquid2 as part of the default in a later PR, which would need to include some thorough testing to see how various OLI functions may or may not be impacted.


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
Loading