Skip to content

Add SetOf domain; improve config's mock yaml dumper#3872

Merged
blnicho merged 7 commits intoPyomo:mainfrom
jsiirola:setof
Mar 18, 2026
Merged

Add SetOf domain; improve config's mock yaml dumper#3872
blnicho merged 7 commits intoPyomo:mainfrom
jsiirola:setof

Conversation

@jsiirola
Copy link
Member

@jsiirola jsiirola commented Mar 14, 2026

Fixes # .

Summary/Motivation:

@dallan-keylogic requested that Pyomo's config system had a SubsetOf domain that paralleled the ListOf domain. This PR refactors ListOf so that SetOf is a trivial extension. SubsetOf can be easily acomplished using a combination of SetOf and In (and is documented in the SetOf docs).

While I was messing with config, I refactored the mock yaml dumper so that we could reliably test it (even on systems with yaml). This included a subtle change to the dumper so that types are more intelligently dumped (that is, we will consistently dump the actual type and not "<class 'type'>"), and so that None is output in proper YAML (as null).

Changes proposed in this PR:

  • Add SetOf
  • Refactor the mock yaml dumper to improve type output and support better testing

Legal Acknowledgement

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

  1. I agree my contributions are submitted under the BSD license.
  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.

@codecov
Copy link

codecov bot commented Mar 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.52%. Comparing base (7ad143c) to head (dc801ee).
⚠️ Report is 21 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3872      +/-   ##
==========================================
- Coverage   89.67%   89.52%   -0.16%     
==========================================
  Files         908      908              
  Lines      106735   106746      +11     
==========================================
- Hits        95717    95563     -154     
- Misses      11018    11183     +165     
Flag Coverage Δ
builders 29.09% <62.06%> (+<0.01%) ⬆️
default 85.97% <100.00%> (?)
expensive 35.52% <75.86%> (?)
linux 86.97% <100.00%> (-2.20%) ⬇️
linux_other 86.97% <100.00%> (-0.17%) ⬇️
oldsolvers 28.01% <62.06%> (+<0.01%) ⬆️
osx 82.32% <100.00%> (+0.28%) ⬆️
win 85.42% <100.00%> (-0.14%) ⬇️
win_other 85.42% <100.00%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dallan-keylogic
Copy link

My original problem was the result of me failing to use a domain validator for ListOf. Looking again at the problem today, it looks like I can solve my problem by correctly using domain validation in ListOf.

I'm not sure adding SetOf as a domain validator is the right solution, however. I am wary of using Python's default set objects because they are unordered.

When working on SOC dynamics, I ended up with an extremely obscure bug. I was specifying the phases in a certain property package as the set {"Liq", "Vap"}. The way IDAES phase equilibrium works is that we specify the phases in equilibrium with a tuple. However, because the phase set was unordered, it was sometimes defined as ("Liq", "Vap") and sometimes as ("Vap", "Liq"). Theoretically the order you get variables by iterating over a set should be the same within each Python version. However, we found that it would change even within the same version of Python.

The way we specified the initial condition was to solve the steady state flowsheet, dump it to a .json file, then load that .json file to serve as the initial condition. Some IDAES variables, such as bubble and dew points, were indexed by the phase equilibrium pair. But if the .json file had variables indexed by ("Liq", "Vap") and the simulation had variables indexed by ("Vap", "Liq"), then those variables wouldn't have the correct values. If we tried to solve the initial condition problem, IPOPT could take a long time to solve and would sometimes fail. If we didn't try to solve the initial condition problem, the results might be nonsensical.

So, in short, I think the user should be discouraged from providing items using unordered objects because it sets the stage for code either failing intermittently, or succeeding in one version of Python and failing in the other.

@blnicho blnicho self-requested a review March 17, 2026 19:02
@blnicho blnicho merged commit fdfb8ee into Pyomo:main Mar 18, 2026
63 of 65 checks passed
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.

5 participants