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

Conversation

adam-a-a
Copy link
Contributor

@adam-a-a adam-a-a commented May 14, 2024

Fixes/Resolves:

Summary/Motivation:

In an attempt to call the bubblepoint function, I discovered that it doesn't work as intended and the user would not be able to run the function. Along the way, I also noticed that input and/or output units provided for OLI properties have oli_unit and pyomo_unit attributes, and when one sets one or the other, there would be inconsistencies between the two (see #1369).

Changes proposed in this PR:

  • add vapor phase as requirement for bubblepoint func to run
  • add bubblepoint to list of post flash methods that are available
  • deal with flatten_results for unanticipated vaporDiffusivityMatrix, which breaks the logic of flatten_results
  • refine FixedKeysDict so that when the user sets either pyomo_unit or oli_unit, the other will automatically update via __setitem__ and using nested FixedKeysDicts
  • add tests for new FixedKeysDict functionality
  • add test for bubblepoint
  • add test for valid_phases

Legal Acknowledgement

By contributing to this software project, I agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the license terms described in the LICENSE.txt file at the top level of this directory.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

…itymatrix results; reminder to check diff and remove debugging bits and finalized fixedkeysdict changes
… bubblepoint; test fixedkeysdict funcitonality with resolved units issue
Copy link

codecov bot commented May 14, 2024

Codecov Report

Attention: Patch coverage is 83.82353% with 11 lines in your changes are missing coverage. Please review.

Project coverage is 95.23%. Comparing base (21c209b) to head (b5bcc5c).

Files Patch % Lines
watertap/tools/oli_api/util/fixed_keys_dict.py 80.00% 7 Missing ⚠️
watertap/tools/oli_api/flash.py 77.77% 2 Missing ⚠️
watertap/tools/oli_api/client.py 88.88% 1 Missing ⚠️
watertap/tools/oli_api/conftest.py 93.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1388      +/-   ##
==========================================
- Coverage   95.25%   95.23%   -0.02%     
==========================================
  Files         335      335              
  Lines       35727    35621     -106     
==========================================
- Hits        34033    33925     -108     
- Misses       1694     1696       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@adam-a-a adam-a-a self-assigned this May 14, 2024
@adam-a-a adam-a-a added the oli label May 14, 2024
Copy link
Contributor

@bknueven bknueven left a comment

Choose a reason for hiding this comment

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

I am not sure about the automatic units conversion piece of this.

In the original conception, I imaged using the "pyomo_unit" attribute so the user could convert whatever input they had to units which will work with OLI by using the existing units conversion in Pyomo, and then back to those units after OLI returns the data.

It could be the answer is to restructure this so that the "pyomo_unit" and "oli_unit" are read-only attributes so as not to confuse the user.

@@ -25,7 +27,48 @@ def __setitem__(self, k, v):
raise RuntimeError(f" Key {k} not in dictionary.")
# also check for valid value if a list of values is given in the default dictionary
else:
self.data[k] = v
# if user setting value in pyomo units
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should have a sub-class of FixedKeysDict just for the units?

"oli_unit": "Pa-s",
"pyomo_unit": pyunits.Pa * pyunits.second,
},
"alkalinity": {"oli_unit": "mg HCO3/L", "pyomo_unit": pyunits.mg / pyunits.L},
Copy link
Contributor

Choose a reason for hiding this comment

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

This example, among others, is why Paul and I gave up on an automatic approach. It seemed like you could be working forever on every edge case and still miss some.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right- we'd need to set valid units accepted for each property, which is documented. We may catch bugs along the way (i.e., what's documented may not match reality in some cases).

@adam-a-a
Copy link
Contributor Author

So, I am not completely sure about the utility of pyomo_unit since we technically haven't applied it yet. Nevertheless, assuming that it might come in handy later and aiming to maintain what you guys put together, I decided to implement in this fashion, primarily because the immediate priority is to allow for the user to be able change units for inputs/outputs readily. I suppose this wasn't the original intent for FixedKeysDict, but that is what is most important to start with. These changes should make that possible. I think we can decide on totally reworking this later, but for now, i want to use it for a subsequent PR that will involve the need to toggle units of inputs and outputs.

@bknueven
Copy link
Contributor

Maybe "pyomo unit" should be replaced with "pint unit".

The idea of the existing mapping is if the user wants to change the units on the input / output they can do that in a standard way through Python, instead of needing to deal with the units in OLI at all.

@adam-a-a
Copy link
Contributor Author

Maybe "pyomo unit" should be replaced with "pint unit".

The idea of the existing mapping is if the user wants to change the units on the input / output they can do that in a standard way through Python, instead of needing to deal with the units in OLI at all.

Ah, so say I want to alter the units for "molecularConcentration" to mol/L, which happens to be set to mg/L by default. How would you imagine that would go? Would it be something like the following? Or do you have a different example in mind?

f = Flash()
f.input_unit_set['molecularConcentration']['pint_unit'] = pyunits.mol/pyunits.L 

@bknueven
Copy link
Contributor

