Skip to content

perf(model): write solve results instead of rebuilding DataArrays - #1035

Open
olivier-lacroix wants to merge 2 commits into
PyPSA:masterfrom
olivier-lacroix:perf/assign-result-arrays
Open

olivier-lacroix wants to merge 2 commits into
PyPSA:masterfrom
olivier-lacroix:perf/assign-result-arrays

Conversation

@olivier-lacroix

@olivier-lacroix olivier-lacroix commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

A minor performance improvement, discovered while profiling some LP models.

Note

The following content was generated by AI.

What

Model.assign_result builds a DataArray per variable and per constraint, then lets the public setters merge it into each container's dataset. After a solve the shape and coordinates are already correct, so writing the arrays into a shallow copy is enough.

  • common.set_data_var writes one data variable without xarray's merge pass.
  • Variable._assign_solution and Constraint._assign_dual use it.
  • Model.assign_result calls both.

The public Variable.solution and Constraint.dual setters are unchanged, so nothing else moves.

Benchmark

Scenario: 20 variables and 20 constraints over 2000 timesteps, median of 10 assign_result calls.

checkout assign_result
master 15.91 ms/call
this branch 0.78 ms/call
Reproduce
import statistics
import time

import pandas as pd

import linopy
from linopy.solvers import Solver

n_time, n_streams, reps = 2000, 20, 10
index = pd.RangeIndex(n_time, name="time")
model = linopy.Model()
variables = [
    model.add_variables(lower=0, coords=[index], name=f"x{i}")
    for i in range(n_streams)
]
for i, var in enumerate(variables):
    model.add_constraints(var + variables[(i + 1) % n_streams] <= 10, name=f"c{i}")
model.add_objective(sum(v.sum() for v in variables))

solver = Solver.from_name("highs", io_api="direct", options={"output_flag": False})
result = solver.solve(model=model)
model.assign_result(result)

timings = []
for _ in range(reps):
    start = time.perf_counter()
    model.assign_result(result)
    timings.append(time.perf_counter() - start)
print(f"{statistics.median(timings) * 1000:.2f} ms/call")

AI assistance

Drafted with Claude Code: the implementation and the measurement. Reviewed by hand before opening.

@olivier-lacroix
olivier-lacroix force-pushed the perf/assign-result-arrays branch from 6319d3d to a7c3668 Compare October 20, 2026 03:16
@olivier-lacroix
olivier-lacroix marked this pull request as ready for review October 20, 2026 03:19
@codspeed

codspeed Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 181 untouched benchmarks
⏩ 181 skipped benchmarks1


Comparing olivier-lacroix:perf/assign-result-arrays (89c4c83) with master (51c4977)

Open in CodSpeed

Footnotes

  1. 181 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@olivier-lacroix

Copy link
Copy Markdown
Contributor Author

Thx @FabianHofmann !

@olivier-lacroix
olivier-lacroix force-pushed the perf/assign-result-arrays branch from d177467 to 341f11a Compare October 24, 2026 02:12
Olivier Lacroix and others added 2 commits October 25, 2026 14:28
`Model.assign_result` built a `DataArray` per variable and constraint and let
the public setters align and merge it against the stored fields. Writing the
arrays straight in is enough: the shape and coordinates are already correct.

At 20 variables and 20 constraints over 2000 timesteps, `assign_result` drops
from 8.8 ms to 1.0 ms.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@olivier-lacroix
olivier-lacroix force-pushed the perf/assign-result-arrays branch from 341f11a to 89c4c83 Compare October 25, 2026 03:32

This branch has not been deployed

No deployments
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