diff --git a/src/core/calls.py b/src/core/calls.py index 9b8dc3f..85a12e4 100644 --- a/src/core/calls.py +++ b/src/core/calls.py @@ -22,9 +22,7 @@ __all__ = [ 'GetState', 'CallValue', 'CallError', - 'keep_calm_and_carry_on', 'run_safely', - 'to_async', ]; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/widgets/function_diagrams.py b/src/widgets/function_diagrams.py index 40efa24..1b90597 100644 --- a/src/widgets/function_diagrams.py +++ b/src/widgets/function_diagrams.py @@ -52,6 +52,12 @@ class FunctionDiagramWidget(): setsfrom: list[list[Any]]; card: list[int]; + ctrl_nr: widgets.IntSlider; + btn_refresh: widgets.ToggleButton; + ctrls_card: list[widgets.IntSlider]; + ctrls_property: list[widgets.IntSlider]; + fcts: Functions; + def __init__( self, fnames: list[str], @@ -60,7 +66,7 @@ class FunctionDiagramWidget(): N: Optional[int] = None, ): self.state = None; - self.N = N; + self.N = N or 1; self.N_max = len(fnames); assert len(setnames) == len(fnames) + 1, f'The number of sets must be {self.N_max+1}.'; self.fnames = fnames; @@ -81,35 +87,23 @@ class FunctionDiagramWidget(): self.card = [ min(3, len(XX)) for XX in self.setsfrom ]; def run(self): - if self.N is None: - control_nr = widgets.IntSlider(description=f'# Funktionen', min=1, max=self.N_max); - display(control_nr); - else: - control_nr = widgets.IntSlider(value=self.N); - self.state = widgets.interactive_output(self.handler_wrapper, controls={'N': control_nr}); - display(self.state); - - def handler_plot(self, N: int, show_labels: bool, **kwargs): - self.card[:(N+1)] = [ kwargs[f'card_{k}'] for k in range(N+1) ]; - properties = [ option_to_property(kwargs[f'property_{k}']) for k in range(N)]; - injective = list(map(is_injective, properties)); - surjective = list(map(is_surjective, properties)); - X = [ XX[:card] for XX, card in zip(self.setsfrom[:(N+1)], self.card[:(N+1)]) ]; - comp = Functions(*[ - Function( - name = (f'{self.fnames[k]}', f'{self.setnames[k]}', f'{self.setnames[k+1]}'), - domain = X[k], - codomain = X[k+1], - fct = random_function(X[k], X[k+1], injective=injective[k], surjective=surjective[k], force=False) - ) - for k in range(N) - ]); - fig, axs = comp.draw(show_labels=show_labels); + ''' + Runs the widget. + ''' + self.create_widgets(); + display(widgets.interactive_output( + f = self.handler_main, + controls = {'N': self.ctrl_nr}, + )); return; - def handler_wrapper(self, N: int): - button_refresh = widgets.ToggleButton(description='Neu laden'); - button_show_labels = widgets.Checkbox( + def create_widgets(self): + ''' + Initialises the widgets. + ''' + N = self.N_max; + self.btn_refresh = widgets.ToggleButton(description='Neu laden'); + self.btn_show_labels = widgets.Checkbox( description = 'Labels anzeigen?', value = True, style = { @@ -117,21 +111,31 @@ class FunctionDiagramWidget(): }, visible = True ); - control_nr = widgets.IntSlider(value=N); - controls_card = [ + self.ctrl_nr = widgets.IntSlider(value=self.N, description=f'# Funktionen', min=1, max=self.N_max); + cards_max = [ len(XX) for XX in self.setsfrom ]; + card_max_max = max(cards_max); + self.ctrls_card = [ widgets.IntSlider( value = card, description = f'|{setname}|', min = 1, - max = len(XX), + max = card_max, + layout = { + 'width': 'initial', + # FIXME: scales wrong: + # 'width': f'{100*cards_max/card_max_max}%', + }, + ) + for setname, XX, card, card_max in zip( + self.setnames[:(N+1)], + self.setsfrom[:(N+1)], + self.card[:(N+1)], + cards_max[:(N+1)], ) - for setname, XX, card in zip(self.setnames[:(N+1)], self.setsfrom[:(N+1)], self.card[:(N+1)]) ]; - - # controls for properties of functions - controls_property = [ + self.ctrls_property = [ widgets.RadioButtons( - options = [ e.value for e in possible_properties(all=True) ], + options = [ e.value for e in possible_properties(self.card[k], self.card[k+1]) ], value = FunctionProperty.NOTHING.value, layout = { 'description_width': 'initial', @@ -141,59 +145,121 @@ class FunctionDiagramWidget(): ) for k in range(N) ]; - # NOTE: dynamically change the possible properties based on the cardinalities. - def update_controls_property(change, k: int): - self.card[k] = change['new']; - if k > 0: - m = self.card[k-1]; - n = self.card[k]; - controls_property[k-1].options = [ e.value for e in possible_properties(m, n) ]; - if k < N: - m = self.card[k]; - n = self.card[k+1]; - controls_property[k].options = [ e.value for e in possible_properties(m, n) ]; + for k, ctrl_card in enumerate(self.ctrls_card): + ctrl_card.observe(partial(self.handler_upd_card, k=k), names='value'); + # FIXME: this does not behave correctly: + self.btn_show_labels.observe(partial(self.handler_plot, create=False)); + return; - for k, control_card in enumerate(controls_card): - control_card.observe( - partial(update_controls_property, k=k), - names='value', - ); - - ui = widgets.VBox( - [ button_refresh ] \ - + [ button_show_labels ] \ - + controls_card \ - + [ - widgets.HBox( - [ - widgets.VBox( - [ - widgets.Text(f'Eigenschaften von {fname}'), - control_property - ], - layout = { - 'description_width': 'initial', - 'width': 'initial', - 'display': 'flex', - 'align_items': 'stretch', - 'overflow': 'hidden', - } - ) - for fname, control_property in zip(self.fnames[:N], controls_property) - ], - ) - ], + def handler_main(self, *_, **__): + ''' + Main handler for updating widgets. + ''' + N = self.N = self.ctrl_nr.value; + ui = widgets.VBox([] \ + + [ self.btn_refresh ] \ + + [ self.btn_show_labels ] \ + + [ widgets.Text(f'Größen den Mengen:') ] \ + + self.ctrls_card[:(N+1)] \ + + [widgets.HBox( + [ + widgets.VBox( + [ + widgets.Text(f'Eigenschaften von {fname}'), + ctrl_property + ], + layout = { + 'description_width': 'initial', + 'width': 'initial', + 'display': 'flex', + 'align_items': 'stretch', + 'overflow': 'hidden', + } + ) + for fname, ctrl_property in zip(self.fnames[:N], self.ctrls_property[:N]) + ], + )], ); display(ui); display(widgets.interactive_output( f = self.handler_plot, - controls = { 'N': control_nr, 'show_labels': button_show_labels } \ - | { f'card_{k}': controls_card[k] for k in range(N+1) } \ - | { f'property_{k}': controls_property[k] for k in range(N) } \ - | { 'refresh': button_refresh } + controls = dict( + N = self.ctrl_nr, + refresh = self.btn_refresh, + # FIXME: shoud not have to rely on this: + show_labels = self.btn_show_labels, + **{ + f'card[{k}]': ctrl_card + for k, ctrl_card in enumerate(self.ctrls_card) + }, + **{ + f'property[{k}]': ctrl_property + for k, ctrl_property in enumerate(self.ctrls_property) + }, + ), )); return; + def handler_plot(self, *_, create: bool = True, **__): + ''' + Method to create sets, functions, and plot. + ''' + self.card = [ ctrl_card.value for ctrl_card in self.ctrls_card ]; + if create: + N = self.N; + options = list(map(lambda ctrl: ctrl.value, self.ctrls_property[:N])); + properties = list(map(option_to_property, options)); + injective = list(map(is_injective, properties)); + surjective = list(map(is_surjective, properties)); + sets = [ XX[:card] for XX, card in zip(self.setsfrom[:(N+1)], self.card[:(N+1)]) ]; + self.fcts = Functions(*[ + Function( + name = (fname, nameX, nameY), + domain = X, + codomain = Y, + fct = random_function(X, Y, injective=inj, surjective=surj, force=False), + ) + for fname, nameX, nameY, X, Y, inj, surj in zip( + self.fnames, + self.setnames, + self.setnames[1:], + sets, + sets[1:], + injective, + surjective, + ) + ]); + # FIXME: + # upon toggling show labels button, this even triggers multiple times, + # and only the old value registers. + show_labels = self.btn_show_labels.value; + fig, axs = self.fcts.draw(show_labels=show_labels); + return; + + def handler_upd_card(self, change, k: int): + ''' + Handler for altering function property options + upon change of set cardinality. + ''' + self.card[k] = change['new']; + + def upd(j: int): + m = self.card[j]; + n = self.card[j+1]; + value = self.ctrls_property[j].value; + options = [ e.value for e in possible_properties(m, n) ]; + if value not in options: + value = FunctionProperty.NOTHING.value; + self.ctrls_property[j].options = options; + self.ctrls_property[j].value = value; + return; + + if k > 0: + upd(k-1); + if k < self.N: + upd(k); + return; + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # AUXILIARY METHODS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -204,36 +270,42 @@ def option_to_property(option: str) -> Optional[FunctionProperty]: except: return None; -def possible_properties(m: int = 1, n: int = 1, all: bool = False) -> list[FunctionProperty]: - if not all: - if m == 1 and n == 1: +def possible_properties(m: int = 1, n: int = 1) -> list[FunctionProperty]: + if m == n: + if m == 1: return [ e for e in FunctionProperty if (is_injective(e) not in [False]) and (is_surjective(e) not in [False]) ]; - elif m == 1 and n > 1: + else: return [ e for e in FunctionProperty - if (is_injective(e) not in [False]) - and (is_surjective(e) not in [True]) - ]; - elif n == 1 and m > 1: - return [ - e for e in FunctionProperty - if (is_surjective(e) not in [False]) - and (is_injective(e) not in [True]) - ]; - elif m > n: - return [ - e for e in FunctionProperty - if (is_injective(e) not in [True]) - ]; - elif n > m: - return [ - e for e in FunctionProperty - if (is_surjective(e) not in [True]) + if not (is_surjective(e) in [True] and is_injective(e) in [False]) + and not (is_surjective(e) in [False] and is_injective(e) in [True]) ]; + elif m == 1 and n > 1: + return [ + e for e in FunctionProperty + if (is_injective(e) not in [False]) + and (is_surjective(e) not in [True]) + ]; + elif n == 1 and m > 1: + return [ + e for e in FunctionProperty + if (is_surjective(e) not in [False]) + and (is_injective(e) not in [True]) + ]; + elif m > n: + return [ + e for e in FunctionProperty + if (is_injective(e) not in [True]) + ]; + elif n > m: + return [ + e for e in FunctionProperty + if (is_surjective(e) not in [True]) + ]; return [ e for e in FunctionProperty ]; def is_injective(property: Optional[FunctionProperty]) -> Optional[bool]: