Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/lpspec/relational/engines/polars/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

#: The four frames a sink reads, as schemas. Stated here because the executor
#: is what fills them and an empty model still has to have them.
_COLS = ('col', 'lb', 'ub', 'vtype')
_COLS = ('lb', 'ub', 'vtype')
_OBJ = ('col', 'coeff')
_ROWS = ('row', 'sense', 'rhs')
_MATRIX = ('row', 'col', 'coeff')
Expand Down Expand Up @@ -159,12 +159,19 @@ def _build_variable(self, v: plan.VariableDeclaration) -> pl.DataFrame:
self._blocks[v.name] = (start, labelled.height)

bounded = self._q.bounds(labelled.lazy(), v)
cols = bounded.select(
pl.col('var_label').alias('col'),
pl.col('lb').cast(pl.Float64),
pl.col('ub').cast(pl.Float64),
pl.lit(v.variable_type, dtype=_DTYPES['vtype']).alias('vtype'),
).collect(engine='streaming')
# A declaration owns a contiguous run of labels and the blocks are
# concatenated in declaration order, so a frame sorted within each
# block is sorted across all of them — which makes a row's *position*
# its solver column index, and the column redundant.
cols = (
bounded.sort('var_label')
.select(
pl.col('lb').cast(pl.Float64),
pl.col('ub').cast(pl.Float64),
pl.lit(v.variable_type, dtype=_DTYPES['vtype']).alias('vtype'),
)
.collect(engine='streaming')
)

bad = cols.filter(pl.col('lb').is_null() | pl.col('ub').is_null()).height
if bad:
Expand Down
16 changes: 7 additions & 9 deletions src/lpspec/relational/sinks/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
class ModelTables:
"""The built model, as a sink sees it.

``cols`` (col, lb, ub, vtype), ``obj`` (col, coeff), ``rows`` (row, sense,
``cols`` (lb, ub, vtype), ``obj`` (col, coeff), ``rows`` (row, sense,
rhs) and ``matrix`` in COO (row, col, coeff). The scalars are what a sink
cannot cheaply recover; the objective constant lives outside the frames
because it has no column to attach to.
Expand Down Expand Up @@ -96,15 +96,13 @@ def dense_columns(self, infinity: float) -> DenseColumns:
import numpy as np

count = self.column_count
at = self.cols['col'].to_numpy()
lb = _scattered(count, at, self.cols['lb'].to_numpy(), -infinity)
ub = _scattered(count, at, self.cols['ub'].to_numpy(), infinity)
integral = _scattered(
count, at, self.cols.select(pl.col('vtype') != 'continuous').to_series().to_numpy(), False
)
# `copy=True` rather than in place: these are views of the frame now,
# and rewriting an infinity through one would edit the built model to
# suit whichever solver asked last.
lb = np.nan_to_num(self.cols['lb'].to_numpy(), copy=True, neginf=-infinity, posinf=infinity)
ub = np.nan_to_num(self.cols['ub'].to_numpy(), copy=True, neginf=-infinity, posinf=infinity)
integral = self.cols.select(pl.col('vtype') != 'continuous').to_series().to_numpy()
cost = _scattered(count, self.obj['col'].to_numpy(), self.obj['coeff'].to_numpy(), 0.0)
np.nan_to_num(lb, copy=False, neginf=-infinity, posinf=infinity)
np.nan_to_num(ub, copy=False, neginf=-infinity, posinf=infinity)
return lb, ub, cost, integral


Expand Down
4 changes: 2 additions & 2 deletions src/lpspec/relational/sinks/writers/lp_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def write_lp_file(model: ModelTables, path: str | Path) -> None:
objective = model.obj.lazy().sort('col').select(_term(pl.col('coeff'), pl.col('col')))
bounds = (
model.cols.lazy()
.sort('col')
.with_row_index('col')
.select(
pl.concat_str(
_bound(pl.col('lb'), '-infinity').alias('lb'),
Expand Down Expand Up @@ -89,7 +89,7 @@ def write_lp_file(model: ModelTables, path: str | Path) -> None:
_sink(bounds, f)

for variable_type, keyword in (('binary', 'binary'), ('integer', 'general')):
chosen = model.cols.lazy().filter(pl.col('vtype') == variable_type).sort('col')
chosen = model.cols.lazy().with_row_index('col').filter(pl.col('vtype') == variable_type)
if chosen.select(pl.len()).collect().item() == 0:
continue
f.write(f'\n{keyword}\n'.encode())
Expand Down