Skip to content

calliope.Model(inputs, attrs, results=None, **kwargs)

Bases: ModelStructure

A Calliope Model.

Returns a instantiated Calliope Model.

Parameters:

Name Type Description Default
inputs Dataset

Input dataset.

required
attrs CalliopeAttrs

Model attributes & properties.

required
results Dataset | None

Results dataset from another Calliope Model with compatible math formulation. Defaults to None.

None
**kwargs

initialisation keyword arguments

{}
Source code in src/calliope/model.py
def __init__(
    self,
    inputs: xr.Dataset,
    attrs: CalliopeAttrs,
    results: xr.Dataset | None = None,
    **kwargs,
) -> None:
    """Returns a instantiated Calliope Model.

    Args:
        inputs (xr.Dataset): Input dataset.
        attrs (CalliopeAttrs): Model attributes & properties.
        results (xr.Dataset | None, optional):
            Results dataset from another Calliope Model with compatible math formulation.
            Defaults to None.
        **kwargs:
            initialisation keyword arguments
    """
    self.inputs: xr.Dataset
    self.results: xr.Dataset = xr.Dataset() if results is None else results
    self.backend: BackendModel

    self.definition = attrs.definition
    self.config = attrs.config

    self._start_window_idx: int = 0
    self._is_built: bool = False
    self._is_solved: bool = False if results is None else True
    self.config = self.config.update({"init": kwargs})

    math_priority = model_math.get_math_priority(self.config.init)
    if math_priority != attrs.runtime.math_priority:
        math = model_math.build_math(
            math_priority,
            attrs.math.init.model_dump(),
            validate=self.config.init.pre_validate_math_strings,
        )
        attrs = attrs.update(
            {
                "runtime.math_priority": math_priority,
                "math.build": math.model_dump(),
            }
        )
    if inputs:
        model_data_factory = ModelDataCleaner(
            self.config.init, inputs, attrs.math.build, attrs.runtime
        )
        model_data_factory.clean()

        self.inputs = model_data_factory.dataset
        self.runtime = model_data_factory.runtime
    else:
        self.inputs = inputs
        self.runtime = attrs.runtime

    self.math = attrs.math

    self._check_versions()
    log_time(
        LOGGER,
        self.runtime.timings,
        "init_complete",
        comment="Model: initialisation complete",
    )

all_attrs property

Get all model attributes as a CalliopeAttrs object.

backend instance-attribute

config = self.config.update({'init': kwargs}) instance-attribute

definition = attrs.definition instance-attribute

inputs instance-attribute

is_built property

Get built status.

is_solved property

Get solved status.

math = attrs.math instance-attribute

name property

Get the model name.

results = xr.Dataset() if results is None else results instance-attribute

runtime = model_data_factory.runtime instance-attribute

build(force=False, **kwargs)

Build description of the optimisation problem in the chosen backend interface.

Parameters:

Name Type Description Default
force bool

If force is True, any existing results will be overwritten. Defaults to False.

False
**kwargs

build configuration overrides.

{}
Source code in src/calliope/model.py
def build(self, force: bool = False, **kwargs) -> None:
    """Build description of the optimisation problem in the chosen backend interface.

    Args:
        force (bool, optional):
            If ``force`` is True, any existing results will be overwritten.
            Defaults to False.
        **kwargs: build configuration overrides.
    """
    if self._is_built and not force:
        raise exceptions.ModelError(
            "This model object already has a built optimisation problem. Use model.build(force=True) "
            "to force the existing optimisation problem to be overwritten with a new one."
        )
    log_time(
        LOGGER,
        self.runtime.timings,
        "build_start",
        comment="Model: backend build starting",
    )

    self.config = self.config.update({"build": kwargs})

    mode = self.config.init.mode
    if mode == "operate":
        backend_input = self._prepare_operate_mode_inputs(self.config.build.operate)
    else:
        backend_input = self.inputs

    self.backend = backend.get_model_backend(
        self.config.build, backend_input, self.math.build
    )
    self.backend.add_optimisation_components()

    log_time(
        LOGGER,
        self.runtime.timings,
        "build_complete",
        comment="Model: backend build complete",
    )

    self._is_built = True

dump_all_attrs()

Dump of all class pydantic model attributes as a single dictionary.

Source code in src/calliope/model.py
def dump_all_attrs(self) -> dict:
    """Dump of all class pydantic model attributes as a single dictionary."""
    return self.all_attrs.model_dump()

info()

Generate basic description of the model, combining its name and a rough indication of the model size.

Returns:

Name Type Description
str str

Basic description of the model.

Source code in src/calliope/model.py
def info(self) -> str:
    """Generate basic description of the model, combining its name and a rough indication of the model size.

    Returns:
        str: Basic description of the model.
    """
    info_strings = []
    model_name = self.name
    info_strings.append(f"Model name:   {model_name}")
    msize = dict(self.inputs.dims)
    msize_exists = self.inputs.definition_matrix.sum()
    info_strings.append(
        f"Model size:   {msize} ({msize_exists.item()} valid node:tech:carrier combinations)"
    )
    return "\n".join(info_strings)

run(force_rerun=False)

Run the model.

If force_rerun is True, any existing results will be overwritten.

Source code in src/calliope/model.py
def run(self, force_rerun=False):
    """Run the model.

    If ``force_rerun`` is True, any existing results will be overwritten.
    """
    exceptions.warn(
        "`run()` is deprecated and will be removed in a "
        "future version. Use `model.build()` followed by `model.solve()`.",
        FutureWarning,
    )
    self.build(force=force_rerun)
    self.solve(force=force_rerun)

