Source code for kqcircuits.util.parameters

# This code is part of KQCircuits
# Copyright (C) 2021 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).


from functools import lru_cache
from kqcircuits.pya_resolver import pya


[docs] def add_parameters_from(cls, /, *param_names, **param_with_default_value): """Decorator function to add parameters to the decorated class. Only the named parameters are added or changed. Use a starting wildcard (``"*"``) argument to get all parameters of ``cls``. For simplicity, if nothing is specified it gets all parameters. Parameters in ``param_names`` after the starting ``"*"`` will be *excluded*. The ``"*"`` is also useful together with ``param_with_default_value`` to get all parameters but change some. Args: cls: the class to take parameters from *param_names: parameter names to take (or remove if '*' is the first) **param_with_default_value: dictionary of parameter names and new default values """ invert = not param_names and not param_with_default_value if param_names and param_names[0] == "*": param_names = param_names[1:] invert = True unknown = (set(param_names) | set(param_with_default_value.keys())) - set(cls.get_schema().keys()) if unknown: raise ValueError(f"Parameter(s) {unknown} not available in '{cls.__name__}'") def _decorate(obj): for name, p in cls.get_schema().items(): if name in param_with_default_value: # Redefine the Param object, if needed, because multiple elements may refer to it if param_with_default_value[name] != p.default: p = Param(p.data_type, p.description, param_with_default_value[name], **p.kwargs) elif invert ^ (name not in param_names): continue setattr(obj, name, p) p.__set_name__(obj, name) return obj return _decorate
[docs] def add_parameter(cls, name, **change): """Decorator function to add a single parameter to the decorated class. Makes it possible to have fine-grained control over the Parameter's properties. Particularly, changing the "hidden" or the "choices" property of the parameter. Args: cls: the class to take parameters from name: name of the re-used parameter **change: dictionary of properties to change, like ``hidden=True`` """ schema = cls.get_schema() if name not in schema: raise ValueError(f"Parameter {name} not available in '{cls.__name__}'") def _decorate(obj): p = schema[name] if change: # Redefine the parameter in case of any change kwargs = {**p.kwargs, "description": p.description, "default": p.default, **change} p = Param(p.data_type, **kwargs) setattr(obj, name, p) p.__set_name__(obj, name) return obj return _decorate
@lru_cache(maxsize=None) def _get_restricted_parameter_names(): """All members of PCellDeclarationHelper should be considered restricted, with exception of "name" as that is used by simulations. """ return [param_name.lower() for param_name in dir(pya.PCellDeclarationHelper) if param_name != "name"] def _prevent_restricted_parameter_names(param_owner, param_name): """Declaring a parameter which shares a name with some member variable or member function of pya.PCellDeclarationHelper (superclass of KQC's Element) causes an exception or even a crash with confusing message. Detect restricted parameter name here and give a more helpful error message. TODO: This doesn't prevent irrecoverable crash Args: param_owner: Element object that is checked param_name: name of the parameter that is checked """ if param_name.lower() in _get_restricted_parameter_names(): raise ValueError( f"{type(param_owner).__name__} contains a parameter with a restricted" f" name '{param_name.lower()}', please rename it to something else!\n" )
[docs] class pdt: # pylint: disable=invalid-name """A namespace for pya.PCellParameterDeclaration types.""" TypeDouble = pya.PCellParameterDeclaration.TypeDouble TypeInt = pya.PCellParameterDeclaration.TypeInt TypeList = pya.PCellParameterDeclaration.TypeList TypeString = pya.PCellParameterDeclaration.TypeString TypeNone = pya.PCellParameterDeclaration.TypeNone TypeShape = pya.PCellParameterDeclaration.TypeShape TypeBoolean = pya.PCellParameterDeclaration.TypeBoolean TypeLayer = pya.PCellParameterDeclaration.TypeLayer TypeNone = pya.PCellParameterDeclaration.TypeNone
[docs] class Param: """PCell parameters as Element class attributes. This should be used for defining PCell parameters in Element subclasses. The attributes of Param are same as ``pya.PCellParameterDeclaration``'s attributes, except for: * ``data_type``: same as ``type`` in ``pya.PCellParameterDeclaration`` * ``choices``: List of (description, value) tuples or plain ``str`` values that are used as description too. * ``docstring``: Longer description of the parameter that gets used by Sphinx to generate API docs. """ _index = {} # A private dictionary of parameter dictionaries indexed by owner classes' name
[docs] @classmethod def get_all(cls, owner): """Get all parameters of given owner. Args: owner: get all parameters of this class Returns: a name-to-Param dictionary of all parameters of class `owner` or an empty one if it has none. """ owner_name = f"{owner.__module__}.{owner.__qualname__}" if owner_name in cls._index: return cls._index[owner_name] else: return {}
def __init__(self, data_type, description, default, **kwargs): self.data_type = data_type self.description = description self.default = default self.kwargs = kwargs def __set_name__(self, owner, name): self.name = name owner_name = f"{owner.__module__}.{owner.__qualname__}" if owner_name not in self._index: self._index[owner_name] = {} self._index[owner_name][name] = self def __get__(self, obj, objtype): _prevent_restricted_parameter_names(obj, self.name) if obj is None or not hasattr(obj, "_param_values") or obj._param_values is None: return self.default if hasattr(obj, "_param_value_map"): # Element return obj._param_values[obj._param_value_map[self.name]] else: # Simulation return obj._param_values[self.name] def __set__(self, obj, value): _prevent_restricted_parameter_names(obj, self.name) if not hasattr(obj, "_param_values") or obj._param_values is None: obj._param_values = {} if hasattr(obj, "_param_value_map"): obj._param_values[obj._param_value_map[self.name]] = value else: obj._param_values[self.name] = value