Skip to content

calliope.backend.backend_model.BackendModel(inputs, math, build_config, instance)

Bases: BackendModelGenerator, Generic[T]

Calliope's backend model functionality.

Abstract base class to build backend models that interface with solvers.

Parameters:

Name Type Description Default
inputs Dataset

Calliope model data.

required
math AttrDict

Calliope math.

required
build_config Build

Build configuration options.

required
instance T

Interface model instance.

required
Source code in src/calliope/backend/backend_model.py
def __init__(
    self,
    inputs: xr.Dataset,
    math: math_schema.CalliopeBuildMath,
    build_config: config_schema.Build,
    instance: T,
) -> None:
    """Abstract base class to build backend models that interface with solvers.

    Args:
        inputs (xr.Dataset): Calliope model data.
        math (AttrDict): Calliope math.
        build_config (config_schema.Build): Build configuration options.
        instance (T): Interface model instance.
    """
    super().__init__(inputs, math, build_config)
    self._instance = instance
    self.shadow_prices: ShadowPrices
    self._has_verbose_strings: bool = False

OBJECTIVE_SENSE_DICT instance-attribute

VARIABLE_DOMAIN_DICT instance-attribute

config = build_config instance-attribute

constraints property

Slice of backend dataset to show only built constraints.

global_expressions property

Slice of backend dataset to show only built global expressions.

has_integer_or_binary_variables abstractmethod property

Confirms if the built model has binary or integer decision variables.

This can be used to understand how long the optimisation may take (MILP problems are harder to solve than LP ones), and to verify whether shadow prices can be tracked (they cannot be tracked in MILP problems).

Returns:

Name Type Description
bool bool

True if the built model has binary or integer decision variables. False if all decision variables are continuous.

inputs = self._add_inputs(inputs) instance-attribute

lookups property

Slice of backend dataset to show only built lookup arrays.

math = math instance-attribute

objective = self.config.objective instance-attribute

Optimisation problem objective name.

objectives property

Slice of backend dataset to show only built objectives.

parameters property

Slice of backend dataset to show only built parameters.

piecewise_constraints property

Slice of backend dataset to show only built piecewise constraints.

postprocessed property

Slice of backend dataset to show only built postprocessed arrays.

shadow_prices instance-attribute

variables property

Slice of backend dataset to show only built variables.

add_constraint(name, definition)

Add constraint equation to backend model in-place.

Resulting backend dataset entries will be constraint objects.

Parameters:

Name Type Description Default
name str

Name of the constraint

required
definition Constraint

Constraint configuration dictionary, ready to be parsed and then evaluated.

required
Source code in src/calliope/backend/backend_model.py
def add_constraint(self, name: str, definition: math_schema.Constraint) -> None:
    """Add constraint equation to backend model in-place.

    Resulting backend dataset entries will be constraint objects.

    Args:
        name (str):
            Name of the constraint
        definition (math_schema.Constraint):
            Constraint configuration dictionary, ready to be parsed and then evaluated.
    """
    references: set[str] = set()
    default_empty = xr.DataArray(np.nan)
    if not definition.active:
        self.log("constraints", name, "Component deactivated.")
        return

    self._raise_error_on_preexistence(name, "constraints")
    parsed_component = parsing.ParsedBackendComponent(
        "constraints", name, definition, self.math.parsing_components
    )
    top_level_where = self._eval_top_level_where(
        self._dataset, references, parsed_component
    )

    if top_level_where.any():
        component_da = self._eval_equations(
            name,
            parsed_component,
            self._dataset,
            top_level_where,
            self._add_constraint,
            references,
        )
    else:
        component_da = default_empty
    self._add_to_dataset(
        name,
        component_da,
        "constraints",
        definition.model_dump(),
        references=references,
    )

add_global_expression(name, definition)

Add global expression (arithmetic combination of parameters and/or decision variables) to backend model in-place.

Resulting backend dataset entries will be linear expression objects.

Parameters:

Name Type Description Default
name str

name of the global expression

required
definition GlobalExpression

Global expression configuration dictionary, ready to be parsed and then evaluated.

