diff --git a/linopy/constraints.py b/linopy/constraints.py index 8c6a7b96..2bdba6e9 100644 --- a/linopy/constraints.py +++ b/linopy/constraints.py @@ -23,7 +23,6 @@ LocIndexer, align_lines_by_delimiter, format_string_as_variable_name, - forward_as_properties, generate_indices_for_printout, get_label_position, has_optimized_model, @@ -62,18 +61,6 @@ def _con_unwrap(con): return con.data if isinstance(con, Constraint) else con -@forward_as_properties( - data=[ - "attrs", - "coords", - "indexes", - "dims", - "sizes", - ], - labels=["values"], - lhs=["nterm"], - rhs=["ndim", "shape", "size"], -) class Constraint: """ Projection to a single constraint in a model. @@ -129,6 +116,76 @@ def __getitem__(self, selector) -> "Constraints": data = Dataset({k: self.data[k][selector] for k in self.data}, attrs=self.attrs) return self.__class__(data, self.model, self.name) + @property + def attrs(self): + """ + Get the attributes of the constraint. + """ + return self.data.attrs + + @property + def coords(self): + """ + Get the coordinates of the constraint. + """ + return self.data.coords + + @property + def indexes(self): + """ + Get the indexes of the constraint. + """ + return self.data.indexes + + @property + def dims(self): + """ + Get the dimensions of the constraint. + """ + return self.data.dims + + @property + def sizes(self): + """ + Get the sizes of the constraint. + """ + return self.data.sizes + + @property + def values(self): + """ + Get the label values of the constraint. + """ + return self.labels.values if self.is_assigned else None + + @property + def nterm(self): + """ + Get the number of terms in the constraint. + """ + return self.lhs.nterm + + @property + def ndim(self): + """ + Get the number of dimensions of the constraint. + """ + return self.rhs.ndim + + @property + def shape(self): + """ + Get the shape of the constraint. + """ + return self.rhs.shape + + @property + def size(self): + """ + Get the size of the constraint. + """ + return self.rhs.size + @property def loc(self): return LocIndexer(self) @@ -392,10 +449,6 @@ def dual(self, value): value = DataArray(value).broadcast_like(self.labels) self.data["dual"] = value - @property - def shape(self): - return self.labels.shape - @classmethod def from_rule(cls, model, rule, coords): """ diff --git a/linopy/expressions.py b/linopy/expressions.py index 27e253be..5d39e411 100644 --- a/linopy/expressions.py +++ b/linopy/expressions.py @@ -242,7 +242,6 @@ def sum(self, **kwargs): return LinearExpression(ds, self.model) -@forward_as_properties(data=["attrs", "coords", "indexes", "sizes"], const=["ndim"]) class LinearExpression: """ A linear expression consisting of terms of coefficients and variables. @@ -589,6 +588,41 @@ def __getitem__(self, selector) -> Union["LinearExpression", "QuadraticExpressio data = Dataset({k: self.data[k][selector] for k in self.data}, attrs=self.attrs) return self.__class__(data, self.model) + @property + def attrs(self): + """ + Get the attributes of the expression + """ + return self.data.attrs + + @property + def coords(self): + """ + Get the coordinates of the expression + """ + return self.data.coords + + @property + def indexes(self): + """ + Get the indexes of the expression + """ + return self.data.indexes + + @property + def sizes(self): + """ + Get the sizes of the expression + """ + return self.data.sizes + + @property + def ndim(self): + """ + Get the number of dimensions. + """ + return self.const.ndim + @property def loc(self): return LocIndexer(self) @@ -1281,7 +1315,6 @@ def mask_func(data): stack = exprwrap(Dataset.stack) -@forward_as_properties(data=["attrs", "coords", "indexes", "sizes"]) class QuadraticExpression(LinearExpression): """ A quadratic expression consisting of terms of coefficients and variables. diff --git a/linopy/objective.py b/linopy/objective.py index 8fca164c..cf6b949b 100644 --- a/linopy/objective.py +++ b/linopy/objective.py @@ -12,7 +12,6 @@ import numpy as np from linopy import expressions -from linopy.common import forward_as_properties def objwrap(method, *default_args, **new_default_kwargs): @@ -33,19 +32,6 @@ def _objwrap(obj, *args, **kwargs): return _objwrap -@forward_as_properties( - expression=[ - "attrs", - "coords", - "indexes", - "sizes", - "flat", - "coeffs", - "vars", - "data", - "nterm", - ] -) class Objective: """ An objective expression containing all relevant information. @@ -83,6 +69,69 @@ def __repr__(self) -> str: return f"Objective:\n----------\n{expr_string}\n{sense_string}\n{value_string}" + @property + def attrs(self): + """ + Returns the attributes of the objective. + """ + return self.expression.attrs + + @property + def coords(self): + """ + Returns the coordinates of the objective. + """ + return self.expression.coords + + @property + def indexes(self): + """ + Returns the indexes of the objective. + """ + return self.expression.indexes + + @property + def sizes(self): + """ + Returns the sizes of the objective. + """ + return self.expression.sizes + + @property + def flat(self): + """ + Returns the flattened objective. + """ + return self.expression.flat + + @property + def coeffs(self): + """ + Returns the coefficients of the objective. + """ + return self.expression.coeffs + + @property + def vars(self): + """ + Returns the variables of the objective. + """ + return self.expression.vars + + @property + def data(self): + """ + Returns the data of the objective. + """ + return self.expression.data + + @property + def nterm(self): + """ + Returns the number of terms in the objective. + """ + return self.expression.nterm + @property def expression(self): """ diff --git a/linopy/variables.py b/linopy/variables.py index 4f9391b8..9b9cfa03 100644 --- a/linopy/variables.py +++ b/linopy/variables.py @@ -22,7 +22,6 @@ LocIndexer, as_dataarray, format_string_as_variable_name, - forward_as_properties, generate_indices_for_printout, get_label_position, has_optimized_model, @@ -61,20 +60,6 @@ def _var_unwrap(var): return var.data if isinstance(var, Variable) else var -@forward_as_properties( - data=[ - "attrs", - "coords", - "indexes", - "sizes", - ], - labels=[ - "shape", - "size", - "dims", - "ndim", - ], -) class Variable: """ Variable container for storing variable labels. @@ -187,6 +172,62 @@ def __getitem__(self, selector) -> Union["Variable", "ScalarVariable"]: ) return self.__class__(data, self.model, self.name) + @property + def attrs(self): + """ + Get the attributes of the variable. + """ + return self.data.attrs + + @property + def coords(self): + """ + Get the coordinates of the variable. + """ + return self.data.coords + + @property + def indexes(self): + """ + Get the indexes of the variable. + """ + return self.data.indexes + + @property + def sizes(self): + """ + Get the sizes of the variable. + """ + return self.data.sizes + + @property + def shape(self): + """ + Get the shape of the variable. + """ + return self.labels.shape + + @property + def size(self): + """ + Get the size of the variable. + """ + return self.labels.size + + @property + def dims(self): + """ + Get the dimensions of the variable. + """ + return self.labels.dims + + @property + def ndim(self): + """ + Get the number of dimensions of the variable. + """ + return self.labels.ndim + @property def at(self): """ @@ -982,14 +1023,6 @@ def __getitem__(self, keys) -> "ScalarVariable": @dataclass(repr=False) -@forward_as_properties( - labels=[ - "attrs", - "coords", - "indexes", - "dims", - ] -) class Variables: """ A variables container used for storing multiple variable arrays. @@ -1081,6 +1114,34 @@ def remove(self, name): """ self.data.pop(name) + @property + def attrs(self): + """ + Get the attributes of all variables. + """ + return self.labels.attrs + + @property + def coords(self): + """ + Get the coordinates of all variables. + """ + return self.labels.coords + + @property + def indexes(self): + """ + Get the indexes of all variables. + """ + return self.labels.indexes + + @property + def sizes(self): + """ + Get the sizes of all variables. + """ + return self.labels.sizes + @property def labels(self): """ diff --git a/test/test_constraint.py b/test/test_constraint.py index aa3f3db9..68222985 100644 --- a/test/test_constraint.py +++ b/test/test_constraint.py @@ -161,6 +161,20 @@ def test_anonymous_scalar_constraint_with_scalar_variable_on_rhs(x, y): # assert (con.rhs == 0).all() +def test_constraint_inherited_properties(x, y): + con = 10 * x + y <= 10 + assert isinstance(con.attrs, dict) + assert isinstance(con.coords, xr.Coordinates) + assert isinstance(con.indexes, xr.core.indexes.Indexes) + assert isinstance(con.sizes, xr.core.utils.Frozen) + assert isinstance(con.ndim, int) + assert isinstance(con.nterm, int) + assert isinstance(con.shape, tuple) + assert isinstance(con.size, int) + assert isinstance(con.dims, xr.core.utils.Frozen) + assert con.values is None + + def test_anonymous_constraint_sel(x, y): expr = 10 * x + y con = expr <= 10 diff --git a/test/test_linear_expression.py b/test/test_linear_expression.py index 9041cfa7..836e0c41 100644 --- a/test/test_linear_expression.py +++ b/test/test_linear_expression.py @@ -468,6 +468,14 @@ def test_linear_expression_multiplication_invalid(x, y, z): expr / x +def test_expression_inherited_properties(x, y): + expr = 10 * x + y + assert isinstance(expr.attrs, dict) + assert isinstance(expr.coords, xr.Coordinates) + assert isinstance(expr.indexes, xr.core.indexes.Indexes) + assert isinstance(expr.sizes, xr.core.utils.Frozen) + + def test_linear_expression_getitem_single(x, y): expr = 10 * x + y + 3 sel = expr[0] diff --git a/test/test_objective.py b/test/test_objective.py index 6fac1b61..9eba175f 100644 --- a/test/test_objective.py +++ b/test/test_objective.py @@ -1,4 +1,6 @@ +import pandas as pd import pytest +import xarray as xr from scipy.sparse import csc_matrix from linopy import Model @@ -59,6 +61,18 @@ def test_sense_setter_error(linear_objective): linear_objective.sense = "not min or max" +def test_variables_inherited_properties(linear_objective): + assert isinstance(linear_objective.attrs, dict) + assert isinstance(linear_objective.coords, xr.Coordinates) + assert isinstance(linear_objective.indexes, xr.core.indexes.Indexes) + assert isinstance(linear_objective.sizes, xr.core.utils.Frozen) + + assert isinstance(linear_objective.flat, pd.DataFrame) + assert isinstance(linear_objective.vars, xr.DataArray) + assert isinstance(linear_objective.coeffs, xr.DataArray) + assert isinstance(linear_objective.nterm, int) + + def test_expression(linear_objective, quadratic_objective): assert isinstance(linear_objective.expression, LinearExpression) assert isinstance(quadratic_objective.expression, QuadraticExpression) diff --git a/test/test_variable.py b/test/test_variable.py index 01f2c090..b0a26fd2 100644 --- a/test/test_variable.py +++ b/test/test_variable.py @@ -40,6 +40,17 @@ def test_variable_repr(x): x.__repr__() +def test_variable_inherited_properties(x): + assert isinstance(x.attrs, dict) + assert isinstance(x.coords, xr.Coordinates) + assert isinstance(x.indexes, xr.core.indexes.Indexes) + assert isinstance(x.sizes, xr.core.utils.Frozen) + assert isinstance(x.shape, tuple) + assert isinstance(x.size, int) + assert isinstance(x.dims, tuple) + assert isinstance(x.ndim, int) + + def test_variable_labels(x): isinstance(x.labels, xr.DataArray) diff --git a/test/test_variables.py b/test/test_variables.py index bda3e9f7..b178fef5 100644 --- a/test/test_variables.py +++ b/test/test_variables.py @@ -7,6 +7,7 @@ import numpy as np import pandas as pd import pytest +import xarray as xr import linopy from linopy import Model @@ -26,6 +27,13 @@ def test_variables_repr(m): m.variables.__repr__() +def test_variables_inherited_properties(m): + assert isinstance(m.variables.attrs, dict) + assert isinstance(m.variables.coords, xr.Coordinates) + assert isinstance(m.variables.indexes, xr.core.indexes.Indexes) + assert isinstance(m.variables.sizes, xr.core.utils.Frozen) + + def test_variables_getattr_formatted(): m = Model() m.add_variables(name="y-0")