DEXiPy Classes and Data Types ============================= Classes ------- DexiModel :py:class:`dexipy.dexi.DexiModel` is a top-level DEXiPy class that represents a whole DEXi model. The hieararchy of model attributes starts at `root`. A `DexiModel` object generally contains its `name` and `description` strings, lists of attributes and their IDs, and a list of alternatives. DexiAttribute :py:class:`dexipy.dexi.DexiAttribute` is a class representing measurable properties of decision alternatives. Attributes are structured hierarchically, therefore each attribute instance contains a list `inputs` of its input attributes, i.e., immediate descendants in the hierarchy. When fully defined, each attribute has an associated `scale`, which is an object of a `DexiScale` class. Furthermore, each aggregate attribute (non-terminal node in the hierarchy) is normally associated with a `DexiFunction` object `funct`, which governs the aggregation of input values to the value of that attribute. Also, an attribute has a `name`, optional `description` and an ID string that is unique in the model context. DexiScale The :py:class:`dexipy.dexi.DexiScale` class defines the set of values that can be assigned to the corresponding attribute. A scale might, but need not be preferentially ordered. While `DexiScale` is a base class, there are two derived scale types that are actually used in DEXi models: DexiContinousScale This scale can be associated with basic attributes, i.e., terminal nodes of the model. Only `float` values can be assigned to such attributes. .. note:: Continuous scales have been introduced in DEXiWin and are not supported in DEXi. DexiDiscreteScale A discrete scale defines an ordered list of discrete values that can be assigned to the corresponding attribute, for instance ``["low", "medium", "high"]``. This scale type can be associated with both basic and aggregate attributes. DexiFunction :py:class:`dexipy.dexi.DexiFunction` is a base class for functions that aggregate or discretize the values of some attribute's inputs to the value of that attribute. Two types of functions are used in DEXiPy: DexiDiscretizeFunction This function type is used to map numeric values of a continuous basic attribute to discrete values of a single discrete parent attribute. .. note:: This function type has been introduced in DEXiWin and is not supported in DEXi. DexiTabularFunction This is the main DEXi aggregation function type that aggregates multiple discrete inputs to a single discrete parent attribute. Essentially, a `DexiTabularFunction` consists of a lookup table that defines the output value for all combinations of input values. Each table entry is referred to as an *elementary decision rule*. .. _dexivalues: DEXi values ----------- *DEXi values* are used throughout DEXi models. They provide input values and carry results of evaluations of decision alternatives. DEXi values are also used in definitions of :py:class:`dexipy.dexi.DexiFunction` and are returned by :py:meth:`dexipy.dexi.DexiFunction.evaluate` when evaluating some function for a given set of arguments. In DEXi, values are always bound to the context provided by a :py:class:`dexipy.dexi.DexiScale`. Since each fully defined :py:class:`dexipy.dexi.DexiAttribute` is associated with some scale, we can generalize the scale context to attributes and speak about "assigning some value to an attribute". In DEXiPy, DEXi values are *not* represented by classes, but rather by different data types that are interpreted in the context of given attributes and their scales. Module :py:mod:`dexipy.types` defines the basic type:: type DexiValue = None | float | set[int] | list[float] `DexiValue` defines data types that prevalently use numeric data and serve for *internal* representation of values in DEXiPy. None of this repesentations require a `DexiAttribute` or `DexiScale` context in order to be interpreted. The `DexiValueSpecification` type extends `DexiValue` to facilitate a user-friendly entry and display of DEXi values. It adds the `tuple` and `dict` value representations and allows using strings individually and in tuples, sets and dictionaries. In this way, it is possible to refer to discrete scale values by their names rather than ordnila numbers and indices:: type DexiValueSpecification = DexiValue | str | set[int | str] | tuple[int | str, ...] | dict[int | str, float] For any scale type, the admissible `DexiValueSpecification` values are ``None``, ``""`` or any string starting with ``"undef"``, indicating an unavailable or unknown value. All evaluations involving ``None`` result in `None`. `DexiContinousScale` allows only floating-point numbers. `DexiDiscreteScale`, as the main scale type used throughout DEXi models, supports a wider range of value types. The "normal" and most common discrete value is a "single qualitative value". For illustration, let us use the scale composed of four qualitative values: ``["unacc", "acc", "good", "exc"]``. Then, "a single qualitative value" denotes one of these words. Internally in DEXiPy, such values are not represented by strings, but rather by ordinal numbers, so that ``ord("unacc") == 0``, ``ord("acc") == 1``, etc. Some DEXiPy functions can convert between the two representations, for example :py:meth:`dexipy.dexi.DexiModel.evaluate` and :py:func:`dexipy.dexi.alternative`. In order to cope with missing, incomplete or uncertain data, DEX extends the concept of single values to value *sets* and *distributions*. In DEXiPy, wherever it is possible to use a single qualitative value, it is also possible to use a value set or distribution. This includes all data representing :ref:`alternatives ` and all functions that return qualitative values. .. note:: DEXi software supports only sets. Value distributions have been introduced in DEXiWin. A *DEXi value set* is a subset of the full range of a :py:class:`dexipy.dexi.DexiDiscreteScale` values. For the above scale example, the full range of ordinal values is ``{0, 1, 2, 3}``, and some possible subsets are ``{1}``, ``(1, 3)``, and ``(1, 2, 3)``. Both tuples and sets can be used, however tuples are internally converted to sets. The string ``"*"`` can be used to indicate a full set of values of the corresponding discrete scale. When a value set is used in a scale context, it can be specified also in terms of value names, for instance ``{"acc"}``, ``{"acc", "exc"}``, and ``("acc", "good", "exc")``. Mixed format is acceptable, too: ``("acc", 2, "exc")``. This format can be used in dictionaries that contain data about alternatives, and is also produced by :py:meth:`dexipy.dexi.DexiModel.alt_text`. A *DEXi value distribution* associates each `DexiDiscreteScale` value with some number, generally denoted :math:`p` and normally expected to be the [0,1] interval. Depending on the context and used evaluation method (see :ref:`evaluation`), :math:`p` can be interpreted as a *probability* or *fuzzy set membership*. In DEXiPy, value distributions are internally represented by a list of floating-point numbers. For example, ``[0.5, 0, 0.2, 0.3]`` represents a value distribution over the above scale example, assigning * :math:`p` = 0.5 to ``"unacc"``, * :math:`p` = 0.0 to ``"acc"``, * :math:`p` = 0.2 to ``"good"`` and * :math:`p` = 0.3 to ``"exc"``. An extended and generally more readable external representation of distributions uses the dictionary format, for instance: * using value indices: ``{0: 0.5, 2: 0.2, 3: 0.3}`` * using value names: ``{"unacc": 0.5, "good": 0.2, "exc": 0.3}`` * mixed: ``{"unacc": 0.5, 2: 0.2, "exc": 0.3}`` .. _alternatives: Alternatives ------------ Alternatives (more specifically, *decision alternatives*) are objects evaluated by DEXi models. In DEXiPy, an alternative is represented by a dictionary whose: * *keys* denote attributes, and * *values* contain :ref:`dexivalues` of the corresponding attributes. In general, *keys* can be either string attribute IDs or integer attribute indices (the former are preferred). In addition, there are two special keys ``"name"`` and ``"description"``, which provide a name and description string of the alternative; both are optional. Attribute keys can refer to any attribute in the model. An alternative that is to be evaluated by :py:meth:`dexipy.dexi.evaluate` is expected to contain key/value pairs of all basic attributes of the model. After this alternative has been evaluated, calculated values that correspond to aggregate attributes are added to the dictionary, possibly overwriting previous values. In :py:mod:`dexipy.types`, the following alternatives-related types are defined:: type DexiAlternative = dict[str, DexiValue | str] type DexiAlternatives = list[DexiAlternative] type DexiAltData = DexiAlternative | DexiAlternatives `DexiAlternative` represents a single alternative. `DexiAlternatives` represents a list of alternatives. `DexiAltData` denotes a union of both representations. Example of an evaluated alternative:: {'name': 'MyCar1a', 'CAR': {0, 3}, 'PRICE': {0, 2}, 'BUY.PRICE': 2, 'MAINT.PRICE': {0, 1, 2}, 'TECH.CHAR.': 2, 'COMFORT': 2, '#PERS': 2, '#DOORS': 2, 'LUGGAGE': 2, 'SAFETY': 1} Representation using attribute indices is also possible:: {'name': 'MyCar1a', 1: {0, 3}, 2: {0, 2}, 3: 2, 4: {0, 1, 2}, 5: 2, 6: 2, 7: 2, 8: 2, 9: 2, 10: 1} Multiple alternatives can be combined together in a list. The method :py:meth:`dexipy.dexi.DexiFunction.evaluate`, which evaluates alternatives, accepts `DexiAltData`, i.e., either a single alternative or a list of alternatives. .. _evaluation: Evaluation of alternatives -------------------------- In DEXiPy, decision alternatives can be evaluated using the method :py:meth:`dexipy.dexi.DexiModel.evaluate` or function :py:meth:`dexipy.dexi.evaluate`. They accept almost the same arguments and the former is actually just a suitable alias for calling the latter. Essentially, *evaluation of alternatives* in DEX is a bottom-up aggregation method: starting with basic attributes (or "pruned" aggregate attributes), values of each alternative are gradually aggregated towards the root attribute. The aggregation at each individual :py:class:`dexipy.dexi.DexiAttribute` is governed by the corresponding :py:class:`dexipy.dexi.DexiFunction`. When alternative values are sets or distributions (see :ref:`dexivalues`), then `evaluate()` traverses all possible combinations of values of input attributes. Four aggregation methods are supported: "set", "prob", "fuzzy" and "fuzzynorm". .. note:: DEXi software supports only the "set" evaluation method. DEXiWin supports all methods. The "set" method interprets DEXi values as sets. The output value assigned to some `attribute` is composed of the union of all `attribute.funct` evaluations for all possible combinations of values of `attribute.inputs`. The remaining three methods interpret DEXi values as value distributions: - "prob" as probability distributions (requires :math:`\sum{p} = 1` in value distributions), - "fuzzy" as fuzzy sets, and - "fuzzynorm" as normalized fuzzy sets (requires :math:`\max{p} = 1` in value distributions). This functionality is achieved by using different methods (see :py:class:`dexipy.eval.DexiEvalParameters`) for normalization, and conjunctive and disjunctive aggregation of values. The evaluation at some aggregate `attribute` is carried out in four steps: 1. All value distributions involved in calculations are normalized by the method `DexiEvalParameters.norm`. 2. All combinations of `attribute.inputs`' values are individually evaluated by the corresponding tabular function `attribute.funct`. 3. The value :math:`p` of each set of `attribute.funct` arguments is determined by the *conjunctive* aggregation function `DexiEvalParameters.and_op` over :math:`p`'s of individual arguments. 4. The :math:`p` of some output value `val` is determined by the *disjunctive* aggregation function `DexiEvalParameters.or_op`, applied on the :math:`p`'s of all partial evaluations that map to `val`. For mathematical background and more details about aggregation in DEX, please see (Trdin, Bohanec, 2018).