required
Source code in src/calliope/backend/backend_model.py
def add_global_expression(
    self, name: str, definition: math_schema.GlobalExpression
) -> None:
    """Add global expression (arithmetic combination of parameters and/or decision variables) to backend model in-place.

    Resulting backend dataset entries will be linear expression objects.

    Args:
        name (str): name of the global expression
        definition (math_schema.GlobalExpression): Global expression configuration dictionary, ready to be parsed and then evaluated.
    """
    references: set[str] = set()
    default_empty = xr.DataArray(np.nan)
    if not definition.active:
        self.log("global_expressions", name, "Component deactivated.")
        return

    self._raise_error_on_preexistence(name, "global_expressions")
    parsed_component = parsing.ParsedBackendComponent(
        "global_expressions", name, definition, self.math.parsing_components
    )
    top_level_where = self._eval_top_level_where(
        self._dataset, references, parsed_component
    )

    if top_level_where.any():
        component_da = self._eval_equations(
            name,
            parsed_component,
            self._dataset,
            top_level_where,
            self._add_global_expression,
            references,
        )
    else:
        component_da = default_empty
    self._add_to_dataset(
        name,
        component_da,
        "global_expressions",
        definition.model_dump(),
        references=references,
    )

add_lookup(name, values, definition)

Add input lookup array to backend model in-place.

This directly passes a copy of the input lookup array to the backend.

Parameters:

Name Type Description Default
name str

Name of lookup.

required
values DataArray

Array of lookup values.

required
definition Lookup

Lookup math definition.

required
Source code in src/calliope/backend/backend_model.py
def add_lookup(
    self, name: str, values: xr.DataArray, definition: math_schema.Lookup
) -> None:
    """Add input lookup array to backend model in-place.

    This directly passes a copy of the input lookup array to the backend.

    Args:
        name (str): Name of lookup.
        values (xr.DataArray): Array of lookup values.
        definition (math_schema.Lookup): Lookup math definition.
    """
    self._raise_error_on_preexistence(name, "lookups")

    if values.isnull().all():
        self.log("lookups", name, "Component not added; no data found in array.")
        values = xr.DataArray(np.nan, attrs=values.attrs)

    self._add_to_dataset(name, values, "lookups", definition.model_dump())

add_objective(name, definition)

Add objective arithmetic to backend model in-place.

Resulting backend dataset entry will be a single, unindexed objective object.

Parameters:

Name Type Description Default
name str

name of the objective.

required
definition Objective

Unparsed objective configuration dictionary.

required
Source code in src/calliope/backend/backend_model.py
def add_objective(self, name: str, definition: math_schema.Objective) -> None:
    """Add objective arithmetic to backend model in-place.

    Resulting backend dataset entry will be a single, unindexed objective object.

    Args:
        name (str): name of the objective.
        definition (math_schema.Objective): Unparsed objective configuration dictionary.
    """
    references: set[str] = set()
    default_empty = xr.DataArray(np.nan)
    if not definition.active:
        self.log("objectives", name, "Component deactivated.")
        return

    self._raise_error_on_preexistence(name, "objectives")

    parsed_component = parsing.ParsedBackendComponent(
        "objectives", name, definition, self.math.parsing_components
    )
    top_level_where = self._eval_top_level_where(
        self._dataset, references, parsed_component
    )

    sense = self.OBJECTIVE_SENSE_DICT[definition.sense]
    if top_level_where.any():
        component_da = self._eval_equations(
            name,
            parsed_component,
            self._dataset,
            top_level_where,
            partial(self._add_objective, sense=sense),
            references,
        )
    else:
        component_da = default_empty
    self._add_to_dataset(
        name,
        component_da,
        "objectives",
        definition.model_dump(),
        references=references,
    )

add_optimisation_components()

Parse math and inputs and set optimisation problem.

Source code in src/calliope/backend/backend_model.py
def add_optimisation_components(self) -> None:
    """Parse math and inputs and set optimisation problem."""
    # The order of adding components matters!
    # 1. Variables, 2. Global Expressions, 3. Constraints, 4. Objectives
    self._load_inputs()
    for components in typing.get_args(ORDERED_COMPONENTS_T):
        component = components.removesuffix("s")
        ordered_items = self._sorted_by_order(self.math[components].root)
        for name, definition in ordered_items:
            start = time.time()
            getattr(self, f"add_{component}")(name, definition)
            end = time.time() - start
            LOGGER.debug(
                f"Optimisation Model | {components}:{name} | Built in {end:.4f}s"
            )
        LOGGER.info(f"Optimisation Model | {components} | Generated.")

add_parameter(name, values, definition)

Add input parameter to backend model in-place.

If the backend interface allows for mutable parameter objects, they will be generated, otherwise a copy of the model input dataset will be used. In either case, NaN values are filled with the given parameter default value.

Parameters:

Name Type Description Default
name str

Name of parameter.

required
values DataArray

Array of parameter values.

required
definition Parameter

Parameter math definition.

