Source code for discrete_optimization.generic_tools.encoding_register

#  Copyright (c) 2025 AIRBUS and its affiliates.
#  This source code is licensed under the MIT license found in the
#  LICENSE file in the root directory of this source tree.

from __future__ import annotations

from dataclasses import dataclass
from typing import Collection, Mapping


[docs] class AttributeType: length: int
[docs] @dataclass class Permutation(AttributeType): """Attribute type for permutation.""" range: Collection[int] """List of integers included in the permutation.""" @property def length(self): """Nb of elements in the permutation.""" return len(self.range)
[docs] @dataclass class ListInteger(AttributeType): """Attribute type for list of integers with variable bounds.""" length: int # length of the list lows: list[int] # lower bound for each element of the list ups: list[int] # upper bound for each element of the list arities: list[int] # nb of possible values for each element of the list def __init__( self, length: int, lows: list[int] | int = 0, ups: list[int] | int | None = None, arities: list[int] | int | None = None, ): """ Args: length: length of the list ups: upper bound for each element of the list. If an integer, assumed to be the same for each element. If None, deduced from lows + arities lows: lower bounds for each element of the list. If an integer, assumed to be the same for each element. arities: used only if ups is None (cannot be None at the same time as it). Number of possible values for each element of the list. If an integer, assumed to be the same for each element. """ self.length = length match lows: case int(): self.lows = [lows] * length case _: self.lows = lows match ups: case None: match arities: case None: raise ValueError("ups and arities cannot be None together") case int(): arities_list = [arities] * length case _: arities_list = arities self.ups = [ low + arity - 1 for low, arity in zip(self.lows, arities_list) ] case int(): self.ups = [ups] * length case _: self.ups = ups @property def arities(self): return [up - low + 1 for low, up in zip(self.lows, self.ups)]
[docs] @dataclass class ListBoolean(ListInteger): def __init__(self, length: int): super().__init__(length=length, lows=0, ups=1)
[docs] class EncodingRegister(Mapping[str, AttributeType]): """List the encoding attributes of a solution. Only the ones to be used by genetic algorithms and local search have to be specified. Attributes: dict_attribute_to_type (dict[str, AttributeType]): specifies the encodings of a solution object. Maps an attribute name to an attribute type. Each attribute name (key of the mapping) should correspond to - an actual attribute of the solution (solution.<attribute_name> should exist) with the proper type - a licit argument of __init__() method of the solution (at least to be used in genetic algorithms) You can bypass the latter hypothesis by overriding `build_solution_from_encoding()` """ def __init__(self, dict_attribute_to_type: dict[str, AttributeType]): self.dict_attribute_to_type = dict_attribute_to_type def __getitem__(self, key, /): return self.dict_attribute_to_type[key] def __len__(self): return len(self.dict_attribute_to_type) def __iter__(self): return iter(self.dict_attribute_to_type)
[docs] def get_first_attribute_of_type( self, attribute_type_cls: type[AttributeType] ) -> str: attributes = [k for k, t in self.items() if isinstance(t, attribute_type_cls)] if len(attributes) > 0: return attributes[0] else: raise ValueError( f"The encoding register must have at least one attribute of type {attribute_type_cls}." )
def __str__(self) -> str: return "Encoding : " + str(self.dict_attribute_to_type)