Source code for pdkmaster.technology.property_

# SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0
from typing import Any, Iterable, Tuple, Union, ClassVar, overload

from . import rule as _rle


__all__ = [
    "Enclosure", "PropertyT", "EnclosurePropertyT", "ComparisonT",
    "Operators", "Ops",
]


[docs]class Enclosure: """Enclosure object are representing the enclosure value of one layer over another layer. Most of the time it is used to indicae the minimum required enclosure of one layer over another layer. Enclosure constraints can symmetric or asymmetric. A symmetric enclosure can be specified with a single float value, an assymetric one with two float values. Internally always two float values are stored and be accessed through the `spec` attribute of an Enclosure object. For a symmetric object the two float values are the same. When an enclosure is used as a constraint symmetric means that the enclosure has to be met in all directions. Asymmetric normally means that a smaller enclosure in one direction is allowed when both enclosures in the other direction are bigger than the bigger enclosure value. For this case the order of the two value don't have a meaning. An enclosure can also be used to specify when doing layout generation. The PDKMaster convention here is that the order has a meaning; the first value is for the horizontal direction and the second value for the vertical one. Also giving meaning to the `wide()` and `tall()` object methods Enclosure objects are implemented as immutable objects. """ def __init__(self, spec: Union[float, Iterable[float]]): self._spec: Tuple[float, float] if isinstance(spec, float): self._spec = (spec, spec) else: self._spec = tuple(spec) if len(self.spec) != 2: raise ValueError( f"spec for Enclosure is either a float or 2 floats" ) @property def spec(self) -> Tuple[float, float]: return self._spec
[docs] @staticmethod def cast(v: Union[float, Iterable[float], "Enclosure"]) -> "Enclosure": if isinstance(v, Enclosure): return v else: return Enclosure(spec=v)
@property def first(self) -> float: return self.spec[0] @property def second(self) -> float: return self.spec[1] @property def is_assymetric(self) -> bool: return self.first != self.second
[docs] def min(self) -> float: return min(self.spec)
[docs] def max(self) -> float: return max(self.spec)
[docs] def wide(self) -> "Enclosure": # Put bigger enclosure value first if self.first >= self.second: return self else: return Enclosure(spec=(self.second, self.first))
[docs] def tall(self) -> "Enclosure": if self.first <= self.second: return self else: return Enclosure(spec=(self.second, self.first))
def __hash__(self) -> int: return hash(self._spec) def __eq__(self, other: Any) -> bool: if not isinstance(other, Enclosure): return False else: return ( (self.first == other.first) and (self.second == other.second) ) def __repr__(self) -> str: if not self.is_assymetric: return f"Enclosure({round(self.first, 6)})" else: return f"Enclosure(({round(self.spec[0], 6)},{round(self.spec[1], 6)}))"
class _Property: """This class represents a property of an object. Rules may be built from from a comparison operation. o = Property(name='width') rule = o >= 3.5 Then `rule` represents the rule for width greater or equal to 3.5. """ value_conv: Any = None value_type: Union[type, Tuple[type, ...]] = (float, int) value_type_str: str = "float" def __init__(self, *, name: str, allow_none: bool=False): self.name = name self.allow_none = allow_none value_type = self.value_type if not isinstance(value_type, tuple): if issubclass(value_type, _Property): raise TypeError("Property.value_type may not be 'Property'") self.dependencies = set() def __gt__(self, other) -> "ComparisonT": return Ops.Greater(left=self, right=other) def __ge__(self, other) -> "ComparisonT": return Ops.GreaterEqual(left=self, right=other) def __lt__(self, other) -> "ComparisonT": return Ops.Smaller(left=self, right=other) def __le__(self, other) -> "ComparisonT": return Ops.SmallerEqual(left=self, right=other) @overload def __eq__(self, other: "_Property") -> bool: ... # pragma: no cover @overload def __eq__(self, other: Any) -> "ComparisonT": ... # pragma: no cover def __eq__(self, other: Any) -> Union[bool, "ComparisonT"]: """The __eq__() method for Property can have two meanings. If it is compared with another Property object it will check if it is the same property. For another object it will generate a Rule object representing that property being equal to the provided value. """ try: return Ops.Equal(left=self, right=other) except TypeError: return isinstance(other, _Property) and (self.name == other.name) def __str__(self) -> str: return f"{self.name}" def __repr__(self) -> str: return f"{self.__class__.__name__}(name={self.name!r}, allow_non={self.allow_none!r})" def __hash__(self) -> int: return hash(self.name) def cast(self, value): if value is None: if self.allow_none: return None else: raise TypeError( f"property '{self.name}' given value '{value!r}' is not of type " f"'{self.value_type_str}'" ) value_conv = self.__class__.value_conv if value_conv is not None: try: value = value_conv(value) except: raise TypeError("could not convert property value {!r} to type '{}'".format( value, self.value_type_str, )) if not isinstance(value, self.value_type): raise TypeError( f"value '{value!r}' for property '{self.name}' is not of type " f"'{self.value_type_str}'", ) return value PropertyT = _Property class _EnclosureProperty(_Property): """An EnclosureProperty object is a Property with an Enclosure object as value. """ value_conv = Enclosure.cast value_type = Enclosure value_type_str = "'Enclosure'" EnclosurePropertyT = _EnclosureProperty class _Comparison(_rle._Condition): """A _Comparison object is a _Condition which represent the comparison of a Property object with a value. The operator for the comparison is represented as a string class variable names `symbol`. """ symbol: ClassVar[str] def __init__(self, *, left: _Property, right: Any): try: self.symbol except AttributeError: raise TypeError( f"class '{self.__class__.__name__}' does not have the" " symbol class variable defined" ) right2 = left.cast(right) super().__init__(elements=(left, right2)) self._elements: Tuple[_Property, Any] @property def left(self) -> _Property: return self._elements[0] @property def right(self) -> Any: return self._elements[1] def __eq__(self, other: object) -> bool: if not isinstance(other, _Comparison): return False else: return ( (self.symbol == other.symbol) and (self.left == other.left) and (self.right == other.right) ) def __str__(self) -> str: return f"{self.left} {self.symbol} {self.right}" def __repr__(self) -> str: return f"{self.left!r} {self.symbol} {self.right!r}" def __bool__(self) -> bool: raise TypeError("BinaryPropertyCondition can't be converted to a bool") ComparisonT = _Comparison
[docs]class Operators: """Operators is a class representing a bunch of boolean operators. """
[docs] class Greater(_Comparison): symbol = ">"
[docs] class GreaterEqual(_Comparison): symbol = ">="
[docs] class Smaller(_Comparison): symbol = "<"
[docs] class SmallerEqual(_Comparison): symbol = "<="
[docs] class Equal(_Comparison): symbol = "==" def __bool__(self): # When == needs to be interpreted as a bool inside the script it is False # It has only to be True when it compared to another Property. return False
# Convenience assigns GT = Greater GE = GreaterEqual ST = Smaller SE = SmallerEqual EQ = Equal
# Convenience assigns Ops = Operators