required
Source code in src/calliope/backend/backend_model.py
def add_parameter(
    self, name: str, values: xr.DataArray, definition: math_schema.Parameter
) -> None:
    """Add input parameter to backend model in-place.

    If the backend interface allows for mutable parameter objects, they will be
    generated, otherwise a copy of the model input dataset will be used.
    In either case, NaN values are filled with the given parameter default value.

    Args:
        name (str): Name of parameter.
        values (xr.DataArray): Array of parameter values.
        definition (math_schema.Parameter): Parameter math definition.
    """
    self._raise_error_on_preexistence(name, "parameters")

    if values.isnull().all():
        self.log("parameters", name, "Component not added; no data found in array.")
        values = xr.DataArray(np.nan, attrs=values.attrs)

    self._add_to_dataset(name, values, "parameters", definition.model_dump())

add_piecewise_constraint(name, definition)

Source code in src/calliope/backend/backend_model.py
def add_piecewise_constraint(  # noqa: D102, override
    self, name: str, definition: math_schema.PiecewiseConstraint
) -> None:
    references: set[str] = set()
    default_empty = xr.DataArray(np.nan)
    if "breakpoints" in definition.foreach:
        raise BackendError(
            f"(piecewise_constraints, {name}) | `breakpoints` dimension should not be in `foreach`. "
            "Instead, index `x_values` and `y_values` parameters over `breakpoints`."
        )

    if not definition.active:
        self.log("piecewise_constraints", name, "Component deactivated.")
        return

    self._raise_error_on_preexistence(name, "piecewise_constraints")

    parsed_component = parsing.ParsedBackendComponent(
        "piecewise_constraints", name, definition, self.math.parsing_components
    )
    top_level_where = self._eval_top_level_where(
        self._dataset, references, parsed_component
    )
    if top_level_where.any():
        where = parsed_component.drop_dims_not_in_foreach(top_level_where)
        expressions = []
        vals = []
        for axis in ["x", "y"]:
            dummy_expression_dict = {
                "equations": [{"expression": definition[f"{axis}_expression"]}],
                "foreach": definition.foreach,
            }
            parsed_component = parsing.ParsedBackendComponent(
                "piecewise_constraints",
                name,
                math_schema.GlobalExpression.model_validate(dummy_expression_dict),
                self.math.parsing_components,
            )
            eq = parsed_component.parse_equations()
            expression_da = eq[0].evaluate_expression(
                self.inputs,
                self._dataset,
                self.math,
                where=where,
                references=references,
            )
            val_name = definition[f"{axis}_values"]
            val_da = self.get_parameter(val_name)
            if "breakpoints" not in val_da.dims:
                raise BackendError(
                    f"(piecewise_constraints, {name}) | "
                    f"`{axis}_values` must be indexed over the `breakpoints` dimension."
                )
            references.add(val_name)
            expressions.append(expression_da)
            vals.extend([*val_da.to_dataset("breakpoints").data_vars.values()])

        try:
            component_da = self._apply_func(
                self._to_piecewise_constraint,
                where,
                1,
                *expressions,
                *vals,
                name=name,
                n_breakpoints=len(self.inputs.breakpoints),
            )
        except BackendError as err:
            raise BackendError(
                f"(piecewise_constraints, {name}) | Errors in generating piecewise constraint: {err}"
            )
    else:
        component_da = default_empty
    self._add_to_dataset(
        name,
        component_da,
        "piecewise_constraints",
        definition.model_dump(),
        references=references,
    )

add_postprocessed_arrays(dataset)

Add postprocessed arrays to the results dataset.

Parameters:

Name Type Description Default
dataset Dataset

The resolved dataset with which to evaluate postprocessed arrays.

required

Returns:

Type Description
Dataset

xr.Dataset: The updated results dataset with postprocessed arrays.

Source code in src/calliope/backend/backend_model.py
def add_postprocessed_arrays(self, dataset: xr.Dataset) -> xr.Dataset:
    """Add postprocessed arrays to the results dataset.

    Args:
        dataset (xr.Dataset): The resolved dataset with which to evaluate postprocessed arrays.

    Returns:
        xr.Dataset: The updated results dataset with postprocessed arrays.
    """
    ordered_items = self._sorted_by_order(self.math.postprocessed.root)
    postprocessed = {}
    for name, definition in ordered_items:
        start = time.time()
        da = self._add_postprocessed(name, definition, dataset)
        end = time.time() - start
        LOGGER.debug(
            f"Optimisation Model | postprocess:{name} | Built in {end:.4f}s"
        )
        dataset = dataset.assign({name: da})
        postprocessed[name] = da
    LOGGER.info("Optimisation Model | postprocess | Generated.")
    return xr.Dataset(postprocessed)

add_variable(name, definition)

Add decision variable to backend model in-place.

Resulting backend dataset entries will be decision variable objects.

Parameters:

Name Type Description Default
name str

name of the variable.

required
definition Variable

Variable configuration dictionary.

required
Source code in src/calliope/backend/backend_model.py
def add_variable(self, name: str, definition: math_schema.Variable) -> None:
    """Add decision variable to backend model in-place.

    Resulting backend dataset entries will be decision variable objects.

    Args:
        name (str): name of the variable.
        definition (math_schema.Variable): Variable configuration dictionary.
    """
    references: set[str] = set()
    component_da = xr.DataArray(np.nan)
    if not definition.active:
        self.log(
            "variables",
            name,
            "Component deactivated; only metadata will be stored if no other "
            "component with the same name is defined.",
        )
        where_results = self.math.parsing_components["where"]["results"]
        expr_results = self.math.parsing_components["expression"]["results"]
        if not (name in where_results and name not in expr_results):
            # No dataset entry, no metadata
            return
    else:
        self._raise_error_on_preexistence(name, "variables")
        parsed_component = parsing.ParsedBackendComponent(
            "variables", name, definition, self.math.parsing_components
        )
        top_level_where = self._eval_top_level_where(
            self._dataset, references, parsed_component
        )

        if top_level_where.any():
            component_da = self._add_variable(
                name,
                top_level_where,
                references,
                self.VARIABLE_DOMAIN_DICT[definition.domain],
                definition.bounds,
            )
    self._add_to_dataset(
        name,
        component_da,
        "variables",
        definition.model_dump(),
        references=references,
    )

delete_component(key, component_type) abstractmethod

Delete a list object from the backend model object.

Parameters:

Name Type Description Default
key str

Name of object.

required
component_type str

Object type.

required
Source code in src/calliope/backend/backend_model.py
@abstractmethod
def delete_component(self, key: str, component_type: ALL_COMPONENTS_T) -> None:
    """Delete a list object from the backend model object.

    Args:
        key (str): Name of object.
        component_type (str): Object type.
    """

fix_variable(name, where=None) abstractmethod

Fix the variable value to the value quantified on the most recent call to solve.

Fixed variables will be treated as parameters in the optimisation.

Parameters:

Name Type Description Default
name str

Variable to update.

required
where DataArray | None

If provided, only a subset of the coordinates in the variable will be fixed. Must be a boolean array or a float equivalent, where NaN is used instead of False. Defaults to None

None
Source code in src/calliope/backend/backend_model.py
@abstractmethod
def fix_variable(self, name: str, where: xr.DataArray | None = None) -> None:
    """Fix the variable value to the value quantified on the most recent call to `solve`.

    Fixed variables will be treated as parameters in the optimisation.

    Args:
        name (str): Variable to update.
        where (xr.DataArray | None, optional):
            If provided, only a subset of the coordinates in the variable will be fixed.
            Must be a boolean array or a float equivalent, where NaN is used instead of False.
            Defaults to None
    """

get_constraint(name, as_backend_objs=True, eval_body=False) abstractmethod

get_constraint(name: str, as_backend_objs: Literal[True] = True, eval_body: bool = False) -> xr.DataArray
get_constraint(name: str, as_backend_objs: Literal[False], eval_body: bool = False) -> xr.Dataset

Get constraint data from the backend for debugging.

Dat can be returned as a table of details or as an array of backend interface objects

Parameters:

Name Type Description Default
name str

Name of constraint, as given in YAML constraint key.

required
as_backend_objs bool

TODO: hide this and create a method to edit constraints that handles differences in interface APIs If True, will keep the array entries as backend interface objects, which can be updated to change the underlying model. Otherwise, constraint body, and lower and upper bounds are given in a table. Defaults to True.

True
eval_body bool

If True and as_backend_objs is False, will attempt to evaluate the constraint body. If the model has been optimised, this attempt will produce a numeric value to see where the constraint sits between the lower or upper bound. If the model has not yet been optimised, this attempt will fall back on the same as if eval_body was set to False, i.e. a string representation of the linear expression in the constraint body. Defaults to False.

False

Returns:

Type Description
DataArray | Dataset

xr.DataArray | xr.Dataset: If as_backend_objs is True, will return an xr.DataArray. Otherwise, a xr.Dataset will be given, indexed over the same dimensions as the xr.DataArray, with variables for the constraint body, and upper (ub) and lower (lb) bounds.

Source code in src/calliope/backend/backend_model.py
@abstractmethod
def get_constraint(
    self, name: str, as_backend_objs: bool = True, eval_body: bool = False
) -> xr.DataArray | xr.Dataset:
    """Get constraint data from the backend for debugging.

    Dat can be returned as  a table of details or as an array of backend interface objects

    Args:
        name (str): Name of constraint, as given in YAML constraint key.
        as_backend_objs (bool, optional): TODO: hide this and create a method to edit constraints that handles differences in interface APIs
            If True, will keep the array entries as backend interface objects,
            which can be updated to change the underlying model.
            Otherwise, constraint body, and lower and upper bounds are given in a table.
            Defaults to True.
        eval_body (bool, optional):
            If True and as_backend_objs is False, will attempt to evaluate the constraint body.
            If the model has been optimised, this attempt will produce a numeric value to see where the constraint sits between the lower or upper bound.
            If the model has not yet been optimised, this attempt will fall back on the same as
            if `eval_body` was set to False, i.e. a string representation of the linear expression in the constraint body.
            Defaults to False.

    Returns:
        xr.DataArray | xr.Dataset:
            If as_backend_objs is True, will return an xr.DataArray.
            Otherwise, a xr.Dataset will be given, indexed over the same dimensions as the xr.DataArray, with variables for the constraint body, and upper (`ub`) and lower (`lb`) bounds.
    """

get_global_expression(name, as_backend_objs=True, eval_body=False)

Extract global expression array from backend dataset.

Parameters:

Name Type Description Default
name str

Name of global expression.

required
as_backend_objs bool

TODO: hide this and create a method to edit expressions that handles differences in interface APIs. If True, will keep the array entries as backend interface objects, which can be updated to update the underlying model. Otherwise, global expression values are given directly. If the model has not been successfully optimised, expression values will all be provided as strings. Defaults to True.

True
eval_body bool

If True and as_backend_objs is False, will attempt to evaluate the expression. If the model has been optimised, this attempt will produce a numeric value. If the model has not yet been optimised, this attempt will fall back on the same as if eval_body was set to False, i.e. a string representation of the linear expression. Defaults to False.

False

Returns:

Type Description
DataArray

xr.DataArray: global expression array.

Source code in src/calliope/backend/backend_model.py
def get_global_expression(
    self, name: str, as_backend_objs: bool = True, eval_body: bool = False
) -> xr.DataArray:
    """Extract global expression array from backend dataset.

    Args:
        name (str): Name of global expression.
        as_backend_objs (bool, optional): TODO: hide this and create a method to edit expressions that handles differences in interface APIs.
            If True, will keep the array entries as backend interface objects,
            which can be updated to update the underlying model.
            Otherwise, global expression values are given directly.
            If the model has not been successfully optimised, expression values will all be provided as strings.
            Defaults to True.
        eval_body (bool, optional):
            If True and `as_backend_objs is False`, will attempt to evaluate the expression.
            If the model has been optimised, this attempt will produce a numeric value.
            If the model has not yet been optimised, this attempt will fall back on the same as
            if `eval_body` was set to False, i.e. a string representation of the linear expression.
            Defaults to False.

    Returns:
        xr.DataArray: global expression array.
    """
    return self._get_expression(
        name, as_backend_objs, eval_body, "global_expressions"
    )

get_lookup(name)

Extract lookup from backend dataset.

Parameters:

Name Type Description Default
name str

Name of lookup.

required

Returns:

Type Description
DataArray

xr.DataArray: lookup array.

Source code in src/calliope/backend/backend_model.py
def get_lookup(self, name: str) -> xr.DataArray:
    """Extract lookup from backend dataset.

    Args:
        name (str): Name of lookup.

    Returns:
        xr.DataArray: lookup array.
    """
    return self._get_component(name, "lookups")

get_objective(name, as_backend_objs=True, eval_body=False)

Extract objective from backend dataset.

Parameters:

Name Type Description Default
name str

Name of objective.

required
as_backend_objs bool

TODO: hide this and create a method to edit expressions that handles differences in interface APIs. If True, will keep the array entries as backend interface objects, which can be updated to update the underlying model. Otherwise, objective values are given directly. If the model has not been successfully optimised, the objective will be provided as a string. Defaults to True.

True
eval_body bool

If True and as_backend_objs is False, will attempt to evaluate the objective. If the model has been optimised, this attempt will produce a numeric value. If the model has not yet been optimised, this attempt will fall back on the same as if eval_body was set to False, i.e. a string representation of the linear expression. Defaults to False.

False

Returns:

Type Description
DataArray

xr.DataArray: objective array.

Source code in src/calliope/backend/backend_model.py
def get_objective(
    self, name: str, as_backend_objs: bool = True, eval_body: bool = False
) -> xr.DataArray:
    """Extract objective from backend dataset.

    Args:
        name (str): Name of objective.
        as_backend_objs (bool, optional): TODO: hide this and create a method to edit expressions that handles differences in interface APIs.
            If True, will keep the array entries as backend interface objects,
            which can be updated to update the underlying model.
            Otherwise, objective values are given directly.
            If the model has not been successfully optimised, the objective will be provided as a string.
            Defaults to True.
        eval_body (bool, optional):
            If True and `as_backend_objs` is False, will attempt to evaluate the objective.
            If the model has been optimised, this attempt will produce a numeric value.
            If the model has not yet been optimised, this attempt will fall back on the same as
            if `eval_body` was set to False, i.e. a string representation of the linear expression.
            Defaults to False.

    Returns:
        xr.DataArray: objective array.
    """
    return self._get_expression(name, as_backend_objs, eval_body, "objectives")

get_parameter(name, as_backend_objs=True) abstractmethod

Extract parameter from backend dataset.

Parameters:

Name Type Description Default
name str

Name of parameter.

required
as_backend_objs bool

TODO: hide this and create a method to edit parameter values (to handle interfaces with non-mutable params) If True, will keep the array entries as backend interface objects, which can be updated to update the underlying model. Otherwise, parameter values are given directly, with default values in place of NaNs. Defaults to True.

True

Returns:

Type Description
DataArray

xr.DataArray: parameter array.

Source code in src/calliope/backend/backend_model.py
@abstractmethod
def get_parameter(self, name: str, as_backend_objs: bool = True) -> xr.DataArray:
    """Extract parameter from backend dataset.

    Args:
        name (str): Name of parameter.
        as_backend_objs (bool, optional): TODO: hide this and create a method to edit parameter values (to handle interfaces with non-mutable params)
            If True, will keep the array entries as backend interface objects,
            which can be updated to update the underlying model.
            Otherwise, parameter values are given directly, with default values in place of NaNs.
            Defaults to True.

    Returns:
        xr.DataArray: parameter array.
    """

get_piecewise_constraint(name)

Get piecewise constraint data as an array of backend interface objects.

This method can be used to inspect and debug built piecewise constraints.

Unlike other optimisation problem components, piecewise constraints can only be inspected as backend interface objects. This is because each element is a collection of variables, parameters, constraints, and expressions.

Parameters:

Name Type Description Default
name str

Name of piecewise constraint, as given in YAML piecewise constraint key.

required

Returns:

Type Description
DataArray

xr.DataArray: Piecewise constraint array.

Source code in src/calliope/backend/backend_model.py
def get_piecewise_constraint(self, name: str) -> xr.DataArray:
    """Get piecewise constraint data as an array of backend interface objects.

    This method can be used to inspect and debug built piecewise constraints.

    Unlike other optimisation problem components, piecewise constraints can only be inspected as backend interface objects.
    This is because each element is a collection of variables, parameters, constraints, and expressions.

    Args:
        name (str): Name of piecewise constraint, as given in YAML piecewise constraint key.

    Returns:
        xr.DataArray: Piecewise constraint array.
    """
    return self._get_component(name, "piecewise_constraints")

get_variable(name, as_backend_objs=True) abstractmethod

Extract decision variable array from backend dataset.

Parameters:

Name Type Description Default
name str

Name of variable.

required
as_backend_objs bool

TODO: hide this and create a method to edit variables that handles differences in interface APIs. If True, will keep the array entries as backend interface objects, which can be updated to update the underlying model. Otherwise, variable values are given directly. If the model has not been successfully optimised, variable values will all be None. Defaults to True.

True

Returns:

Type Description
DataArray

xr.DataArray: Decision variable array.

Source code in src/calliope/backend/backend_model.py
@abstractmethod
def get_variable(self, name: str, as_backend_objs: bool = True) -> xr.DataArray:
    """Extract decision variable array from backend dataset.

    Args:
        name (str): Name of variable.
        as_backend_objs (bool, optional): TODO: hide this and create a method to edit variables that handles differences in interface APIs.
            If True, will keep the array entries as backend interface objects,
            which can be updated to update the underlying model.
            Otherwise, variable values are given directly.
            If the model has not been successfully optimised, variable values will all be None.
            Defaults to True.

    Returns:
        xr.DataArray: Decision variable array.
    """

get_variable_bounds(name) abstractmethod

Extract decision variable upper and lower bound array from backend dataset.

Parameters:

Name Type Description Default
name str

Name of variable.

required

Returns:

Type Description
Dataset

xr.Dataset: Contains the arrays for upper ("ub", a.k.a. "max") and lower ("lb", a.k.a. "min") variable bounds.

Source code in src/calliope/backend/backend_model.py
@abstractmethod
def get_variable_bounds(self, name: str) -> xr.Dataset:
    """Extract decision variable upper and lower bound array from backend dataset.

    Args:
        name (str): Name of variable.

    Returns:
        xr.Dataset: Contains the arrays for upper ("ub", a.k.a. "max") and lower ("lb", a.k.a. "min") variable bounds.
    """

load_results(postprocess)

Load and evaluate model results after a successful run.

Evaluates backend decision variables, global expressions, parameters (if not in inputs), and shadow_prices (if tracked).

Returns:

Type Description
Dataset

xr.Dataset: Dataset of optimal solution results (all numeric data).

Source code in src/calliope/backend/backend_model.py
def load_results(self, postprocess: bool) -> xr.Dataset:
    """Load and evaluate model results after a successful run.

    Evaluates backend decision variables, global expressions, parameters (if not in
    inputs), and shadow_prices (if tracked).

    Returns:
        xr.Dataset: Dataset of optimal solution results (all numeric data).
    """

    def _drop_attrs(da):
        da.attrs = {}
        return da

    all_variables = {
        name_: self.get_variable(name_, as_backend_objs=False)
        for name_ in self.variables.keys()
    }
    all_global_expressions = {
        name_: self.get_global_expression(
            name_, as_backend_objs=False, eval_body=True
        )
        for name_ in self.global_expressions.keys()
    }
    all_objectives = {
        name_: self.get_objective(name_, as_backend_objs=False, eval_body=True)
        for name_ in self.objectives.keys()
    }

    all_shadow_prices = {
        f"shadow_price_{constraint}": self.shadow_prices.get(constraint)
        for constraint in self.shadow_prices.tracked
    }

    results = xr.Dataset(
        {
            **all_variables,
            **all_global_expressions,
            **all_shadow_prices,
            **all_objectives,
        },
        attrs=self._dataset.attrs,
    ).astype(float)

    if postprocess:
        postprocessed = self.add_postprocessed_arrays(results.assign(self.inputs))
        results = results.assign(postprocessed)
    cleaned_results = xr.Dataset(
        {
            k: _drop_attrs(v)
            for k, v in results.data_vars.items()
            if v.notnull().any()
        },
        attrs=self._dataset.attrs,
    )
    return cleaned_results

log(component_type, component_name, message, level='debug')

Log to module-level logger with some prettification of the message.

Parameters:

Name Type Description Default
component_type ALL_COMPONENTS_T

type of component.

required
component_name str

name of the component.

required
message str

message to log.

required
level Literal['info', 'warning', 'debug', 'error', 'critical']

log level. Defaults to "debug".

'debug'
Source code in src/calliope/backend/backend_model.py
def log(
    self,
    component_type: ALL_COMPONENTS_T,
    component_name: str,
    message: str,
    level: Literal["info", "warning", "debug", "error", "critical"] = "debug",
):
    """Log to module-level logger with some prettification of the message.

    Args:
        component_type (ALL_COMPONENTS_T): type of component.
        component_name (str): name of the component.
        message (str): message to log.
        level (Literal["info", "warning", "debug", "error", "critical"], optional): log level. Defaults to "debug".
    """
    getattr(LOGGER, level)(
        f"Optimisation model | {component_type}:{component_name} | {message}"
    )

set_objective(name) abstractmethod

Set a built objective to be the optimisation objective.

Parameters:

Name Type Description Default
name str

name of the objective.

required
Source code in src/calliope/backend/backend_model.py
@abstractmethod
def set_objective(self, name: str) -> None:
    """Set a built objective to be the optimisation objective.

    Args:
        name (str): name of the objective.
    """

to_lp(path) abstractmethod

Write the optimisation problem to file in the linear programming LP format.

The LP file can be used for debugging and to submit to solvers directly.

Parameters:

Name Type Description Default
path str | Path

Path to which the LP file will be written.

required
Source code in src/calliope/backend/backend_model.py
@abstractmethod
def to_lp(self, path: str | Path) -> None:
    """Write the optimisation problem to file in the linear programming LP format.

    The LP file can be used for debugging and to submit to solvers directly.

    Args:
        path (str | Path): Path to which the LP file will be written.
    """

unfix_variable(name, where=None) abstractmethod

Unfix the variable so that it is treated as a decision variable in the next call to solve.

Parameters:

Name Type Description Default
name str

Variable to update

required
where DataArray | None

If provided, only a subset of the coordinates in the variable will be unfixed. Must be a boolean array or a float equivalent, where NaN is used instead of False. Defaults to None

None
Source code in src/calliope/backend/backend_model.py
@abstractmethod
def unfix_variable(self, name: str, where: xr.DataArray | None = None) -> None:
    """Unfix the variable so that it is treated as a decision variable in the next call to `solve`.

    Args:
        name (str): Variable to update
        where (xr.DataArray | None, optional):
            If provided, only a subset of the coordinates in the variable will be unfixed.
            Must be a boolean array or a float equivalent, where NaN is used instead of False.
            Defaults to None
    """

update_input(name, new_values) abstractmethod

Update input elements (parameters/lookups) using an array of new values.

If the input has not been previously defined, it will be added to the optimisation problem based on the new values given (with NaNs reverting to default values). If the new values have fewer dimensions than are on the input array, the new values will be broadcast across the missing dimensions before applying the update.

Parameters:

Name Type Description Default
name str

Input array to update

required
new_values DataArray | SupportsFloat

New values to apply. Any empty (NaN) elements in the array will be skipped.

required
Source code in src/calliope/backend/backend_model.py
@abstractmethod
def update_input(self, name: str, new_values: xr.DataArray | SupportsFloat) -> None:
    """Update input elements (parameters/lookups) using an array of new values.

    If the input has not been previously defined, it will be added to the
    optimisation problem based on the new values given (with NaNs reverting to
    default values).
    If the new values have fewer dimensions than are on the input array, the
    new values will be broadcast across the missing dimensions before applying the
    update.

    Args:
        name (str): Input array to update
        new_values (xr.DataArray | SupportsFloat): New values to apply. Any
            empty (NaN) elements in the array will be skipped.
    """

update_variable_bounds(name, *, min=None, max=None) abstractmethod

Update the bounds on a decision variable.

If the variable bounds are defined by parameters in the math formulation, the parameters themselves will be updated.

Parameters:

Name Type Description Default
name str

Variable to update.

required
min DataArray | SupportsFloat | None

If provided, the Non-NaN values in the array will be used to defined new lower bounds in the decision variable. Defaults to None.

None
max DataArray | SupportsFloat | None

If provided, the Non-NaN values in the array will be used to defined new upper bounds in the decision variable. Defaults to None.

None
Source code in src/calliope/backend/backend_model.py
@abstractmethod
def update_variable_bounds(
    self,
    name: str,
    *,
    min: xr.DataArray | SupportsFloat | None = None,
    max: xr.DataArray | SupportsFloat | None = None,
) -> None:
    """Update the bounds on a decision variable.

    If the variable bounds are defined by parameters in the math formulation,
    the parameters themselves will be updated.

    Args:
        name (str): Variable to update.
        min (xr.DataArray | SupportsFloat | None, optional):
            If provided, the Non-NaN values in the array will be used to defined new lower bounds in the decision variable.
            Defaults to None.
        max (xr.DataArray | SupportsFloat | None, optional):
            If provided, the Non-NaN values in the array will be used to defined new upper bounds in the decision variable.
            Defaults to None.
    """

verbose_strings() abstractmethod

Update optimisation model object string representations to include the index coordinates of the object.

E.g., variables(flow_out)[0] will become variables(flow_out)[power, region1, ccgt, 2005-01-01 00:00]

This takes approximately 10% of the peak memory required to initially build the optimisation problem, so should only be invoked if inspecting the model in detail (e.g., debugging).

Only string representations of model parameters and variables will be updated since global expressions automatically show the string representation of their contents.

Source code in src/calliope/backend/backend_model.py
@abstractmethod
def verbose_strings(self) -> None:
    """Update optimisation model object string representations to include the index coordinates of the object.

    E.g., `variables(flow_out)[0]` will become `variables(flow_out)[power, region1, ccgt, 2005-01-01 00:00]`

    This takes approximately 10% of the peak memory required to initially build the
    optimisation problem, so should only be invoked if inspecting the model in
    detail (e.g., debugging).

    Only string representations of model parameters and variables will be updated
    since global expressions automatically show the string representation of their
    contents.
    """