Skip to content

Commit 787c4ce

Browse files
committed
feat(xpress): add direct io
perf(xpress): faster and logging perf(xpress): int32 index support perf: numpy matrix building perf: numpy filter missing perf: cache data perf: raw numpy array ops for other components refactor: clean up
1 parent 0a40d2c commit 787c4ce

8 files changed

Lines changed: 749 additions & 77 deletions

File tree

linopy/constraints.py

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,32 +1091,120 @@ def flat(self) -> pd.DataFrame:
10911091
df["key"] = df.labels.map(map_labels)
10921092
return df
10931093

1094+
def to_polars(self) -> pl.DataFrame:
1095+
"""
1096+
Convert all constraints to a single polars DataFrame.
1097+
1098+
The resulting dataframe is a long format with columns
1099+
`labels`, `coeffs`, `vars`, `rhs`, `sign`, `key`.
1100+
"""
1101+
dfs = [self[k].to_polars() for k in self]
1102+
if not dfs:
1103+
return pl.DataFrame(
1104+
{
1105+
"labels": pl.Series([], dtype=pl.Int64),
1106+
"coeffs": pl.Series([], dtype=pl.Float64),
1107+
"vars": pl.Series([], dtype=pl.Int64),
1108+
"sign": pl.Series([], dtype=pl.String),
1109+
"rhs": pl.Series([], dtype=pl.Float64),
1110+
"key": pl.Series([], dtype=pl.Int64),
1111+
}
1112+
)
1113+
1114+
df = pl.concat(dfs, how="vertical_relaxed")
1115+
labels = (
1116+
df.select("labels")
1117+
.unique(maintain_order=True)
1118+
.with_row_index(name="key", offset=0)
1119+
)
1120+
return df.join(labels, on="labels", how="left")
1121+
10941122
def to_matrix(self, filter_missings: bool = True) -> scipy.sparse.csc_matrix:
10951123
"""
10961124
Construct a constraint matrix in sparse format.
10971125
10981126
Missing values, i.e. -1 in labels and vars, are ignored filtered
10991127
out.
11001128
"""
1101-
# TODO: rename "filter_missings" to "~labels_as_coordinates"
1102-
cons = self.flat
1103-
11041129
if not len(self):
11051130
raise ValueError("No constraints available to convert to matrix.")
11061131

1132+
def _build_dense_key_map(
1133+
arrays: list[np.ndarray], total_size: int
1134+
) -> tuple[np.ndarray, int]:
1135+
mapping = np.full(total_size, -1, dtype=np.int64)
1136+
next_key = 0
1137+
for labels in arrays:
1138+
labels = labels[labels != -1]
1139+
n = labels.size
1140+
if n:
1141+
mapping[labels] = np.arange(next_key, next_key + n)
1142+
next_key += n
1143+
return mapping, next_key
1144+
1145+
# Build sparse triplets directly from NumPy arrays to avoid dataframe overhead.
1146+
row_parts: list[np.ndarray] = []
1147+
col_parts: list[np.ndarray] = []
1148+
data_parts: list[np.ndarray] = []
1149+
constraint_labels: list[np.ndarray] = []
1150+
1151+
for _, constraint in self.items():
1152+
labels = constraint.labels.values.reshape(-1)
1153+
constraint_labels.append(labels)
1154+
vars_arr = constraint.vars.values
1155+
coeffs_arr = constraint.coeffs.values
1156+
1157+
term_axis = constraint.vars.get_axis_num(constraint.term_dim)
1158+
if term_axis != vars_arr.ndim - 1:
1159+
vars_arr = np.moveaxis(vars_arr, term_axis, -1)
1160+
coeffs_arr = np.moveaxis(coeffs_arr, term_axis, -1)
1161+
1162+
nterm = vars_arr.shape[-1]
1163+
row = np.repeat(labels, nterm)
1164+
col = vars_arr.reshape(-1)
1165+
data = coeffs_arr.reshape(-1)
1166+
1167+
mask = (row != -1) & (col != -1) & (data != 0)
1168+
if mask.any():
1169+
row_parts.append(row[mask])
1170+
col_parts.append(col[mask])
1171+
data_parts.append(data[mask])
1172+
1173+
if row_parts:
1174+
row = np.concatenate(row_parts)
1175+
col = np.concatenate(col_parts)
1176+
data = np.concatenate(data_parts)
1177+
else:
1178+
row = np.array([], dtype=np.int64)
1179+
col = np.array([], dtype=np.int64)
1180+
data = np.array([], dtype=float)
1181+
11071182
if filter_missings:
1108-
vars = self.model.variables.flat
1109-
shape = (cons.key.max() + 1, vars.key.max() + 1)
1110-
cons["vars"] = cons.vars.map(vars.set_index("labels").key)
1111-
return scipy.sparse.csc_matrix(
1112-
(cons.coeffs, (cons.key, cons.vars)), shape=shape
1183+
cons_map, next_con_key = _build_dense_key_map(
1184+
constraint_labels, self.model._cCounter
11131185
)
1114-
else:
1115-
shape = self.model.shape
1116-
return scipy.sparse.csc_matrix(
1117-
(cons.coeffs, (cons.labels, cons.vars)), shape=shape
1186+
vars_map, next_var_key = _build_dense_key_map(
1187+
[
1188+
var.labels.values.reshape(-1)
1189+
for _, var in self.model.variables.items()
1190+
],
1191+
self.model._xCounter,
11181192
)
11191193

1194+
shape = (next_con_key, next_var_key)
1195+
1196+
row = cons_map[row]
1197+
col = vars_map[col]
1198+
keep = (row != -1) & (col != -1)
1199+
if not keep.all():
1200+
row = row[keep]
1201+
col = col[keep]
1202+
data = data[keep]
1203+
return scipy.sparse.csc_matrix((data, (row, col)), shape=shape)
1204+
1205+
shape = self.model.shape
1206+
return scipy.sparse.csc_matrix((data, (row, col)), shape=shape)
1207+
11201208
def reset_dual(self) -> None:
11211209
"""
11221210
Reset the stored solution of variables.

0 commit comments

Comments
 (0)