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

Expose C++ Units to Python #1076

Merged
merged 22 commits into from
Sep 16, 2021
Merged

Conversation

ischoegl
Copy link
Member

@ischoegl ischoegl commented Jul 17, 2021

Changes proposed in this pull request

  • Shorten Units::str() output
  • Expose CxxUnits to Cython interface
  • Implement Python API for Units
  • Expose Reaction.rate_coeff_units and ThermoPhase.standard_concentration_units
  • Expose UnitSystem to Python

If applicable, provide an example illustrating new features this pull request is introducing

The new feature can be used as follows:

In [1]: import cantera as ct
   ...: gas = ct.Solution("gri30.yaml")
   ...: gas.reaction(0)
   ...:
Out[1]: <ThreeBodyReaction: 2 O + M <=> O2 + M>

In [2]: gas.reaction(0).rate_coeff_units
Out[2]: <Units(1.0 m^6 / kmol^2 / s) at 7f928bee69f0>

In [3]: gas.standard_concentration_units
Out[3]: <Units(1.0 kmol / m^3) at 7f928bee6ab0>

In [4]: ct.Units("Pa")
Out[4]: <Units(1.0 kg / m / s^2) at 7f928bee6cf0>

In [5]: ct.UnitSystem().defaults
Out[5]: 
{'activation-energy': 'J/kmol',
 'current': 'A',
 'energy': 'J',
 'length': 'm',
 'mass': 'kg',
 'pressure': 'Pa',
 'quantity': 'kmol',
 'temperature': 'K',
 'time': 's'}

Checklist

  • The pull request includes a clear description of this code change
  • Commit messages have short titles and reference relevant issues
  • Build passes (scons build & scons test) and unit tests address code coverage
  • Style & formatting of contributed code follows contributing guidelines
  • The pull request is ready for review

Other thoughts

This PR seeks to expose units used in the C++ layer, rather than facilitating conversion of alternative units as proposed in #991.

@codecov
Copy link

codecov bot commented Jul 17, 2021

Codecov Report

Merging #1076 (6f8aed6) into main (e77148b) will increase coverage by 0.03%.
The diff coverage is 88.65%.

❗ Current head 6f8aed6 differs from pull request most recent head 1844332. Consider uploading reports for the commit 1844332 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1076      +/-   ##
==========================================
+ Coverage   73.45%   73.49%   +0.03%     
==========================================
  Files         364      364              
  Lines       47611    47735     +124     
==========================================
+ Hits        34972    35081     +109     
- Misses      12639    12654      +15     
Impacted Files Coverage Δ
include/cantera/base/Units.h 100.00% <ø> (ø)
include/cantera/base/YamlWriter.h 100.00% <ø> (ø)
include/cantera/kinetics/Falloff.h 67.64% <55.55%> (-6.43%) ⬇️
src/kinetics/Reaction.cpp 88.90% <60.00%> (-0.31%) ⬇️
src/base/Units.cpp 94.21% <91.89%> (-0.59%) ⬇️
src/kinetics/Falloff.cpp 86.79% <92.00%> (+1.60%) ⬆️
src/base/YamlWriter.cpp 100.00% <100.00%> (ø)
src/kinetics/FalloffFactory.cpp 100.00% <100.00%> (ø)
test/general/test_serialization.cpp 100.00% <100.00%> (ø)
test/general/test_units.cpp 100.00% <100.00%> (ø)
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e77148b...1844332. Read the comment docs.

@ischoegl ischoegl force-pushed the expose-units-to-python branch 2 times, most recently from 08b8e16 to 735990b Compare July 17, 2021 22:27
@ischoegl ischoegl changed the title Expose reaction rate coefficient units to python Expose C++ Units to Python Jul 17, 2021
@ischoegl ischoegl marked this pull request as ready for review July 18, 2021 03:36
@ischoegl ischoegl requested a review from a team July 18, 2021 03:41
@ischoegl ischoegl force-pushed the expose-units-to-python branch 6 times, most recently from f25bca8 to 5abd858 Compare July 19, 2021 02:23
@ischoegl ischoegl force-pushed the expose-units-to-python branch 3 times, most recently from f034688 to 01c2ba1 Compare July 27, 2021 11:59
Copy link
Member

@speth speth left a comment

Choose a reason for hiding this comment

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

I think making information about the units of reaction rate coefficients and phase standard concentrations available is a valuable addition to the Python interface, and a light interface to the Units class seems to serve this purpose, though I think there are currently a few quirks.

While I understand that the string representation of Units objects draws from the existing implementation (with some cleanup), I think that this output is kind of confusing when the units aren't in Cantera's base system, e.g. in the case of

>>> ct.Units("mol/cm^3")
<Units(999.9999999999999 kmol / m^3) at 7f6ee85c93f0>

it's not really clear anymore that what this is intended to mean is that 1000 is the conversion factor between mol/cm^3 and kmol/m^3, rather than representing a particular quantity of 1000 kmol/m^3. This distinction gets even muddier with newly-allowed expressions like Units("4 atm").

