Skip to content

Commit 3dc0ef0

Browse files
committed
Merge branch 'copy' into dual
2 parents f45c49c + fe8d952 commit 3dc0ef0

2 files changed

Lines changed: 87 additions & 71 deletions

File tree

linopy/io.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,3 +1239,88 @@ def get_prefix(ds: xr.Dataset, prefix: str) -> xr.Dataset:
12391239
setattr(m, k, ds.attrs.get(k))
12401240

12411241
return m
1242+
1243+
1244+
def copy(src: Model, include_solution: bool = False) -> Model:
1245+
"""
1246+
Return a deep copy of this model.
1247+
1248+
Copies variables, constraints, objective, parameters, blocks, and all
1249+
scalar attributes (counters, flags). The copy is fully independent:
1250+
modifying one does not affect the other.
1251+
1252+
Parameters
1253+
----------
1254+
src : Model
1255+
The model to copy.
1256+
include_solution : bool, optional
1257+
Whether to include the current solution and dual values in the copy.
1258+
If False (default), the copy is returned in an initialized state:
1259+
solution and dual data are excluded, objective value is set to None,
1260+
and status is set to 'initialized'. If True, solution, dual values,
1261+
solve status, and objective value are also copied.
1262+
1263+
Returns
1264+
-------
1265+
Model
1266+
A deep copy of the model.
1267+
"""
1268+
from linopy.model import (
1269+
Constraint,
1270+
Constraints,
1271+
LinearExpression,
1272+
Model,
1273+
Objective,
1274+
Variable,
1275+
Variables,
1276+
)
1277+
1278+
SOLVE_STATE_ATTRS = {"status", "termination_condition"}
1279+
1280+
m = Model(
1281+
chunk=src._chunk,
1282+
force_dim_names=src._force_dim_names,
1283+
auto_mask=src._auto_mask,
1284+
solver_dir=str(src._solver_dir),
1285+
)
1286+
1287+
m._variables = Variables(
1288+
{
1289+
name: Variable(
1290+
var.data.copy(deep=True)
1291+
if include_solution
1292+
else var.data[src.variables.dataset_attrs].copy(deep=True),
1293+
m,
1294+
name,
1295+
)
1296+
for name, var in src.variables.items()
1297+
},
1298+
m,
1299+
)
1300+
1301+
m._constraints = Constraints(
1302+
{
1303+
name: Constraint(
1304+
con.data.copy(deep=True)
1305+
if include_solution
1306+
else con.data[src.constraints.dataset_attrs].copy(deep=True),
1307+
m,
1308+
name,
1309+
)
1310+
for name, con in src.constraints.items()
1311+
},
1312+
m,
1313+
)
1314+
1315+
obj_expr = LinearExpression(src.objective.expression.data.copy(deep=True), m)
1316+
m._objective = Objective(obj_expr, m, src.objective.sense)
1317+
m._objective._value = src.objective.value if include_solution else None
1318+
1319+
m._parameters = src._parameters.copy(deep=True)
1320+
m._blocks = src._blocks.copy(deep=True) if src._blocks is not None else None
1321+
1322+
for attr in src.scalar_attrs:
1323+
if include_solution or attr not in SOLVE_STATE_ATTRS:
1324+
setattr(m, attr, getattr(src, attr))
1325+
1326+
return m

linopy/model.py

Lines changed: 2 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
ScalarLinearExpression,
5555
)
5656
from linopy.io import (
57+
copy,
5758
to_block_files,
5859
to_cupdlpx,
5960
to_file,
@@ -1878,77 +1879,7 @@ def reset_solution(self) -> None:
18781879
self.variables.reset_solution()
18791880
self.constraints.reset_dual()
18801881

1881-
def copy(self, include_solution: bool = False) -> Model:
1882-
"""
1883-
Return a deep copy of this model.
1884-
1885-
Copies variables, constraints, objective, parameters, blocks, and all
1886-
scalar attributes (counters, flags). The copy is fully independent:
1887-
modifying one does not affect the other.
1888-
1889-
Parameters
1890-
----------
1891-
include_solution : bool, optional
1892-
Whether to include the current solution and dual values in the copy.
1893-
If False (default), the copy is returned in an initialized state:
1894-
solution and dual data are excluded, objective value is set to None,
1895-
and status is set to 'initialized'. If True, solution, dual values,
1896-
solve status, and objective value are also copied.
1897-
1898-
Returns
1899-
-------
1900-
Model
1901-
A deep copy of the model.
1902-
"""
1903-
SOLVE_STATE_ATTRS = {"status", "termination_condition"}
1904-
1905-
m = Model(
1906-
chunk=self._chunk,
1907-
force_dim_names=self._force_dim_names,
1908-
auto_mask=self._auto_mask,
1909-
solver_dir=str(self._solver_dir),
1910-
)
1911-
1912-
m._variables = Variables(
1913-
{
1914-
name: Variable(
1915-
var.data.copy()
1916-
if include_solution
1917-
else var.data[self.variables.dataset_attrs].copy(deep=True),
1918-
m,
1919-
name,
1920-
)
1921-
for name, var in self.variables.items()
1922-
},
1923-
m,
1924-
)
1925-
1926-
m._constraints = Constraints(
1927-
{
1928-
name: Constraint(
1929-
con.data.copy()
1930-
if include_solution
1931-
else con.data[self.constraints.dataset_attrs].copy(deep=True),
1932-
m,
1933-
name,
1934-
)
1935-
for name, con in self.constraints.items()
1936-
},
1937-
m,
1938-
)
1939-
1940-
obj_expr = LinearExpression(self.objective.expression.data.copy(), m)
1941-
m._objective = Objective(obj_expr, m, self.objective.sense)
1942-
m._objective._value = self.objective.value if include_solution else None
1943-
1944-
m._parameters = self._parameters.copy(deep=True)
1945-
m._blocks = self._blocks.copy() if self._blocks is not None else None
1946-
1947-
for attr in self.scalar_attrs:
1948-
if include_solution or attr not in SOLVE_STATE_ATTRS:
1949-
setattr(m, attr, getattr(self, attr))
1950-
1951-
return m
1882+
copy = copy
19521883

19531884
to_netcdf = to_netcdf
19541885

0 commit comments

Comments
 (0)