Coverage for picos/expressions/mutable.py: 91.11%
90 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 14:21 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-15 14:21 +0000
1# ------------------------------------------------------------------------------
2# Copyright (C) 2019-2020 Maximilian Stahlberg
3# Based on the original picos.expressions module by Guillaume Sagnol.
4#
5# This file is part of PICOS.
6#
7# PICOS is free software: you can redistribute it and/or modify it under the
8# terms of the GNU General Public License as published by the Free Software
9# Foundation, either version 3 of the License, or (at your option) any later
10# version.
11#
12# PICOS is distributed in the hope that it will be useful, but WITHOUT ANY
13# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along with
17# this program. If not, see <http://www.gnu.org/licenses/>.
18# ------------------------------------------------------------------------------
20"""Implements the :class:`Mutable` base class for variables and parameters."""
22import random
23import threading
24from abc import ABC, abstractmethod
25from copy import copy as pycopy
27from .. import glyphs
28from ..apidoc import api_end, api_start
29from ..caching import cached_property
30from .data import load_data
31from .expression import Expression, NotValued
33_API_START = api_start(globals())
34# -------------------------------
37# The mutable IDs start at a random value to prevent a clash if mutables are
38# pickled and loaded in another python session. The choice of 31 bits is to
39# produce up to 2**31-1 short hexadecimal IDs for use in private mutable names.
40_NEXT_MUTABLE_ID = int(random.getrandbits(31))
42# A lock for _NEXT_MUTABLE_ID, if the user uses threads to create mutables.
43_MUTABLE_ID_LOCK = threading.Lock()
46def _make_mutable_id(dim):
47 """Create a unique (starting) ID for a new mutable."""
48 with _MUTABLE_ID_LOCK:
49 global _NEXT_MUTABLE_ID
50 id = _NEXT_MUTABLE_ID
51 _NEXT_MUTABLE_ID += dim
52 return id
55# A map used when converting IDs to strings.
56_ID_HEX2STR_MAP = {ord(a): ord(b) for a, b in zip(
57 "0123456789abcdef", "ABCDEFGHIJKLMNOP")}
60def _id_to_str(id):
61 """Convert a mutable id to a short string identifier."""
62 return "{:08x}".format(id).translate(_ID_HEX2STR_MAP)
65class Mutable(ABC):
66 """Primary base class for all variable and parameter types.
68 Mutables need to inherit this class with priority (first class listed) and
69 the affine expression type that they represent without priority.
70 """
72 def __init__(self, name, vectorization):
73 """Perform basic initialization for :class:`Mutable` instances.
75 :param str name:
76 Name of the mutable. A leading `"__"` denotes a private mutable
77 and is replaced by a sequence containing the mutable's unique ID.
79 :param vectorization:
80 Vectorization format used to store the value.
81 :type vectorization:
82 ~picos.expressions.vectorizations.BaseVectorization
83 """
84 if not isinstance(self, Expression):
85 raise TypeError("{} may not be initialized directly.".format(
86 type(self).__name__))
88 self._id = _make_mutable_id(vectorization.dim)
89 self._vec = vectorization
90 self._value = None
92 if not name or name.startswith("__"):
93 id_str = "__{}_".format(_id_to_str(self._id))
94 name = name.replace("__", id_str, 1) if name else id_str + "_"
96 self._name = name
98 @abstractmethod
99 def copy(self, new_name=None):
100 """Return an independent copy of the mutable.
102 Note that unlike constraints which keep their ID on copy, mutables are
103 supposed to receive a new id.
104 """
105 pass
107 @property
108 def id(self):
109 """The unique (starting) ID of the mutable, assigned at creation."""
110 return self._id
112 def id_at(self, index):
113 """Return the unique ID of a scalar entry, assigned at creation."""
114 if index < 0 or index >= self._vec.dim:
115 raise IndexError("Bad index {} for the {} dimensional mutable {}."
116 .format(index, self._vec.dim, self.name))
118 return self._id + index
120 @property
121 def dim(self):
122 """The mutable's dimension on the real field.
124 This corresponds to the length of its vectorized value.
125 """
126 return self._vec.dim
128 @property
129 def name(self):
130 """The name of the mutable."""
131 return self._name
133 @cached_property
134 def long_string(self):
135 """A string used to represent the mutable in a problem string."""
136 return "{} {} {}".format(glyphs.shape(self.shape),
137 self._get_type_string_base().lower(), self._name)
139 def _load_vectorized(self, value):
140 """Support :meth:`__init__` and ``_set_value``."""
141 return self._vec.vectorize(
142 load_data(value, self._vec.shape, self._typecode)[0])
144 def _check_internal_value(self, value):
145 """Support :meth:`_set_value` and :meth:`_set_internal_value`."""
146 pass
148 # NOTE: This needs to be inherited with priority.
149 def _get_value(self):
150 return self._vec.devectorize(self._get_internal_value())
152 # NOTE: This needs to be inherited with priority.
153 def _set_value(self, value):
154 if value is None:
155 self._value = None
156 return
158 try:
159 value = self._load_vectorized(value)
160 self._check_internal_value(value)
161 except Exception as error:
162 raise type(error)("Failed to assign a value to mutable {}: {}"
163 .format(self.string, error)) from None
164 else:
165 self._value = value
167 def _get_internal_value(self):
168 if self._value is None:
169 raise NotValued("Mutable {} is not valued.".format(self.string))
170 else:
171 return pycopy(self._value)
173 def _set_internal_value(self, value):
174 if value is None:
175 self._value = None
176 return
178 try:
179 value = load_data(value, (self._vec.dim, 1), "d")[0]
180 self._check_internal_value(value)
181 except Exception as error:
182 raise type(error)(
183 "Failed to assign an internal value to mutable {}: {}"
184 .format(self.string, error)) from None
185 else:
186 self._value = value
188 internal_value = property(
189 lambda self: self._get_internal_value(),
190 lambda self, value: self._set_internal_value(value),
191 lambda self: self._set_internal_value(None),
192 """The internal (special vectorized) value of the mutable.""")
194 def _get_clstype(self):
195 # Mimic an ordinary (complex) affine expression.
196 return self._basetype
198 def _get_subtype(self):
199 # Mimic an ordinary (complex) affine expression.
200 return self._basetype._get_subtype(self)
202 @classmethod
203 def _predict(cls, subtype, relation, other):
204 # Mimic an ordinary (complex) affine expression.
205 return cls._get_basetype()._predict(subtype, relation, other)
208# --------------------------------------
209__all__ = api_end(_API_START, globals())