Assuming the goal here is just to provide unit information for these special cases where the dimensions vary in different instances, rather than presenting a full unit conversion capability (which I don't think we really want or need) I'd suggest that maybe Python Units objects should only be constructible in the default unit system, and that they could be represented without the leading 1.0 factor, e.g.

>>> ct.Units("J/kmol")
<Units(kg * m^2 / kmol / s^2) at 7f6ef085c030>

and noting that practically, there's really no purpose for a user-constructed Units object.

I'm not clear on what the benefit of exposing the UnitSystem class here is. Is the purpose just to make information about the (completely fixed) default unit system available?

@ischoegl
Copy link
Member Author

ischoegl commented Sep 2, 2021

@speth ... thanks for your comments!

I think making information about the units of reaction rate coefficients and phase standard concentrations available is a valuable addition to the Python interface, and a light interface to the Units class seems to serve this purpose, though I think there are currently a few quirks. [...]

This distinction gets even muddier with newly-allowed expressions like Units("4 atm").

Interesting observation. I definitely didn't have that in mind when I created the constructor! While I did code this up to finally wrap my head around the internal unit conversions (it did help), my main concern was to display what's done internally.

I'm not clear on what the benefit of exposing the UnitSystem class here is. Is the purpose just to make information about the (completely fixed) default unit system available?

Displaying the unit system was certainly one of my main concerns here (it would clear up things once and for all for any user). But at the same time, it would serve a purpose to have it as a stand-alone object that is passed to YamlWriter (see 5751633), where Unitswith conversion factors do make some sense.

I agree that making the Python Units constructor less 'useful' makes sense. This should be a relatively easy tweak to this PR.

@ischoegl
Copy link
Member Author

ischoegl commented Sep 3, 2021

@speth ... I reworked some of the interfaces to make things more intuitive. For unit output, the factor is suppressed:

In [1]: import cantera as ct
   ...:    ...: gas = ct.Solution("gri30.yaml")
   ...:    ...: gas.reaction(0)
   ...: 
Out[1]: <ThreeBodyReaction: 2 O + M <=> O2 + M>

In [2]: gas.reaction(0).rate_coeff_units
Out[2]: <Units(m^6 / kmol^2 / s) at 7fd73eb86f30>

In [3]: gas.standard_concentration_units
Out[3]: <Units(kmol / m^3) at 7fd73eb86c90>

The UnitSystem now displays units automatically, and has a more intuitive units property (instead of the internally used C++ defaults, which originates in the pre-existing Units::setDefaults):

In [4]: ct.UnitSystem().units # default unit system
Out[4]: 
{'activation-energy': 'J / kmol',
 'current': 'A',
 'energy': 'J',
 'length': 'm',
 'mass': 'kg',
 'pressure': 'Pa',
 'quantity': 'kmol',
 'temperature': 'K',
 'time': 's'}

In [5]: units = ct.UnitSystem({"quantity": "mol", "length": "cm"}) 

In [6]: units.units # alternate unit system (e.g. input to YamlWriter)
Out[6]:
{'activation-energy': 'J / mol',
 'current': 'A',
 'energy': 'J',
 'length': 'cm',
 'mass': 'kg',
 'pressure': 'Pa',
 'quantity': 'mol',
 'temperature': 'K',
 'time': 's'}

Finally, unit conversions are no longer enabled for unit strings with non-unity conversion factors:

In [6]: ct.Units('Pa')
Out[6]: <Units(kg / m / s^2) at 7f0275f42c90>

In [7]: ct.Units('3 Pa')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-3-f54d3ab3f8e5> in <module>
----> 1 ct.Units('3 Pa')

/src/interfaces/cython/cantera/units.pyx in cantera._cantera.Units.__cinit__()
     19             self.units = CxxUnits(stringify(name))
     20             if abs(self.units.factor() - 1.) > np.nextafter(0, 1):
---> 21                 raise ValueError(
     22                     "The creation of 'Units' objects that require a conversion "
     23                     "factor\nwith respect to Cantera's default unit system is not "

ValueError: The creation of 'Units' objects that require a conversion factor
with respect to Cantera's default unit system is not supported:
input '3 Pa' is equivalent to '3.0 kg / m / s^2' where the conversion factor is '3.0'

PS: I added a direct UnitSystem based setter YamlWriter::setUnitSystem to clarify my intent.

In [8]: writer = ct.YamlWriter()
   ...: writer.add_solution(ct.Solution("h2o2.yaml"))
   ...: writer.output_units = ct.UnitSystem({"quantity": "mol", "length": "cm"})

(this was possible even before the latest commits, but now there's a direct path for this)

Copy link
Member

@speth speth left a comment

Choose a reason for hiding this comment

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

Thanks for the updates, @ischoegl, I think this is fairly close.

interfaces/cython/cantera/units.pyx Outdated Show resolved Hide resolved
include/cantera/base/Units.h Outdated Show resolved Hide resolved
interfaces/cython/cantera/_cantera.pxd Outdated Show resolved Hide resolved
interfaces/cython/cantera/units.pyx Outdated Show resolved Hide resolved
src/base/Units.cpp Outdated Show resolved Hide resolved
test/general/test_units.cpp Outdated Show resolved Hide resolved
test/general/test_units.cpp Outdated Show resolved Hide resolved
src/base/Units.cpp Outdated Show resolved Hide resolved
@ischoegl ischoegl requested a review from speth September 10, 2021 19:27
@ischoegl ischoegl force-pushed the expose-units-to-python branch 2 times, most recently from 23c7b25 to fd56254 Compare September 10, 2021 21:21
@ischoegl
Copy link
Member Author

@speth ... thank you for the additional comments, which should be taken care of. Per suggestion, all the formatting flags are now implemented in C++ rather than just for Python. The unit tests were easily taken care of and the documentation is updated. Regarding Unittest::default the direction I am taking requires the steps, as I am starting from the factors and work my way from there.

@ischoegl
Copy link
Member Author

@speth ... let me know if there's anything else. From my side, I believe this is ready to be merged.

@ischoegl
Copy link
Member Author

@speth ... thanks for clarifying! I simply cherry-picked your suggestion as it's indeed simpler (I was apparently too focused on the numeric factors ...).

Copy link
Member

@speth speth left a comment

Choose a reason for hiding this comment

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

Thanks, @ischoegl, I think that wraps this all up.

@speth speth merged commit 8266605 into Cantera:main Sep 16, 2021
@ischoegl ischoegl deleted the expose-units-to-python branch September 16, 2021 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants