Allow for connections including slice specifications#774
Allow for connections including slice specifications#774jaredthomas68 wants to merge 13 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the H2Integrate connection specification syntax to support OpenMDAO-style partial connections by allowing slice/index notation in connected parameter definitions and translating that into src_indices during connect_technologies().
Changes:
- Adds
_split_indices_from_connected_parameter_definition()to parse bracket slice/index specs and producesrc_indices. - Updates
connect_technologies()to passsrc_indicesintoplant.connect(...). - Adds unit tests for slice parsing and loosens the plant config schema for
technology_interconnections.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| h2integrate/core/h2integrate_model.py | Adds slice parsing and threads src_indices into technology connections. |
| h2integrate/core/inputs/plant_schema.yaml | Adjusts schema typing for technology_interconnections entries. |
| h2integrate/core/test/test_split_indices.py | Adds unit tests for _split_indices_from_connected_parameter_definition. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
elenya-grant
left a comment
There was a problem hiding this comment.
Thanks for pulling this together to fast! It's looking good.I haven't checked whether you wanted feedback on this yet or not - so sorry for getting ahead of it if so! I think the tests for the standalone method look good - but I'm worried about some of functionality when it comes to actually running OpenMDAO.
My main comments/suggestions/questions on revisions of the code as-is are:
- remove use of
evalif possible - replace
if/elif/elselogic withifstatements paired with areturnwithin that if statement. - why not use
re.search(r"\[.*?\]", dest_parameter)instead of defining apatternvariable? - Nitpick and non-blocking:
_split_indices_from_connected_parameter_definition()is a long method name. It's very clear what it does but I wonder if theres a shorter name that would work?
I'm assuming that these are on your to-do list, but some to-dos I'm thinking of are:
- Integration tests where openmdao is actually run.
- Updates to
docs/user_guide/connecting_technologies.md - Added error messages when someone tries to use this incorrectly.
|
|
||
| # Scale source indices by destination length to handle shape mismatches | ||
| # Example: source_slice="[0:1]" with dest_length=8760 -> multiply 1 by 8760 | ||
| src_indices = eval(f"om.slicer[{source_slice} * {dest_length}]") |
There was a problem hiding this comment.
why do we need to use the om.slicer here? I think in the past we've tried to stay away from the use of eval.
As this is, I don't think this would work if someone was using more than 1 slice from the source when open-mdao is actually run. Aka - if src_slice is len 2 - then we don't want to multiply it by the full destination length (because then it'll be twice as long as needed).
Like this case:
source_parameter = "power_in[0,1,2]"
dest_parameter = "power_out[:8760]"Then shouldn't we take the [0,1,2] and multiply it by 2920 instead of 8760?
There was a problem hiding this comment.
We definitely need om.slicer to properly format for openmdao. I have dropped the use of eval. I updated the logic to allow for tiling as you suggested.
There was a problem hiding this comment.
You are right about eval. I removed eval. I kept om.slicer because it is an easy way to create the slices in the way that openmdao expects. I also added the ability to tile as you suggested.
There was a problem hiding this comment.
yeah - I like the use of om.slicer (once I understood what it was doing). Thanks for getting rid of eval()
| pattern = re.compile(r":(\d+)") | ||
| dest_length = int(pattern.search(dest_slice).group().split(":")[-1]) |
There was a problem hiding this comment.
| pattern = re.compile(r":(\d+)") | |
| dest_length = int(pattern.search(dest_slice).group().split(":")[-1]) | |
| if (d_start_index := re.search(r"(\d+):", dest_slice)) is not None: | |
| raise ValueError(f"Cannot have starting index of {d_start_index.group()} in parameter {dest_parameter}") | |
| dest_indices = re.search(r":(\d+)", dest_slice) | |
| if (dest_indices := re.search(r":(\d+)", dest_slice)) is not None | |
| dest_length = int(pattern.search(dest_slice).group().split(":")[-1]) |
There was a problem hiding this comment.
I think this has been addressed, but with a different approach. Let me know if you still think this should change.
…ce and connection approaches
…ectrolyzer example
I have added two more finance/connection approaches to example 15 (wind solar electrolyzer) and tests. I updated the connecting_technologies.md file to include documentation for using slice notation in connections. I added a check to catch incorrect usage and provide what I hope is a clear error message. |
elenya-grant
left a comment
There was a problem hiding this comment.
I left a few clarifying questions but the logic is a lot easier to follow now! Thanks for working on this! Looks good to go on my end!
|
|
||
| Behavior depends on which side carries a slice: | ||
|
|
||
| - **Source only** (`"source_param[0:100]"`): the slice selects source indices directly (supports start/stop/step, e.g. `[0:100:2]`, and `[:]` for the full range). |
There was a problem hiding this comment.
Clarification question: When it's "Source only", then the destination length isn't needed? Does this mean that if my source_param is a 100 element array and my destination is a 50 element array, then I just need to do the connection as
["source_tech", "destination_tech", ["source_param[0:100:2]", "destination_param"]]
There was a problem hiding this comment.
Yes, that is correct.
| Behavior depends on which side carries a slice: | ||
|
|
||
| - **Source only** (`"source_param[0:100]"`): the slice selects source indices directly (supports start/stop/step, e.g. `[0:100:2]`, and `[:]` for the full range). | ||
| - **Destination only** or **both sides equal**: no `src_indices` are applied (the slice is treated as documentation of the target shape). |
There was a problem hiding this comment.
Idk if the definition of "destination only" needs to be explained here? Isn't "Destination only" when we don't specify indices?
["source_tech", "destination_tech", ["source_param", "destination_param"]]
There was a problem hiding this comment.
Yes, that is what it means by "no src_indices are applied". Basically it treats it as though no slice was provided.
improve comment Co-authored-by: elenya-grant <116225007+elenya-grant@users.noreply.github.com>
Allow connections using src_indices/slices
As noted in #763, there are times when it can be helpful to connect only part of a source variable to a target variable in the OpenMDAO connections. OpenMDAO allows for this, as described in the docs. This PR extends the connection specifications in H2Integrate to allow users to connect from part of an array variable to a target variable.
Section 1: Type of Contribution
Section 2: Draft PR Checklist
TODO:
Type of Reviewer Feedback Requested (on Draft PR)
Structural feedback:
Implementation feedback:
Other feedback:
Section 3: General PR Checklist
docs/files are up-to-date, or added when necessaryCHANGELOG.md"A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where
XYZshould be replaced with the actual number.Section 4: Related Issues
Section 5: Impacted Areas of the Software
Section 5.1: New Files
h2integrate/core/test/test_split_indices.py: tests for_split_indices_from_connected_parameter_definitionSection 5.2: Modified Files
h2integrate/core/h2integrate_model.py_split_indices_from_connected_parameter_definition: parses connection definitions that include slices and returns the indices and the connection spec without the slice definitionconnect_technologies: use_split_indices_from_connected_parameter_definitionto facilitate slice/src_indices connectionsSection 6: Additional Supporting Information
Section 7: Test Results, if applicable
Section 8 (Optional): New Model Checklist
docs/developer_guide/coding_guidelines.mdattrsclass to define theConfigto load in attributes for the modelBaseConfigorCostModelBaseConfiginitialize()method,setup()method,compute()methodCostModelBaseClasssupported_models.pycreate_financial_modelinh2integrate_model.pytest_all_examples.pydocs/user_guide/model_overview.mddocs/section<model_name>.mdis added to the_toc.ymlgenerate_class_hierarchy.pyto update the class hierarchy diagram indocs/developer_guide/class_structure.md