Skip to content

Implement Python dict to C++ AnyMap conversion#1014

Merged
bryanwweber merged 12 commits intoCantera:mainfrom
ischoegl:implement-dict-to-anymap
Apr 30, 2021
Merged

Implement Python dict to C++ AnyMap conversion#1014
bryanwweber merged 12 commits intoCantera:mainfrom
ischoegl:implement-dict-to-anymap

Conversation

@ischoegl
Copy link
Copy Markdown
Member

@ischoegl ischoegl commented Apr 17, 2021

Changes proposed in this pull request

This PR adds the ability to create C++ AnyMap and AnyValue objects from the Python interface, where dict_to_anymap/python_to_anyvalue mirrors recently introduced anymap_to_dict/anyvalue_to_python (see PR #984). The following uses are anticipated:

The new service functions enable the use of C++ constructors (or other C++ functions) that take AnyMap or AnyValue as an argument from the Cython layer. Round-trip conversion is covered by unit tests via a hidden helper function cantera._cantera._py_to_any_to_py in a new test_utils.py job. Beyond, the functions are not accessible from the Python API, as they are internal service functions that facilitate the transfer of data from Python to the C++ layer.

Checklist

  • There is a clear use-case for this code change
  • The commit message has a short title & references relevant issues
  • Build passes (scons build & scons test) and unit tests address code coverage
  • The pull request is ready for review

@ischoegl ischoegl added Input Input parsing and conversion (for example, ck2yaml) Python labels Apr 17, 2021
@ischoegl ischoegl force-pushed the implement-dict-to-anymap branch from 787133f to 1afcdbe Compare April 17, 2021 15:38
@ischoegl ischoegl requested a review from a team April 17, 2021 15:55
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 17, 2021

Codecov Report

Merging #1014 (87ad140) into main (81cffde) will increase coverage by 0.14%.
The diff coverage is 100.00%.

❗ Current head 87ad140 differs from pull request most recent head 32bf083. Consider uploading reports for the commit 32bf083 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1014      +/-   ##
==========================================
+ Coverage   72.39%   72.53%   +0.14%     
==========================================
  Files         364      364              
  Lines       46433    46750     +317     
==========================================
+ Hits        33616    33911     +295     
- Misses      12817    12839      +22     
Impacted Files Coverage Δ
include/cantera/base/AnyMap.h 100.00% <ø> (ø)
src/base/AnyMap.cpp 89.59% <100.00%> (+0.54%) ⬆️
src/kinetics/RxnRates.cpp 89.83% <0.00%> (-3.58%) ⬇️
src/kinetics/Reaction.cpp 89.18% <0.00%> (-1.50%) ⬇️
include/cantera/kinetics/RxnRates.h 91.97% <0.00%> (-0.17%) ⬇️
include/cantera/kinetics/Reaction.h 100.00% <0.00%> (ø)
include/cantera/kinetics/RateCoeffMgr.h 100.00% <0.00%> (ø)
src/kinetics/Kinetics.cpp 83.62% <0.00%> (+0.04%) ⬆️
src/kinetics/BulkKinetics.cpp 88.63% <0.00%> (+0.13%) ⬆️
src/kinetics/GasKinetics.cpp 97.43% <0.00%> (+0.15%) ⬆️
... and 7 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 81cffde...32bf083. Read the comment docs.

@ischoegl ischoegl mentioned this pull request Apr 18, 2021
8 tasks
@speth
Copy link
Copy Markdown
Member

speth commented Apr 19, 2021

Well, I had a good portion of this implemented, as it is the logical extension to #984, but I guess you beat me to opening a PR. I pushed my work-in-progress on this to my set-user-data branch in case it's of any use.

@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Apr 19, 2021

@speth ... sorry, didn’t realize. While there is some redundancy, my PR doesn’t include user data. The reason I implemented it was that not being able to move data from Python to AnyMap forced me to use awkward workarounds, and I wanted to have this available for #995.

@ischoegl
Copy link
Copy Markdown
Member Author

@speth ... I believe some of your ideas made the code simpler (especially using set operations). Shaved some 20 lines off, and the logic is clearer.

ischoegl and others added 4 commits April 21, 2021 12:39
Simplify code using:
* explicit type conversions
* leverage of set properties

Co-authored-by: Ray Speth <[email protected]>
Remove tests for unhandled cases that are now handled
@speth
Copy link
Copy Markdown
Member

speth commented Apr 21, 2021

I updated the implementation on my branch, following on from what you have here. This version achieves a couple of things:

  • It works for any combination of nested containers (where the base types are convertible), including "ragged" nested arrays and higher-dimensional arrays.
  • The use of helper functions for populating the containers of like items avoids creating a bunch of unused containers on every call to python_to_anyvalue.

I hope you're willing to take this as a friendly amendment. If so, you can push those commits to this branch and we can continue the review from there (and of course you or @bryanwweber should provide a review of my changes).

@ischoegl ischoegl force-pushed the implement-dict-to-anymap branch from 5774c40 to 9e12518 Compare April 21, 2021 18:37
@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Apr 21, 2021

@speth ... I did a hard reset to your branch, so I believe at this point both of our ideas are blended.

Overall, I have no objections to adopting the composite implementation, despite being somewhat sad to see isEmpty go, as I believe it to be more intuitive than isType[void], but that's not worth haggling over. Regarding 3-dim (and higher) arrays I had blocked those as I didn't see a need, and treating them as vector<AnyValue>> is different from the logical extension of vector<vector<vector<...>>>; I guess it's a theoretical question at the moment and would only have repercussions in the C++ layer anyways. Of course, they are legitimate values (just like ragged), but my NotImplementedError would have tabled these question. Overall, I'm generally more concerned about what features and interfaces are available than how things are implemented. To qualify the last statement: performance and code readability do matter, but that's not a concern here. Edit: those minor points are all resolved.

@bryanwweber ... it's up to you to review I guess, as @speth's friendly amendments are an improvement over my initial code, and I'm ok with them (Edit: my comments below are intended to clarify my points above). As an aside: @speth in case any changes come up, feel free to push directly to my branch.

@bryanwweber
Copy link
Copy Markdown
Member

I'll take a look later this week after I finish this round of grading.

@ischoegl ischoegl force-pushed the implement-dict-to-anymap branch from 9e12518 to 453ee47 Compare April 23, 2021 01:21
Copy link
Copy Markdown
Member

@bryanwweber bryanwweber left a comment

Choose a reason for hiding this comment

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

Relatively minor changes here, thanks @ischoegl!

Comment on lines +155 to +171
itype = set()
for i in item:
if isinstance(i, dict):
itype.add(dict)
elif isinstance(i, list) or isinstance(i , tuple):
itype.add(list)
elif type(i) == bool:
itype.add(bool) # otherwise bools will get counted as integers
elif isinstance(i, numbers.Integral):
itype.add(numbers.Integral)
elif isinstance(i, numbers.Real):
itype.add(numbers.Real)
else:
itype.add(type(i))

if itype == {numbers.Real, numbers.Integral}:
itype = {numbers.Real}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

2 questions here:

  1. Are the first two conditions needed? It seems as though itype.add(type(i)) would do well enough for those.
  2. For the numbers, could you combine that condition? Since you reduce the set to numbers.Real at the end anyways. Or is there a use to having just numbers.Integral as the type?

It might be worth adding comments that answer these questions, for any future readers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I can see from code below that the answer to question 2 is, yes there is a use to having numbers.Integral as the type.

Copy link
Copy Markdown
Member Author

@ischoegl ischoegl Apr 24, 2021

Choose a reason for hiding this comment

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

Ad (1) ... I don't believe that the two conditions can be merged. Of course the second one can be simplified to isinstance(i, (list, tuple)).

Copy link
Copy Markdown
Member

@speth speth Apr 27, 2021

Choose a reason for hiding this comment

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

@ischoegl is right that list + tuple check can be combined.

The reason for not just using itype.add(type(i)) is that I want to treat any type that inherits from dict as a dict, and likewise any types inheriting from list or tuple are essentially list-like for the purpose of serialization. I added a few comments to this effect.

@ischoegl ischoegl force-pushed the implement-dict-to-anymap branch from 453ee47 to 69b635b Compare April 24, 2021 19:55
@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Apr 24, 2021

@bryanwweber ... thanks for the review! I believe I resolved everything, so this should be ready to merge. The two comments where I left responses 'unresolved' pertain to code @speth (re-)wrote, so if there are remaining questions, I'll defer to him.

@ischoegl ischoegl requested a review from bryanwweber April 24, 2021 20:08
* Clarify empty AnyValue
* Fix docstrings
* Address review comments
@ischoegl ischoegl force-pushed the implement-dict-to-anymap branch from 69b635b to 87ad140 Compare April 24, 2021 22:43
@ischoegl
Copy link
Copy Markdown
Member Author

@bryanwweber ... I hope I was able to clarify the discrepancy between long int and long.

@ischoegl ischoegl requested a review from bryanwweber April 24, 2021 22:45
@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Apr 27, 2021

@bryanwweber ... any additional questions? I am mostly done rebasing #995 to include serialization (i.e. assuming that this would get merged before).

@bryanwweber
Copy link
Copy Markdown
Member

@ischoegl Sorry, I was waiting to see if @speth wanted to respond to the other unresolved questions.

@speth
Copy link
Copy Markdown
Member

speth commented Apr 27, 2021

I responded to two of the open threads. I didn't have anything to add to the third -- I think it's all set now, right?

@ischoegl
Copy link
Copy Markdown
Member Author

@speth … thank you for your comments! All set from my side too.

@bryanwweber bryanwweber merged commit 3466422 into Cantera:main Apr 30, 2021
@ischoegl
Copy link
Copy Markdown
Member Author

ischoegl commented Apr 30, 2021

🎉 ... thanks, @bryanwweber (and @speth)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Input Input parsing and conversion (for example, ck2yaml) Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants