Feat/highs speedups - #898
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #898 +/- ##
==========================================
- Coverage 97.52% 97.38% -0.14%
==========================================
Files 39 39
Lines 5045 5092 +47
Branches 658 664 +6
==========================================
+ Hits 4920 4959 +39
- Misses 65 70 +5
- Partials 60 63 +3
🚀 New features to boost your workflow:
|
irm-codebase
left a comment
There was a problem hiding this comment.
Looks good. Had to do some brain churning to understand the method.
Overall it looks good. I trust our tests catch poor implementation thanks to our static result checkers.
Requests:
- Modify comments and docstrings to get rid of chaff
- Make sure test coverage is complete.
| Mirrors the broadcasting done by `_apply_func`, so that components added to | ||
| HiGHS in batches end up in arrays with the same shape, dims and coords as an | ||
| element-wise apply would produce. |
There was a problem hiding this comment.
Two things:
- Wouldn't other backends benefit from this approach, if it is more efficient?
If it can be demonstrated (e.g., viamemray, we should consider moving this to the parent class. - Let us avoid referencing other functions in comments (
_apply_func). Names change and this is bound to become obsolete.
| def _scatter_objects( | ||
| template: xr.DataArray, mask: np.ndarray, objs: list | ||
| ) -> xr.DataArray: | ||
| """Place backend objects at the `mask` positions of an otherwise-NaN array. |
There was a problem hiding this comment.
Another generic function that perhaps move upwards if other backends would benefit from it.
| status = self._instance.addCols( | ||
| n_new, | ||
| np.zeros(n_new), | ||
| np.asarray(lb_vals[mask], dtype=float), | ||
| np.asarray(ub_vals[mask], dtype=float), | ||
| 0, | ||
| np.zeros(n_new, dtype=np.int32), | ||
| np.empty(0, dtype=np.int32), | ||
| np.empty(0), |
There was a problem hiding this comment.
Trying to summarise this to see if I got the behaviour right.
- total new variables to add
- objective coefficient (0).
- lower bound
- upper bound
- coefficient in constraints (0)
- constraint coefficient rows indices (none)
- coefficient values (none).
Basically, this puts the variables in the model, does not initialise or set coefficients for them in the objective or the constraint matrix. Kind of 'non-parametric' variable initialisation.
| # Variables are added in one batch: `addVariable` allocates arrays and queries | ||
| # the model on every call, which dominates build time on large models. |
There was a problem hiding this comment.
Do not refer to code in comments (addVariable) to avoid confusion in the future.
It's fine to just state that this is more efficient in HiGHS, and state what this actually does (create non-parametric variables in the model).
| summed, mirroring `highs_linear_expression.unique_elements`. HiGHS rejects a | ||
| row that references the same column twice, so the merge is required, not an | ||
| optimisation. | ||
|
|
There was a problem hiding this comment.
Please adapt AI over explained text into actual docstrings.
| Entries are sorted by (row, column) and duplicate columns within a row are summed (3x + 3x + 3y = 6x + y). | |
| Otherwise, HiGHS would reject a row that references the same column twice. |
There was a problem hiding this comment.
Github suggestions are so, so broken nowadays :(
| # E.g. a coefficient whose absolute value is below `small_matrix_value`, | ||
| # which HiGHS drops, flagging it with a warning status. |
| lengths = np.empty(n_new, dtype=np.int64) | ||
| idxs: list[list[int]] = [] | ||
| vals: list[list[float]] = [] | ||
| for idx, expr in enumerate(exprs): |
There was a problem hiding this comment.
Another attempt of trying to summarise what this is doing.
This is the row equivalent of _add_variable above (which uses addCols).
- fetches the bonds from the expression and builds its compressed sparse row matrix equivalent (to fit HiGHS). This is a 'squashed' version of the matrix constructed by the model, built for higher efficiency. Note that if the problem is very 'dense' this will be less efficient than regular notation. In most applications sparse will win, though.
- starts: where each constraint 'begins'
- cols: which variables are referenced (
$3x_1 + 2x_3$ -> [1, 3]) - coefs: the coefficients (
$3x_1 + 2x_3$ -> [3, 2])
- get the current number of rows (constraints) already in the model to make sure we do not forget / overwrite them.
- Add the set of new constraints in one go via
addRows- n_new: number of constraints to add
- lower / upper: constraint bounds
- coefs.size: total number of coefficients being added.
- starts: where each constraint starts
- cols: variable index for the coefficient in each constraint
- coefs: value of each coefficient
- Check if all is well, blow up otherwise.
- Return the location of the new constraints in the backend so we can extract results later.
Fixes #
Summary of changes in this pull request
Reviewer checklist