Skip to content

Generalzie pyomo model handling#55

Open
avdudchenko wants to merge 23 commits into
we3lab:mainfrom
avdudchenko:generalzie_pyomo_model_handling
Open

Generalzie pyomo model handling#55
avdudchenko wants to merge 23 commits into
we3lab:mainfrom
avdudchenko:generalzie_pyomo_model_handling

Conversation

@avdudchenko

@avdudchenko avdudchenko commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Fix issue with pyomo model handling.

Right now its assumed pyomo model has a variable t on it which is used for indexing the consumption variable, this is not necessarly true and complicates model handling.

Here we update the costing to automatically extract index set from the consumption variable, and use it through out. This removes the need to find a model.t var or for user to create it.

This additionally fixes tests:

  • removes Gurobi as dependency and uses scip for tests -> user can decide on solver of choice
  • fixes mixing approx in some tests.
  • extends tests to cover linux/windows and 3.11, 3.12., 3.13 python versions.

@avdudchenko
avdudchenko marked this pull request as ready for review July 13, 2026 18:29
@avdudchenko
avdudchenko requested review from dalyw and fletchapin July 13, 2026 18:29
- "3.13"
os:
- linux
- win64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice, thanks for making this more extensive

Comment thread eeco/tests/test_costs.py
return model, pyo_vars


def setup_pyo_vars_with_non_standard_indexing_constraints(consumption_data_dict):

@dalyw dalyw Jul 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This version of the function should work for standard and non-standard indexes, right? Can this be renamed to just replace the other setup_pyo_vars?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, but we should explicitly test for both. Technically, we could add the two function as part of parametrize in pytest, but I prefer this to be more explicit. There is no such thing as too many tests...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fair enough haha. I wonder if rather than (or in addition to) testing all these cases in calculate_cost, we could add more tests on ut.multiply(pyo_var, np_array) since "multiply" has the most potential for mismatch in the indices. With more variations on ways the Pyo variable indexes could be defined.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It should handle most use cases... but we can probably explore it and modify as we hit walls. Right now it should use the right indexing regardless of order in multiple (np_aray, pyo_var) or (pyo_var, np_array). Tat should ensure it always pulls right index for each input type.

The bigger issue if we need to start supporting block indexing (e..g m.block[1,2,3,4,5].power) which is more common in IDAES/WaterTAP model structures. Although I think in 99% of cases the power/fuel vars will be indexed directly (var[indx]).

Comment thread eeco/tests/test_costs.py
Comment thread eeco/utils.py Outdated
Comment thread eeco/tests/test_utils.py
assert model is not None


@pytest.mark.skipif(skip_all_tests, reason="Exclude all tests")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If adding an additional test case for multiply with Pyomo * numpy:

@pytest.mark.skipif(skip_all_tests, reason="Exclude all tests")
@pytest.mark.parametrize(
"consumption_data, varstr1, varstr2, time_set, expected",
[
# two Pyo variables
(
{"electric": np.ones(96) * 100, "gas": np.ones(96)},
"electric",
"gas",
None,
np.ones(96) * 100,
),
# Pyo variable * numpy charge array on non-standard index
(
{"electric": np.array([100.0, 200.0, 300.0, 400.0])},
"electric",
np.array([0.1, 0.2, 0.3, 0.4]),
[0.0, 900.0, 1800.0, 2700.0],
np.array([10.0, 40.0, 90.0, 160.0]),
),
# Pyo variable * numpy charge array on irregular (non-uniform) index
(
{"electric": np.array([100.0, 200.0, 300.0, 400.0])},
"electric",
np.array([0.1, 0.2, 0.3, 0.4]),
[2.0, 4.0, 5.0, 8.0],
np.array([10.0, 40.0, 90.0, 160.0]),
),
],
)
def test_multiply_pyo(consumption_data, varstr1, varstr2, time_set, expected):
model = pyo.ConcreteModel()
model.T = len(consumption_data[varstr1])
model.t = range(model.T) if time_set is None else time_set
pos = {t: i for i, t in enumerate(model.t)}
for key, val in consumption_data.items():
var = pyo.Var(model.t, bounds=(None, None))
model.add_component(key, var)
for t in model.t:
var[t].fix(float(val[pos[t]]))

var1 = getattr(model, varstr1)
var2 = getattr(model, varstr2) if isinstance(varstr2, str) else varstr2
ut.create_pyomo_model_index_ref(model, var1)
result, model = ut.multiply(var1, var2, model=model, varstr="test")
model.objective = pyo.Objective(expr=0)
solver = pyo.SolverFactory("ipopt")
solver.solve(model)
assert np.allclose([pyo.value(result[t]) for t in model.t], expected)
assert model is not None

Comment thread eeco/utils.py
return var[t] == expression1[t] * expression2[t]

constraint = pyo.Constraint(model.t, rule=const_rule)
if isinstance(expression1, (pyo.Param, pyo.Var)):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Check for a Pyomo IndexedExpression as well?

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