Skip to content

Commit fe8d952

Browse files
committed
Moved copy to io.py, added deep-copy to all xarray operations.
1 parent bb6b8a4 commit fe8d952

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
@@ -53,6 +53,7 @@
5353
ScalarLinearExpression,
5454
)
5555
from linopy.io import (
56+
copy,
5657
to_block_files,
5758
to_cupdlpx,
5859
to_file,
@@ -1877,77 +1878,7 @@ def reset_solution(self) -> None:
18771878
self.variables.reset_solution()
18781879
self.constraints.reset_dual()
18791880

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

19521883
to_netcdf = to_netcdf
19531884

0 commit comments

Comments
 (0)