Ah, so say I want to alter the units for "molecularConcentration" to mol/L, which happens to be set to mg/L by default. How would you imagine that would go? Would it be something like the following? Or do you have a different example in mind?

So, for this particular issue, I would suggest we mirror what we already do in other parts of IDAES / WaterTAP: force the user to tell us up-front if they want an molar or mass basis and select the appropriate mapping for them.

@adam-a-a adam-a-a added the 1.0 Hard requirement for the 1.0 release label May 16, 2024
@adam-a-a
Copy link
Contributor Author

Ah, so say I want to alter the units for "molecularConcentration" to mol/L, which happens to be set to mg/L by default. How would you imagine that would go? Would it be something like the following? Or do you have a different example in mind?

So, for this particular issue, I would suggest we mirror what we already do in other parts of IDAES / WaterTAP: force the user to tell us up-front if they want an molar or mass basis and select the appropriate mapping for them.

That could be problematic given the way properties are passed/returned in OLI. We can think about changing this entirely. In any case, the current code resolved the issue that I identified. Now, if you overwrite either oli_unit or pyomo_unit with string or pint unit (all options are compatible), pyomo_unit would automatically match the newly set oli_unit; conversely, setting the pyomo_unit to a new value (string or pint units), would automatically update the corresponding oli_unit.

I think we can decide to accept as an incremental change and revisit in subsequent PRs, or I can strip that segment from this PR. If I do the latter, I will likely reintroduce those mods in a subsequent PR which would illustrate an example of why the user might need to change units for certain workflows in a non-obvious way. In the longer-term, we can think about a total refactor of the related code to better match practical user needs. Most importantly, I want to have some treatment of this before it ends up in 1.0.

@bknueven
Copy link
Contributor

Now, if you overwrite either oli_unit or pyomo_unit with string or pint unit (all options are compatible), pyomo_unit would automatically match the newly set oli_unit; conversely, setting the pyomo_unit to a new value (string or pint units), would automatically update the corresponding oli_unit.

My issue is that this does not work for all cases because the OLI units are bespoke, e.g., #1388 (comment), and that issue aside, according to the documentation OLI will only take a fixed set of units for each parameter.

Because of this, I would suggest if the user wants different units to go to OLI we make them specify them as OLI unit / pint unit pair.

@adam-a-a
Copy link
Contributor Author

adam-a-a commented May 16, 2024

Now, if you overwrite either oli_unit or pyomo_unit with string or pint unit (all options are compatible), pyomo_unit would automatically match the newly set oli_unit; conversely, setting the pyomo_unit to a new value (string or pint units), would automatically update the corresponding oli_unit.

My issue is that this does not work for all cases because the OLI units are bespoke, e.g., #1388 (comment), and that issue aside, according to the documentation OLI will only take a fixed set of units for each parameter.

Because of this, I would suggest if the user wants different units to go to OLI we make them specify them as OLI unit / pint unit pair.

Right- so, I think I have a solution for this, but that would come as a later PR if we decided to handle that way. Essentially, the same way we have valid_phases that OLI accepts, for each OLI property, we'd define valid_units. That would take more time/effort but can be handled separately. I also am not too crazy about needing to specify a pair. In any case, maybe the first thing to do is think through the use-case for pyomo_unit with a mini working example and see whether we want to keep/change. From that, if it becomes clear that we don't need to have pyomo_unit for whatever reason, then we wouldn't have wasted time on it.

But on this PR- it sounds like you'd opt to strip the changes to FixedKeysDict from here. I would reinject it in a subsequent PR for a common use case that WaterTAP users (myself included) might want to run often, and we can take it from there. How does that sound?

@ksbeattie ksbeattie added the Priority:High High Priority Issue or PR label May 16, 2024
@adam-a-a
Copy link
Contributor Author

adam-a-a commented May 24, 2024

@OOAmusat @avdudchenko I think you two are the only ones who could add reviews to this PR at this point. Please take a look if you have a chance. If this is something you don't think you'll have time for this quarter, let me know, and I will merge without reviews (because we really should have the major fix included in this PR as part of 1.0).

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.

@adam-a-a
Copy link
Contributor Author

I have tests passing for OLI. @TimBartholomew @OOAmusat @avdudchenko - just a reminder that I would like to get this merged for the June release since it fixes some issues that needed repair. Hoping to get your reviews in time; otherwise, we may just end up merging without the full 2 reviews.

Copy link
Contributor

@OOAmusat OOAmusat left a comment

Choose a reason for hiding this comment

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

LGTM.

Would have liked to test the units switching myself to check for edge cases, but in the absence of OLI access, I guess this is the best we can do.

Copy link
Contributor

@avdudchenko avdudchenko left a comment

Choose a reason for hiding this comment

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

This looks good to me - nothing really popped out that would cause issues. LTMG

@bknueven bknueven enabled auto-merge (squash) June 10, 2024 17:27
@bknueven bknueven merged commit aefad78 into watertap-org:main Jun 10, 2024
22 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1.0 Hard requirement for the 1.0 release oli Priority:High High Priority Issue or PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Updating units in OLI Flash
5 participants