Commit 2bbbeef
feat(persistent): in-place solver updates (Solver framework + HiGHS/Gurobi/Xpress/Mosek) (#718)
* feat(constraints): add _coef_dirty flag + rhs setter short-circuit
Tracks per-Constraint coefficient mutation via a single boolean slot,
flipped in coeffs/vars/lhs setters. Pure-constant rhs writes now
short-circuit and leave coeffs/vars buffers untouched (by identity),
so rhs-only updates don't trigger expensive coefficient recompare on
the persistent-solver fast path.
* feat(persistent): add ModelSnapshot, CoefPattern, StructuralKey
Pure-Python snapshot primitives for the persistent-solver Phase 1.
Deep-copies value-side fields (var_lb/ub, con_rhs/sign, obj_linear),
holds vlabels/clabels by reference, stores canonical CSR
(indptr, indices) per constraint container. No Solver import.
* feat(persistent): add ModelDiff and compute_diff
Pure-function diff for the persistent-solver Phase 1. Detects
structural, coord, sparsity, quadratic-objective, value-only var/con,
and objective-linear/sense changes. Supports same_model fast path
via _coef_dirty and cross-model full re-scan. Includes a focused
test suite covering capture, mutation paths, deep-copy invariant,
and the same_model toggle.
* feat(solvers): add persistent-update orchestration to Solver
- supports_persistent_update class flag (default False)
- snapshot/_rebuilds/_in_place_updates/_last_rebuild_reason fields
- snapshot capture at end of direct _build, _clear_coef_dirty helper
- apply_update stub raising UnsupportedUpdate
- solve(model, assign) dispatcher with diff-or-rebuild path
- update(model, apply=True) primitive returning ModelDiff
- threading.Lock around diff+apply+resnapshot
- __getstate__/__setstate__ drop native handle and snapshot
* test(persistent): smoke test Solver orchestrator with fake backend
* feat(solvers): short-circuit rebuild when backend lacks persistent-update support
Skip diff computation entirely when supports_persistent_update is False
on apply, per plan: 'dispatcher checks flag before calling — if False,
skips diffing entirely and goes to rebuild.'
* feat(solvers): Gurobi apply_update for persistent solves
* feat(solvers): HiGHS apply_update for persistent solves
* test(persistent): cross-model, pickle, threading, failure-path coverage
* refactor(persistent): row-block numpy snapshot/diff
Replace xarray-based snapshot and CSR pattern compare with per-row
canonicalised numpy buffers; new ContainerVarUpdate / ContainerRowUpdate
payloads. Gurobi/HiGHS apply_update rewritten around batched setAttr /
changeColsBounds / changeColsCost / changeColsIntegrality; coefficient
writes touch only changed cells. Cross-model diff now ~matches same-model
cost for bound/rhs/coef-value sweeps.
* feat(persistent): opt-in coord-equality via ignore_dims
compute_diff/Solver.solve/Solver.update grow an ignore_dims kwarg.
None (default) keeps the current no-coord-check behaviour;
any iterable opts into per-container coord-equality on every dim
not in the set, supporting rolling-horizon workflows where e.g.
the snapshot dim is expected to drift.
* feat(persistent): lazy-build Solver, ModelDiff constructors, disallow_rebuild
- Solver.from_name now accepts model=None; the first solve(m, ...) builds.
- compute_diff folded into ModelDiff.from_snapshot classmethod; new
ModelDiff.from_models diffs two linopy models directly.
- Solver.solve grows disallow_rebuild=True, which raises
RebuildRequiredError instead of falling back to a rebuild.
* feat(persistent): opt-in update tracking, snapshot-free ModelDiff.from_models
- Add `track_updates` flag (default False) to Solver; skip ModelSnapshot
capture when disabled. Raise UpdatesDisabledError on solve(model)/update()
if a built solver was constructed without tracking.
- Rewrite ModelDiff.from_models to build directly from two models without
capturing snapshots; share helpers with from_snapshot.
- Update persistent tests to opt into track_updates=True; add coverage
for the disabled path.
* feat(persistent): wire ModelDiff.from_models for track_updates=False
Cross-instance resolves now diff via from_models against the previously
built model, with no snapshot. Same-instance mutation still raises
UpdatesDisabledError. Snapshot recapture is skipped in this mode.
Add cross-instance solve/update tests for the no-snapshot path.
* refactor(persistent): cleanups, VarKind enum, fold _clear_coef_dirty
Collapse _diff_objective QUAD_OBJ branches; cache n_coef_updates;
short-circuit _canonicalize_rows when rows already sorted; tighten
buffer extraction. Introduce VarKind enum used across snapshot/diff
and HiGHS/Gurobi apply_update; reuse linopy.constants sign tokens.
Move _clear_coef_dirty into ModelSnapshot.capture.
* refactor(persistent): CSR-backed ContainerConBuffers
Source con buffers from Constraint.to_matrix_with_rhs, replacing the
dense (n_rows, max_n_term) arrays with CSR (indptr, indices, data).
Sign dtype adopts 'U1' across the persistent layer and apply_update
in HiGHS/Gurobi consumes CSR-slice payloads instead of -1 masks.
Deletes _canonicalize_rows and the _INT64_MAX sentinel.
* refactor(persistent): flat-native ModelDiff storage
Replace per-container ContainerVarUpdate/ContainerRowUpdate dicts with
flat arrays (var_bounds_*, var_type_*, con_coef_* COO, con_rhs_*,
con_sign_*) plus VarSlice/ConSlice per-container offsets for
diagnostics. Add con_rhs_as_bounds() for ranged-row solvers. Backend
apply_update bodies collapse to flat-array calls; remove duplicated
label->position resolution.
* feat(persistent): apply_update for Xpress and Mosek
Implement in-place model updates for Xpress (chgbounds/chgrhs/chgmcoef/
chgrowtype/chgobj/chgobjsense/chgcoltype) and Mosek (chgvarbound/
chgconbound/putaijlist/putclist/putvartypelist/putobjsense). Mosek
rejects constraint sign change to trigger rebuild. Consolidate
gurobi/highs apply_update tests into a single parametrized file that
also covers xpress and mosek.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* fix(persistent): serialize concurrent solves; satisfy mypy
* hold solver lock through _run_direct so two threads calling
solve(model) on the same Solver no longer race on the native handle
(HiGHS returned 0.0 from the second concurrent solve).
* narrow Optional ndarrays in persistent.diff.push_var / push_con and
in HiGHS/Gurobi/Xpress/Mosek apply_update objective paths.
* widen Constraint.rhs setter to ExpressionLike | VariableLike |
ConstantLike to match the as_expression call in the body.
* widen Constraints.__getitem__(str) return type to Constraint (the
dominant case) so tests can set .rhs/.coeffs/.sign without ignores.
* add docs for in-place solver updates.
* harden coords comparison
* Variable.update() / Constraint.update() as canonical mutation API (#727)
* feat: Variable.update() / Constraint.update() as canonical mutation API
Introduces typed ``.update()`` methods on Variable and Constraint as
the single, validated, multi-attribute mutation entry point.
- ``Variable.update(lower=, upper=)``: validates non-constant
inputs are rejected, new dims are rejected, and the resulting
``lower <= upper`` invariant holds across all coords. Returns
``self`` for chaining.
- ``Constraint.update(rhs=, sign=)``: constant RHS only. The
legacy ``c.rhs = variable`` rearrange-to-lhs path stays on the
setter (different semantic, deserves its own explicit method).
The existing ``.lower`` / ``.upper`` / ``.sign`` setters become
thin shims that forward to ``.update()``, so single-attribute
writes (``z.lower = 2``) stay ergonomic and the canonical
validation runs in one place. The ``.rhs`` setter forwards
constants through ``.update()`` and keeps the expression-rhs
rearrange behaviour.
This is the on-top experiment for the design discussion on #718.
``.lhs`` / ``.coeffs`` / ``.vars`` setters are untouched for now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(update): Constraint.update accepts Variable/Expression rhs
Mirrors the existing ``c.rhs = expr`` setter and ``add_constraints``
which both accept mixed-side input and rearrange the residual onto
lhs. ``c.update(rhs=x + 5)`` now subtracts ``x`` from lhs and stores
``5`` on rhs. ``.rhs`` setter collapses to a one-line shim.
Variable bound rejection of Variable/Expression is kept (bounds are
numeric, not symbolic); docstring clarified to spell out that
pandas / xarray / numpy arrays are first-class (time-varying bounds).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(update): extend Constraint.update to lhs/coeffs/vars; shim all setters
Adds lhs / coeffs / vars to the canonical mutation API. All
.lhs / .coeffs / .vars setters now forward to .update() — every
Constraint mutation goes through one method with one validation
path, one place that flips _coef_dirty.
Composition rules:
- lhs= replaces the whole expression first; subsequent rhs=
rearrangement (Variable/Expression in rhs) sees the new lhs.
- lhs= and coeffs= / vars= are mutually exclusive (whole
replacement vs partial array update).
- sign= is applied last so it composes cleanly.
Internal Constraint.sanitize_zeros migrated to update(vars=,
coeffs=) — no more internal setter calls in linopy/.
389 tests pass across mutation + persistent-solver suite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(update): rename Constraint.update kwarg vars= -> variables=
Avoids shadowing Python's vars() builtin. The .vars attribute on
Constraint stays (it parallels the .data.vars internal name);
only the kwarg gets the unambiguous spelling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(update): accept positional ConstraintLike in Constraint.update
Mirrors add_constraints' dispatch: c.update(x + 5 <= 3) is now
shorthand for c.update(lhs=x, sign='<=', rhs=-2), extracted from
the AnonymousConstraint / ConstraintBase the comparison produces.
Mutually exclusive with the per-attribute kwargs; clear error when
mixed.
Also reverts the internal sanitize_zeros migration. The setters
are pure shims forwarding to update(), so the migration didn't
change behaviour or cost — just spelling. The original setter
syntax reads more naturally there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(update): note kwarg form is the targeted, cheap path
The positional ConstraintLike form (c.update(x + 5 <= 3)) always
rewrites lhs / sign / rhs and flips _coef_dirty. For hot loops that
only touch one part, kwarg form (c.update(rhs=...)) skips the
unchanged attributes and is materially cheaper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(persistent): default ModelDiff.from_snapshot(same_model=False)
Closes the A1 residual from the #718 review. The flag-trust path
(`skip_coef_compare = same_model and not coef_dirty`) is correct
through Constraint.update() (set in one place, shims forward), but
`c.coeffs.values[...] = ...` still bypasses _coef_dirty. With
same_model=True as the default, that bypass silently produces wrong
diffs.
Flip the default to False. Cross-model paths (the only production
caller, Solver._update_locked, passes explicitly) are unaffected.
Same-model warm-update paths now value-diff the CSR data — small
perf hit (50-200ms at Mayk-scale per Mayk's bench), correct by
default. Solver-aware callers who own the mutation contract can
opt back into the optimization with `same_model=True`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: teach .update() in tutorials; mark setters as syntactic sugar
- examples/manipulating-models.ipynb: rewrite mutation cells to use
Variable.update / Constraint.update; setter form is mentioned in
notes as syntactic sugar for the same call.
- examples/creating-constraints.ipynb: reframe the CSRConstraint vs
Constraint API table around .update() as the mutation API; setters
are sugar.
- Setter docstrings now say 'syntactic sugar for Constraint/Variable
.update; do not add logic here so the contract stays single-sourced'
— a directive to future contributors as much as to readers.
No deprecation, no breaking change. .update() is the documented
canonical mutation API; the seven setters continue to exist as
one-line shims.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* deprecate(update): warn on mutation setters; promote .update() in docs
Adds DeprecationWarning to all seven mutation setters (Variable.lower,
Variable.upper, Constraint.coeffs, Constraint.vars, Constraint.sign,
Constraint.rhs, Constraint.lhs). Each setter still forwards to .update()
so existing code keeps working; the warning points at the canonical API.
Internal sanitize_zeros migrated off setters (the last linopy/ caller).
api.rst gains Modification sections listing .update() for both Variable
and Constraint; tutorial notes rewritten to teach .update() and flag
setters as deprecated. Release note added.
dual.setter / solution.setter untouched — result assignment, not
mutation, different deprecation track.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(update): edge-case coverage; document rhs-rearrangement invariant
Constraint.update tests: lhs-only, coeffs-only (vars preserved),
compound lhs+sign, mutually-exclusive lhs+coeffs and lhs+variables.
Variable.update tests: upper-only, valid array bound.
Migrate test_constraint_coef_dirty.py from the now-deprecated setters
to .update(), exercising the canonical path; add positional-form and
no-op cases. Net effect: same dirty-flag invariants, 7 fewer warnings
per pytest run.
Docs: Constraint.update rhs= gains a worked example showing the two
forms (constant vs variable/expression). add_constraints rhs gets a
matching note pointing at the linopy invariant so the rearrangement
rule is documented at the creation site too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* review(update): address inline feedback on #727
- Constraint._assign_lhs_expr → _assign_lhs (drop redundant suffix;
the method already takes a LinearExpression, so the type was in the
signature, not the name).
- Add Constraint._assign_data(**fields) helper. Wraps the four
``self._data = assign_multiindex_safe(self.data, **kw)`` callsites
inside update() (rhs / coeffs / vars / sign). Untouched: the same
pattern in dual.setter, sanitize_missings, sanitize_infinities —
those aren't update() and stay out of scope here.
- Add types.CONSTANT_TYPES tuple, derived from ConstantLike via
get_args so the two cannot drift. Variable.update bound validation
flipped from negative (isinstance against a hand-rolled
non_constant tuple) to positive (isinstance against CONSTANT_TYPES);
drops a redundant in-function ``from linopy import expressions``
(the module-level import already covered it).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* deprecate(update): FutureWarning on DataArray as variables=; extract _validate_update
Constraint
- sanitize_zeros now writes _data via _assign_data directly (no
longer round-trips through update(variables=DataArray), which
would self-trigger the new deprecation warning).
- Constraint.update(variables=...) emits FutureWarning when passed a
raw DataArray of integer labels; Variable is the supported input.
The path stays accepted for back-compat and will be removed
alongside the v1 cleanup.
Variable
- Extract Variable._validate_update(*, lower, upper) — validates,
broadcasts, and runs the cross-bound (lb<=ub) check, returning a
dict ready for assignment. update() body shrinks to ~3 lines.
Coord validation (parity with add_variables) deferred to #726 land.
Tests
- test_constraint_coef_dirty's variables= test now passes a Variable
instead of a DataArray (matches the supported input).
- test_constraint_vars_setter_with_array wrapped in
pytest.warns(FutureWarning) — locks in the deprecation contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(constraints): _assign_data → _update_data; manage _coef_dirty inside
The helper now writes fields AND flips _coef_dirty when the written
set includes coeffs or vars. Callsites in update() (coeffs / vars
branches) and sanitize_zeros drop their explicit `self._coef_dirty = True`
lines — the rule lives in one place, can't be forgotten by future
field additions.
rhs / sign writes still don't dirty (correctly). _assign_lhs is
untouched: it uses a different write mechanic (drop_vars + assign)
and manages its own flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* revert(update): rename Constraint.update kwarg variables= back to vars=
Restores symmetry with Constraint.vars (property) and data.vars
(underlying xarray Dataset key) — the original rename traded one
small asymmetry (read vs. mutate kwarg) for a worse one (Python
property name vs. Dataset key name).
The `vars()` builtin shadowing inside the kwarg position is benign:
we never call `vars()` here, and dropping the rename also lets the
top-level `linopy.variables` module be used directly inside the
function body instead of importing `Variable as _Variable` to dodge
the kwarg shadow.
Closes #730.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* restore(update): keep Constraint.update kwarg as variables=
Reverts cf845e1. Under the A/B framing for the .vars naming question,
update(variables=...) is Category A — it accepts a linopy.Variable.
The previous "restore symmetry with .vars / data.vars" argument
conflated two layers:
- Public Python API speaks about linopy variables → variables=
- Internal xarray Dataset key stays as "vars" (xarray collision
on Dataset.variables blocks renaming the key)
The asymmetry between property/kwarg name and Dataset key name is
principled (API layer vs. storage layer), not arbitrary — same
pattern ORMs / serializers use. Keeping variables= here lines up
with the broader .vars → .variables direction now being considered
for properties.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: ensure dims in solution assignment
* refactor(persistent): address review round
ModelDiff.from_snapshot/from_models return RebuildReason on rebuild (NONE dropped);
diff walkers moved onto _DiffBuilder with context in __init__, single _cat helper.
Snapshot buffers share constraint arrays (identity fast path); CSRConstraint.sanitize_zeros copy-on-write.
Use isinstance(val, ConstantLike) in Variable._validate_update.
* fix: CI failures after master merge
Variable.fix()/unfix() set both bounds atomically via update() instead of
sequential deprecated setters (tripped new lower<=upper cross-validation).
Fail fast on quadratic lhs in Constraint.update; type-narrowing fixes for mypy.
* refactor(persistent): lazy COO expansion of coefficient diffs
ModelDiff stores per-container _CoefDelta (changed rows referencing the
CSR buffers); con_coef_rows/cols/vals materialize on first access via
cached property. Expansion is now vectorized; backends guard on
n_coef_updates. Follows coroa's suggestion on PR 718.
* refactor(persistent): assemble snapshot from diff walk; pure capture (A2+A6)
_DiffBuilder records target buffers/coords; ModelDiff.snapshot replaces the
O(nnz) re-capture after in-place updates. ModelSnapshot.capture no longer
mutates the model: the _coef_dirty clear moves to the solver, coupled to
snapshot adoption (build + successful apply, never on apply=False).
* refactor(persistent): template-method apply_update (A4, D2-D4)
Base Solver orchestrates the diff sections and validates up front (sign
support; Mosek semi-continuous now fails before any native mutation);
backends implement _apply_* hooks. Binary [0,1] re-clamp lifted to base
with Gurobi no-op (VType 'B' implies bounds natively). self.sense now
set uniformly; HiGHS vtype map cached; Xpress/Mosek list-conversion helpers.
* refactor(persistent): lock-free diff preview, document solve atomicity (A5)
update(model, apply=False) computes the diff without the solver lock
(immutable snapshot buffers, same_model=False since _coef_dirty cannot be
trusted concurrently). solve keeps the coarse lock: apply->run must be
atomic and native handles are not thread-safe. Tests pin the non-blocking
preview and the preview/apply asymmetry for raw .values mutations.
* refactor(persistent): split diff/apply, namedtuple ctx (coroa review)
Replace _update_locked(apply=...) with _compute_diff + _apply_locked,
dropping the dead apply=False branches. Read supports_* off the instance
and give Gurobi's apply context a _GurobiApplyCtx namedtuple.
* chore(codespell): whitelist 'coo' (sparse COO format) to fix false positive
* refactor(persistent): delegate from_models to from_snapshot
ModelDiff.from_models now captures a snapshot of model_a and defers to
from_snapshot(same_model=False), removing the duplicated baseline-extraction
and diff loops.
* refactor(persistent): drop dead per-container structural guards
The global _structural_reason precheck (via vlabels/clabels, the
concatenation of per-container active_labels) already pins each
container's active_labels and shape before diff_var/diff_con run, so the
per-container shape/active_labels mismatch branches were unreachable
duplication that silently degraded to a rebuild. Removed from diff_var,
diff_con, and diff_objective.
* test(persistent): cover diff rebuild reasons and snapshot var kinds
Adds coverage for QUAD_OBJ, variable-type change, sign-only mutation,
top-level STRUCTURAL_LABELS (vlabels/clabels), indices SPARSITY, binary/
integer/semi-continuous capture, and ModelDiff inspect/repr helpers.
persistent/diff.py 81->96%, snapshot.py 87->99%.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Felix <117816358+FBumann@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 8dfa6d4 commit 2bbbeef
21 files changed
Lines changed: 3569 additions & 103 deletions
File tree
- doc
- examples
- linopy
- persistent
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
33 | 34 | | |
34 | 35 | | |
35 | 36 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
139 | 143 | | |
140 | 144 | | |
141 | 145 | | |
| 146 | + | |
142 | 147 | | |
143 | 148 | | |
144 | 149 | | |
| |||
332 | 337 | | |
333 | 338 | | |
334 | 339 | | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
335 | 353 | | |
336 | 354 | | |
337 | 355 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
7 | 18 | | |
8 | 19 | | |
9 | | - | |
| 20 | + | |
10 | 21 | | |
11 | 22 | | |
12 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
13 | 29 | | |
14 | 30 | | |
15 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
348 | 348 | | |
349 | 349 | | |
350 | 350 | | |
351 | | - | |
| 351 | + | |
352 | 352 | | |
353 | 353 | | |
354 | 354 | | |
355 | 355 | | |
356 | 356 | | |
357 | 357 | | |
358 | 358 | | |
359 | | - | |
360 | | - | |
| 359 | + | |
| 360 | + | |
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
77 | | - | |
| 77 | + | |
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| |||
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
86 | | - | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
87 | 90 | | |
88 | 91 | | |
89 | 92 | | |
| |||
127 | 130 | | |
128 | 131 | | |
129 | 132 | | |
130 | | - | |
| 133 | + | |
131 | 134 | | |
132 | 135 | | |
133 | 136 | | |
| |||
157 | 160 | | |
158 | 161 | | |
159 | 162 | | |
160 | | - | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
161 | 166 | | |
162 | | - | |
| 167 | + | |
| 168 | + | |
163 | 169 | | |
164 | 170 | | |
165 | 171 | | |
| |||
169 | 175 | | |
170 | 176 | | |
171 | 177 | | |
172 | | - | |
| 178 | + | |
173 | 179 | | |
174 | 180 | | |
175 | 181 | | |
| |||
178 | 184 | | |
179 | 185 | | |
180 | 186 | | |
181 | | - | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
182 | 191 | | |
183 | 192 | | |
184 | 193 | | |
| |||
212 | 221 | | |
213 | 222 | | |
214 | 223 | | |
215 | | - | |
| 224 | + | |
216 | 225 | | |
217 | 226 | | |
218 | 227 | | |
| |||
221 | 230 | | |
222 | 231 | | |
223 | 232 | | |
224 | | - | |
225 | | - | |
226 | | - | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
227 | 242 | | |
228 | 243 | | |
229 | 244 | | |
| |||
0 commit comments