5858 get_index_map ,
5959 group_terms_polars ,
6060 has_optimized_model ,
61+ is_constant ,
6162 iterate_slices ,
6263 print_coord ,
6364 print_single_expression ,
@@ -441,6 +442,11 @@ def __repr__(self) -> str:
441442
442443 return "\n " .join (lines )
443444
445+ @property
446+ def is_constant (self ) -> bool :
447+ """True if the expression contains no variables."""
448+ return self .data .sizes [TERM_DIM ] == 0
449+
444450 def print (self , display_max_rows : int = 20 , display_max_terms : int = 20 ) -> None :
445451 """
446452 Print the linear expression.
@@ -840,9 +846,7 @@ def cumsum(
840846 dim_dict = {dim_name : self .data .sizes [dim_name ] for dim_name in dim }
841847 return self .rolling (dim = dim_dict ).sum (keep_attrs = keep_attrs , skipna = skipna )
842848
843- def to_constraint (
844- self , sign : SignLike , rhs : ConstantLike | VariableLike | ExpressionLike
845- ) -> Constraint :
849+ def to_constraint (self , sign : SignLike , rhs : SideLike ) -> Constraint :
846850 """
847851 Convert a linear expression to a constraint.
848852
@@ -859,6 +863,11 @@ def to_constraint(
859863 which are moved to the left-hand-side and constant values which are moved
860864 to the right-hand side.
861865 """
866+ if self .is_constant and is_constant (rhs ):
867+ raise ValueError (
868+ f"Both sides of the constraint are constant. At least one side must contain variables. { self } { rhs } "
869+ )
870+
862871 all_to_lhs = (self - rhs ).data
863872 data = assign_multiindex_safe (
864873 all_to_lhs [["coeffs" , "vars" ]], sign = sign , rhs = - all_to_lhs .const
@@ -1439,12 +1448,18 @@ def to_polars(self) -> pl.DataFrame:
14391448
14401449 The resulting DataFrame represents a long table format of the all
14411450 non-masked expressions with non-zero coefficients. It contains the
1442- columns `coeffs`, `vars`.
1451+ columns `coeffs`, `vars`, `const`. The coeffs and vars columns will be null if the expression is constant .
14431452
14441453 Returns
14451454 -------
14461455 df : polars.DataFrame
14471456 """
1457+ if self .is_constant :
1458+ df = pl .DataFrame (
1459+ {"const" : self .data ["const" ].values .reshape (- 1 )}
1460+ ).with_columns (pl .lit (None ).alias ("coeffs" ), pl .lit (None ).alias ("vars" ))
1461+ return df .select (["vars" , "coeffs" , "const" ])
1462+
14481463 df = to_polars (self .data )
14491464 df = filter_nulls_polars (df )
14501465 df = group_terms_polars (df )
@@ -1647,6 +1662,26 @@ def process_one(
16471662
16481663 return merge (exprs , cls = cls ) if len (exprs ) > 1 else exprs [0 ]
16491664
1665+ @classmethod
1666+ def from_constant (cls , model : Model , constant : ConstantLike ) -> LinearExpression :
1667+ """
1668+ Create a linear expression from a constant value or series
1669+
1670+ Parameters
1671+ ----------
1672+ model : linopy.Model
1673+ The model to which the constant expression will belong.
1674+ constant : int/float/array_like
1675+ The constant value for the linear expression.
1676+
1677+ Returns
1678+ -------
1679+ linopy.LinearExpression
1680+ A linear expression representing the constant value.
1681+ """
1682+ const_da = as_dataarray (constant )
1683+ return LinearExpression (const_da , model )
1684+
16501685
16511686class QuadraticExpression (BaseExpression ):
16521687 """
@@ -1835,12 +1870,22 @@ def to_polars(self, **kwargs: Any) -> pl.DataFrame:
18351870
18361871 The resulting DataFrame represents a long table format of the all
18371872 non-masked expressions with non-zero coefficients. It contains the
1838- columns `coeffs`, `vars` .
1873+ columns `vars1`, `vars2`, ` coeffs`, `const`. If the expression is constant, the `vars1` and `vars2` and `coeffs` columns will be null .
18391874
18401875 Returns
18411876 -------
18421877 df : polars.DataFrame
18431878 """
1879+ if self .is_constant :
1880+ df = pl .DataFrame (
1881+ {"const" : self .data ["const" ].values .reshape (- 1 )}
1882+ ).with_columns (
1883+ pl .lit (None ).alias ("coeffs" ),
1884+ pl .lit (None ).alias ("vars1" ),
1885+ pl .lit (None ).alias ("vars2" ),
1886+ )
1887+ return df .select (["vars1" , "vars2" , "coeffs" , "const" ])
1888+
18441889 vars = self .data .vars .assign_coords (
18451890 {FACTOR_DIM : ["vars1" , "vars2" ]}
18461891 ).to_dataset (FACTOR_DIM )
0 commit comments