Skip to content

base

CompositeArgExpr

Bases: CompositeExpr

Composite expr which single args to init plus additional kwargs

Source code in slimfit/base.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
class CompositeArgExpr(CompositeExpr):
    """Composite expr which single args to init plus additional kwargs"""

    def __init__(self, arg, **kwargs):
        expr = {0: arg}
        self.kwargs = kwargs
        super().__init__(expr)

    def to_numerical(self):
        from slimfit.numerical import to_numerical

        arg = to_numerical(self.expr[0])
        instance = self.__class__(arg, **self.kwargs)

        return instance

CompositeArgsExpr

Bases: CompositeExpr

Composite expr which takes *args to init rather than dictionary of expressions

Source code in slimfit/base.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
class CompositeArgsExpr(CompositeExpr):
    """Composite expr which takes *args to init rather than dictionary of expressions"""

    def __init__(self, *args, **kwargs):
        expr = {i: arg for i, arg in enumerate(args)}
        self.kwargs = kwargs
        super().__init__(expr)

    def to_numerical(self):
        from slimfit.numerical import to_numerical

        args = (to_numerical(expr) for expr in self.values())
        instance = self.__class__(*args, **self.kwargs)

        return instance

CompositeExpr

Bases: SymbolicBase

Source code in slimfit/base.py
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
class CompositeExpr(SymbolicBase):
    """"""

    def __init__(
        self,
        expr: dict[str | NumExprBase | Expr | CompositeExpr | MatrixBase | np.ndarray, float],
    ):
        if not isinstance(expr, dict):
            raise TypeError(f"{self.__class__.__name__} must be initialized with a dict.")
        # for v in expr.values():
        #     if not isinstance(v, (NumExprBase, Expr, CompositeExpr, MatrixBase)):
        #         raise TypeError(f"Invalid type in expr dict: {v!r}.")

        self.expr = expr

        if self.is_numerical():
            self._call = self._numerical_call
        else:
            self._call = self._symbolic_call

    def _numerical_call(self, **kwargs):
        return {expr_name: expr(**kwargs) for expr_name, expr in self.expr.items()}

    def _symbolic_call(self, **kwargs):
        return self.numerical._call(**kwargs)

    def __call__(self, **kwargs) -> dict[str, np.ndarray]:
        return self._call(**kwargs)

    def __getitem__(self, item) -> NumExprBase | Expr:
        if isinstance(item, str):
            return self.expr.__getitem__(item)
        else:
            return super().__getitem__(item)

    def is_numerical(self) -> bool:
        """Returns `True` if all expressions are numerical expressions."""
        for v in self.values():
            # this should check for all base (non-composite) classes which are allowed and
            # can be converted to numerical
            # todo list this globally and check for this at init time
            if isinstance(v, (Expr, MatrixBase, HadamardProduct, np.ndarray)):
                return False
            if isinstance(v, CompositeExpr):
                # recursively check if composite parts are numerical
                return v.is_numerical()
        return True

    @cached_property
    def numerical(self) -> Optional[CompositeExpr]:
        if self.is_numerical():
            return self
        else:
            return self.to_numerical()

    def keys(self) -> KeysView[str]:
        return self.expr.keys()

    def values(self) -> ValuesView[NumExprBase, Expr]:
        return self.expr.values()

    def items(self) -> ItemsView[str, NumExprBase, Expr]:
        return self.expr.items()

    def to_numerical(self):
        from slimfit.numerical import to_numerical

        num_expr = {str(k): to_numerical(expr) for k, expr in self.items()}

        # TODO **unpack
        instance = self.__class__(num_expr)
        return instance

    @cached_property
    def symbols(self) -> set[Symbol]:
        """Return symbols in the CompositeNumExpr.
        sorting is by dependent_variables, variables, parameters, then by alphabet
        """

        # this fails because `free_symbols` is a dict on NumExpr but `set` on Expr

        symbols = set()
        for rhs in self.values():
            if isinstance(rhs, (Expr, MatrixBase)):
                symbols |= rhs.free_symbols
            else:
                try:
                    symbols |= set(rhs.symbols)
                except AttributeError:
                    # RHS doesnt have any symbols; for example might be a numpy array
                    pass

            # symbols = getattr(rhs, 'free_symbols')
            # try:
            #     # rhs is a sympy `Expr` and has `free_symbols` as a set
            #     symbols |= rhs.free_symbols
            # except TypeError:
            #     # rhs is a slimfit `NumExpr`
            #     symbols |= set(rhs.symbols)
            # except AttributeError:
            #     # RHS doesnt have any symbols; for example might be a numpy array
            #     pass
        return symbols

    @property
    def shapes(self) -> dict[str, Shape]:
        """shapes of symbols"""
        return reduce(or_(expr.shapes for expr in self.expr.values()))

    @property
    def shape(self) -> Shape:
        """
        Base class shape is obtained from broadcasting all expressing values together
        """
        shapes = (expr.shape for expr in self.values())
        return np.broadcast_shapes(*shapes)

shape property

Base class shape is obtained from broadcasting all expressing values together

shapes property

shapes of symbols

symbols cached property

Return symbols in the CompositeNumExpr. sorting is by dependent_variables, variables, parameters, then by alphabet

is_numerical()

Returns True if all expressions are numerical expressions.

Source code in slimfit/base.py
84
85
86
87
88
89
90
91
92
93
94
95
def is_numerical(self) -> bool:
    """Returns `True` if all expressions are numerical expressions."""
    for v in self.values():
        # this should check for all base (non-composite) classes which are allowed and
        # can be converted to numerical
        # todo list this globally and check for this at init time
        if isinstance(v, (Expr, MatrixBase, HadamardProduct, np.ndarray)):
            return False
        if isinstance(v, CompositeExpr):
            # recursively check if composite parts are numerical
            return v.is_numerical()
    return True

NumExprBase

Bases: SymbolicBase

Symbolic expression which allows calling cached lambified expressions subclasses must implement symbols attribute / property

Source code in slimfit/base.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
class NumExprBase(SymbolicBase):
    """Symbolic expression which allows calling cached lambified expressions
    subclasses must implement `symbols` attribute / property
    """

    # def __init__(
    #     self,
    # ):
    #
    #     # Accepted parameters are a subset of `symbols`
    #     # #todo property with getter / setter where setter filters parameters?
    #     # self.parameters = Parameters({name: p for name, p in parameters.items() if name in self.symbols})

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for name in self.symbol_names:
            if not is_valid_key(name):
                raise ValueError(f"Invalid symbol name: {name}; must be valid python identifier.")

    @property
    def shape(self) -> Shape:
        shapes = self.shapes.values()

        return np.broadcast_shapes(*shapes)

    def parse_kwargs(self, **kwargs) -> dict[str, np.ndarray]:
        """Parse kwargs and take only the ones in `free_parameters`"""
        try:
            arguments: dict[str, np.ndarray | float] = {k: kwargs[k] for k in self.symbol_names}
        except KeyError as e:
            raise KeyError(f"Missing value for {e}") from e

        return arguments

parse_kwargs(**kwargs)

Parse kwargs and take only the ones in free_parameters

Source code in slimfit/base.py
198
199
200
201
202
203
204
205
def parse_kwargs(self, **kwargs) -> dict[str, np.ndarray]:
    """Parse kwargs and take only the ones in `free_parameters`"""
    try:
        arguments: dict[str, np.ndarray | float] = {k: kwargs[k] for k in self.symbol_names}
    except KeyError as e:
        raise KeyError(f"Missing value for {e}") from e

    return arguments

SymbolicBase

Source code in slimfit/base.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class SymbolicBase(metaclass=abc.ABCMeta):
    @cached_property
    @abc.abstractmethod
    def symbols(self) -> set[Symbol]:
        ...

    @cached_property
    def symbol_names(self) -> set[str]:
        return set(s.name for s in self.symbols)

    @property
    def shapes(self) -> dict[str, Shape]:
        """
        dict of symbol shapes
        """

    def filter_parameters(self, parameters: Parameters) -> Parameters:
        """Filters a list of parameters, returning only the ones whose symbols are
        in this model
        """
        return Parameters([p for p in parameters if p.symbol in self.symbols])

    @property
    def T(self):
        return FuncExpr(self, func=np.transpose)

    def __getitem__(self, item):
        from slimfit.operations import Indexer

        return Indexer(self, item)

shapes property

dict of symbol shapes

filter_parameters(parameters)

Filters a list of parameters, returning only the ones whose symbols are in this model

Source code in slimfit/base.py
32
33
34
35
36
def filter_parameters(self, parameters: Parameters) -> Parameters:
    """Filters a list of parameters, returning only the ones whose symbols are
    in this model
    """
    return Parameters([p for p in parameters if p.symbol in self.symbols])

is_valid_key(key)

Checks if a string is a valid key to use as keyword argument in a function call.

Source code in slimfit/base.py
167
168
169
def is_valid_key(key: str) -> bool:
    """Checks if a string is a valid key to use as keyword argument in a function call."""
    return key.isidentifier() and not keyword.iskeyword(key)