/ pdkmaster / technology / property_.py
property_.py
  1  # SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0
  2  from typing import Any, Iterable, Tuple, Union, ClassVar, overload
  3  
  4  from . import rule as _rle
  5  
  6  
  7  __all__ = [
  8      "Enclosure", "PropertyT", "EnclosurePropertyT", "ComparisonT",
  9      "Operators", "Ops",
 10  ]
 11  
 12  
 13  class Enclosure:
 14      """Enclosure object are representing the enclosure value of one layer
 15      over another layer. Most of the time it is used to indicae the
 16      minimum required enclosure of one layer over another layer.
 17  
 18      Enclosure constraints can symmetric or asymmetric. A symmetric
 19      enclosure can be specified with a single float value, an assymetric one
 20      with two float values. Internally always two float values are stored and
 21      be accessed through the `spec` attribute of an Enclosure object. For a
 22      symmetric object the two float values are the same.
 23  
 24      When an enclosure is used as a constraint symmetric means that the
 25      enclosure has to be met in all directions. Asymmetric normally means
 26      that a smaller enclosure in one direction is allowed when both enclosures
 27      in the other direction are bigger than the bigger enclosure value.
 28      For this case the order of the two value don't have a meaning.
 29  
 30      An enclosure can also be used to specify when doing layout generation.
 31      The PDKMaster convention here is that the order has a meaning; the first
 32      value is for the horizontal direction and the second value for the
 33      vertical one. Also giving meaning to the `wide()` and `tall()` object methods
 34  
 35      Enclosure objects are implemented as immutable objects.
 36      """
 37      def __init__(self, spec: Union[float, Iterable[float]]):
 38          self._spec: Tuple[float, float]
 39          if isinstance(spec, float):
 40              self._spec = (spec, spec)
 41          else:
 42              self._spec = tuple(spec)
 43              if len(self.spec) != 2:
 44                  raise ValueError(
 45                      f"spec for Enclosure is either a float or 2 floats"
 46                  )
 47  
 48      @property
 49      def spec(self) -> Tuple[float, float]:
 50          return self._spec
 51  
 52      @staticmethod
 53      def cast(v: Union[float, Iterable[float], "Enclosure"]) -> "Enclosure":
 54          if isinstance(v, Enclosure):
 55              return v
 56          else:
 57              return Enclosure(spec=v)
 58  
 59      @property
 60      def first(self) -> float:
 61          return self.spec[0]
 62  
 63      @property
 64      def second(self) -> float:
 65          return self.spec[1]
 66  
 67      @property
 68      def is_assymetric(self) -> bool:
 69          return self.first != self.second
 70  
 71      def min(self) -> float:
 72          return min(self.spec)
 73  
 74      def max(self) -> float:
 75          return max(self.spec)
 76  
 77      def wide(self) -> "Enclosure":
 78          # Put bigger enclosure value first
 79          if self.first >= self.second:
 80              return self
 81          else:
 82              return Enclosure(spec=(self.second, self.first))
 83  
 84      def tall(self) -> "Enclosure":
 85          if self.first <= self.second:
 86              return self
 87          else:
 88              return Enclosure(spec=(self.second, self.first))
 89  
 90      def __hash__(self) -> int:
 91          return hash(self._spec)
 92  
 93      def __eq__(self, other: Any) -> bool:
 94          if not isinstance(other, Enclosure):
 95              return False
 96          else:
 97              return (
 98                  (self.first == other.first)
 99                  and (self.second == other.second)
100              )
101  
102      def __repr__(self) -> str:
103          if not self.is_assymetric:
104              return f"Enclosure({round(self.first, 6)})"
105          else:
106              return f"Enclosure(({round(self.spec[0], 6)},{round(self.spec[1], 6)}))"
107  
108  
109  class _Property:
110      """This class represents a property of an object. Rules may be built from
111      from a comparison operation.
112  
113          o = Property(name='width')
114          rule = o >= 3.5
115  
116      Then `rule` represents the rule for width greater or equal to 3.5.
117      """
118      value_conv: Any = None
119      value_type: Union[type, Tuple[type, ...]] = (float, int)
120      value_type_str: str = "float"
121  
122      def __init__(self, *, name: str, allow_none: bool=False):
123          self.name = name
124          self.allow_none = allow_none
125  
126          value_type = self.value_type
127          if not isinstance(value_type, tuple):
128              if issubclass(value_type, _Property):
129                  raise TypeError("Property.value_type may not be 'Property'")
130  
131          self.dependencies = set()
132  
133      def __gt__(self, other) -> "ComparisonT":
134          return Ops.Greater(left=self, right=other)
135      def __ge__(self, other) -> "ComparisonT":
136          return Ops.GreaterEqual(left=self, right=other)
137      def __lt__(self, other) -> "ComparisonT":
138          return Ops.Smaller(left=self, right=other)
139      def __le__(self, other) -> "ComparisonT":
140          return Ops.SmallerEqual(left=self, right=other)
141      @overload
142      def __eq__(self, other: "_Property") -> bool:
143          ... # pragma: no cover
144      @overload
145      def __eq__(self, other: Any) -> "ComparisonT":
146          ... # pragma: no cover
147      def __eq__(self, other: Any) -> Union[bool, "ComparisonT"]:
148          """The __eq__() method for Property can have two meanings. If it is
149          compared with another Property object it will check if it is the same
150          property. For another object it will generate a Rule object representing
151          that property being equal to the provided value.
152          """
153          try:
154              return Ops.Equal(left=self, right=other)
155          except TypeError:
156              return isinstance(other, _Property) and (self.name == other.name)
157  
158      def __str__(self) -> str:
159          return f"{self.name}"
160      def __repr__(self) -> str:
161          return f"{self.__class__.__name__}(name={self.name!r}, allow_non={self.allow_none!r})"
162  
163      def __hash__(self) -> int:
164          return hash(self.name)
165  
166      def cast(self, value):
167          if value is None:
168              if self.allow_none:
169                  return None
170              else:
171                  raise TypeError(
172                      f"property '{self.name}' given value '{value!r}' is not of type "
173                      f"'{self.value_type_str}'"
174                  )
175  
176          value_conv = self.__class__.value_conv
177          if value_conv is not None:
178              try:
179                  value = value_conv(value)
180              except:
181                  raise TypeError("could not convert property value {!r} to type '{}'".format(
182                      value, self.value_type_str,
183                  ))
184          if not isinstance(value, self.value_type):
185              raise TypeError(
186                  f"value '{value!r}' for property '{self.name}' is not of type "
187                  f"'{self.value_type_str}'",
188              )
189          return value
190  PropertyT = _Property
191  
192  
193  class _EnclosureProperty(_Property):
194      """An EnclosureProperty object is a Property with an Enclosure object as value.
195      """
196      value_conv = Enclosure.cast
197      value_type = Enclosure
198      value_type_str = "'Enclosure'"
199  EnclosurePropertyT = _EnclosureProperty
200  
201  
202  class _Comparison(_rle._Condition):
203      """A _Comparison object is a _Condition which represent the comparison of a Property
204      object with a value. The operator for the comparison is represented as a string class
205      variable names `symbol`.
206      """
207      symbol: ClassVar[str]
208  
209      def __init__(self, *, left: _Property, right: Any):
210          try:
211              self.symbol
212          except AttributeError:
213              raise TypeError(
214                  f"class '{self.__class__.__name__}' does not have the"
215                  " symbol class variable defined"
216              )
217  
218          right2 = left.cast(right)
219  
220          super().__init__(elements=(left, right2))
221          self._elements: Tuple[_Property, Any]
222  
223      @property
224      def left(self) -> _Property:
225          return self._elements[0]
226      @property
227      def right(self) -> Any:
228          return self._elements[1]
229  
230      def __eq__(self, other: object) -> bool:
231          if not isinstance(other, _Comparison):
232              return False
233          else:
234              return (
235                  (self.symbol == other.symbol)
236                  and (self.left == other.left)
237                  and (self.right == other.right)
238              )
239  
240      def __str__(self) -> str:
241          return f"{self.left} {self.symbol} {self.right}"
242      def __repr__(self) -> str:
243          return f"{self.left!r} {self.symbol} {self.right!r}"
244  
245      def __bool__(self) -> bool:
246          raise TypeError("BinaryPropertyCondition can't be converted to a bool")
247  ComparisonT = _Comparison
248  
249  
250  class Operators:
251      """Operators is a class representing a bunch of boolean operators.
252      """
253      class Greater(_Comparison):
254          symbol = ">"
255      class GreaterEqual(_Comparison):
256          symbol = ">="
257      class Smaller(_Comparison):
258          symbol = "<"
259      class SmallerEqual(_Comparison):
260          symbol = "<="
261      class Equal(_Comparison):
262          symbol = "=="
263  
264          def __bool__(self):
265              # When == needs to be interpreted as a bool inside the script it is False
266              # It has only to be True when it compared to another Property.
267              return False
268  
269      # Convenience assigns
270      GT = Greater
271      GE = GreaterEqual
272      ST = Smaller
273      SE = SmallerEqual
274      EQ = Equal
275  # Convenience assigns
276  Ops = Operators