Source code for minizinc.model

#  This Source Code Form is subject to the terms of the Mozilla Public
#  License, v. 2.0. If a copy of the MPL was not distributed with this
#  file, You can obtain one at http://mozilla.org/MPL/2.0/.

import json
import threading
import warnings
from enum import Enum, EnumMeta
from pathlib import Path
from typing import Any, Dict, List, Optional, Type, Union

ParPath = Union[Path, str]


[docs] class Method(Enum): """Enumeration that represents of a solving method. Attributes: SATISFY: Represents a satisfaction problem. MINIMIZE: Represents a minimization problem. MAXIMIZE: Represents a maximization problem. """ SATISFY = 1 MINIMIZE = 2 MAXIMIZE = 3 @classmethod def from_string(cls, s: str): """Get Method represented by the string s. Args: s: String expected to contain either "sat", "min", or "max". Returns: Method: Method represented by s """ if s == "sat": return cls.SATISFY elif s == "min": return cls.MINIMIZE elif s == "max": return cls.MAXIMIZE else: raise ValueError( f"Unknown Method {s}, valid options are 'sat', 'min', or 'max'" )
class UnknownExpression(str): pass
[docs] class Model: """The representation of a MiniZinc model in Python Attributes: output_type (Type): the type used to store the solution values created in the process of solving the Instance. This attribute is particularly helpful when comparing the results of multiple instances together. The type must support initialisation with the assignments returned by MiniZinc. These assignments currently always include "__output_item" and include "objective" if the instance is not a satisfaction problem. Raises: MiniZincError: when an error occurs during the parsing or type checking of the model object. """ output_type: Optional[Type] = None _code_fragments: List[str] _data: Dict[str, Any] _enum_map: Dict[str, Enum] _includes: List[Path] _lock: threading.Lock _checker: bool = False def __init__(self, files: Optional[Union[ParPath, List[ParPath]]] = None): self._data = {} self._includes = [] self._code_fragments = [] self._enum_map = {} self._lock = threading.Lock() if isinstance(files, Path) or isinstance(files, str): self._add_file(files) elif files is not None: for file in files: self._add_file(file)
[docs] def __setitem__(self, key: str, value: Any): """Set parameter of Model. This method overrides the default implementation of item access (``obj[key] = value``) for models. Item access on a Model can be used to set parameters of the Model. Args: key (str): Identifier of the parameter. value (Any): Value to be assigned to the parameter. """ with self._lock: if self._data.get(key, None) is None: if isinstance(value, EnumMeta): self._register_enum_values(value) self._data.__setitem__(key, value) else: if self._data[key] != value: # TODO: Fix the error type and document raise AssertionError( f"The parameter '{key}' cannot be assigned multiple values. " f"If you are changing the model, consider using the branch " f"method before assigning the parameter." )
def _register_enum_values(self, t: EnumMeta): for name in t.__members__: if name in self._enum_map: # TODO: Fix the error type and document raise AssertionError( f"Identifier '{name}' is used in multiple enumerated types" f"within the same model. Identifiers in enumerated types " f"have to be unique." ) self._enum_map[name] = t.__members__[name]
[docs] def __getitem__(self, key: str) -> Any: """Get parameter of Model. This method overrides the default implementation of item access (``obj[key]``) for models. Item access on a Model can be used to get parameters of the Model. Args: key (str): Identifier of the parameter. Returns: The value assigned to the parameter. Raises: KeyError: The parameter you are trying to access is not known. """ return self._data.__getitem__(key)
[docs] def add_file(self, file: ParPath, parse_data: bool = False) -> None: """Adds a MiniZinc file (``.mzn``, ``.dzn``, or ``.json``) to the Model. Args: file (Union[Path, str]): Path to the file to be added parse_data (bool): Signal if the data should be parsed for usage within Python. This option is ignored if the extra `dzn` is not enabled. Raises: MiniZincError: when an error occurs during the parsing or type checking of the model object. """ return self._add_file(file, parse_data)
def _add_file(self, file: ParPath, parse_data: bool = False) -> None: if not isinstance(file, Path): file = Path(file) assert file.exists() if not parse_data: with self._lock: self._includes.append(file) return if file.suffix == ".json": data = json.load(file.open()) for k, v in data.items(): self.__setitem__(k, v) elif file.suffix == ".dzn" and parse_data: try: from lark.exceptions import LarkError from .dzn import parse_dzn try: data = parse_dzn(file) for k, v in data.items(): self.__setitem__(k, v) except LarkError: warnings.warn( f"Could not parse {file}. Parameters included within this file " f"are not available in Python", stacklevel=1, ) with self._lock: self._includes.append(file) except ImportError: with self._lock: self._includes.append(file) elif file.suffix not in [".dzn", ".mzn", ".mzc"]: raise NameError("Unknown file suffix %s", file.suffix) else: with self._lock: if ".mzc" in file.suffixes: self._checker = True self._includes.append(file)
[docs] def add_string(self, code: str) -> None: """Adds a string of MiniZinc code to the Model. Args: code (str): A string contain MiniZinc code Raises: MiniZincError: when an error occurs during the parsing or type checking of the model object. """ with self._lock: self._code_fragments.append(code)
def __copy__(self): copy = self.__class__() copy._includes = self._includes[:] copy._code_fragments = self._code_fragments[:] copy._data = dict.copy(self._data) return copy