solve(force=False, warmstart=False, **kwargs)

Solve the built optimisation problem.

Parameters:

Name Type Description Default
force bool

If force is True, any existing results will be overwritten. Defaults to False.

False
warmstart bool

If True and the optimisation problem has already been run in this session (i.e., force is not True), the next optimisation will be run with decision variables initially set to their previously optimal values. If the optimisation problem is similar to the previous run, this can decrease the solution time. Warmstart will not work with some solvers (e.g., CBC, GLPK). Defaults to False.

False
**kwargs

solve configuration overrides.

{}

Raises:

Type Description
ModelError

Optimisation problem must already be built.

ModelError

Cannot run the model if there are already results loaded, unless force is True.

ModelError

Some preprocessing steps will stop a run mode of "operate" from being possible.

Source code in src/calliope/model.py
def solve(self, force: bool = False, warmstart: bool = False, **kwargs) -> None:
    """Solve the built optimisation problem.

    Args:
        force (bool, optional):
            If ``force`` is True, any existing results will be overwritten.
            Defaults to False.
        warmstart (bool, optional):
            If True and the optimisation problem has already been run in this session
            (i.e., `force` is not True), the next optimisation will be run with
            decision variables initially set to their previously optimal values.
            If the optimisation problem is similar to the previous run, this can
            decrease the solution time.
            Warmstart will not work with some solvers (e.g., CBC, GLPK).
            Defaults to False.
        **kwargs: solve configuration overrides.

    Raises:
        exceptions.ModelError: Optimisation problem must already be built.
        exceptions.ModelError: Cannot run the model if there are already results loaded, unless `force` is True.
        exceptions.ModelError: Some preprocessing steps will stop a run mode of "operate" from being possible.
    """
    if not self.is_built:
        raise exceptions.ModelError(
            "You must build the optimisation problem (`.build()`) "
            "before you can run it."
        )

    # Check that results exist and are non-empty
    if self.results.data_vars and not force:
        raise exceptions.ModelError(
            "This model object already has results. "
            "Use model.solve(force=True) to force"
            "the results to be overwritten with a new run."
        )

    self.config = self.config.update({"solve": kwargs})

    self.backend.shadow_prices.track_constraints(self.config.solve.shadow_prices)

    mode = self.config.init.mode
    log_time(
        LOGGER,
        self.runtime.timings,
        "solve_start",
        comment=f"Optimisation model | starting model in {mode} mode.",
    )
    if mode == "operate":
        results = self._solve_operate(self.config.solve)
    elif mode == "spores":
        results = self._solve_spores(self.config.solve)
    else:
        results = self.backend._solve(self.config.solve, warmstart=warmstart)

    log_time(
        LOGGER,
        self.runtime.timings,
        "solver_exit",
        time_since_solve_start=True,
        comment="Backend: solver finished running",
    )

    # Add additional post-processed result variables to results
    if results.attrs["termination_condition"] in ["optimal", "feasible"]:
        results = self._apply_zero_threshold(
            results, self.config.solve.zero_threshold
        )

    self.math = self.math.update({"build": self.backend.math.model_dump()})
    self.runtime = self.runtime.update(
        {"termination_condition": results.attrs.pop("termination_condition")}
    )

    log_time(
        LOGGER,
        self.runtime.timings,
        "postprocess_complete",
        time_since_solve_start=True,
        comment="Postprocessing: ended",
    )

    log_time(
        LOGGER,
        self.runtime.timings,
        "solve_complete",
        time_since_solve_start=True,
        comment="Backend: model solve completed",
    )
    self.results = results

    self._is_solved = True

to_csv(path, dropna=True, allow_overwrite=False)

Save complete model data (inputs and, if available, results) as a set of CSV files to the given path.

Parameters:

Name Type Description Default
path str | Path

file path to save at.

required
dropna bool

If True, NaN values are dropped when saving, resulting in significantly smaller CSV files. Defaults to True

True
allow_overwrite bool

If True, allow the option to overwrite the directory contents if it already exists. This will overwrite CSV files one at a time, so if the dataset has different arrays to the previous saved models, you will get a mix of old and new files. Defaults to False.

False
Source code in src/calliope/model.py
def to_csv(
    self, path: str | Path, dropna: bool = True, allow_overwrite: bool = False
):
    """Save complete model data (inputs and, if available, results) as a set of CSV files to the given ``path``.

    Args:
        path (str | Path): file path to save at.
        dropna (bool, optional):
            If True, NaN values are dropped when saving, resulting in significantly smaller CSV files.
            Defaults to True
        allow_overwrite (bool, optional):
            If True, allow the option to overwrite the directory contents if it already exists.
            This will overwrite CSV files one at a time, so if the dataset has different arrays to the previous saved models, you will get a mix of old and new files.
            Defaults to False.

    """
    io.save_csv(self.inputs, "inputs", path, dropna, allow_overwrite)

    if self.results:
        io.save_csv(self.results, "results", path, dropna, allow_overwrite=True)
    else:
        exceptions.warn("No results available, saving inputs only.")

    io.to_yaml(self.dump_all_attrs(), path=Path(path) / "attrs.yaml")

to_netcdf(path)

Save complete model data (inputs and, if available, results) to a NetCDF file at the given path.

Source code in src/calliope/model.py
def to_netcdf(self, path):
    """Save complete model data (inputs and, if available, results) to a NetCDF file at the given `path`."""
    io.save_netcdf(self.inputs, "inputs", "w", path)
    io.save_netcdf(self.results, "results", "a", path)
    io.save_netcdf(xr.Dataset(attrs=self.dump_all_attrs()), "attrs", "a", path)