diff --git a/src/maths/diagrams/sets.py b/src/maths/diagrams/sets.py index e4e3f9f..9238f20 100644 --- a/src/maths/diagrams/sets.py +++ b/src/maths/diagrams/sets.py @@ -28,7 +28,7 @@ __all__ = [ T1 = TypeVar('T1'); T2 = TypeVar('T2'); -SCALE = (1., 4.); +SCALE = (1., 1.5); OFFSET = (3., 0.); HMARGIN = 0.1; VMARGIN = 0.2; @@ -119,15 +119,15 @@ class Functions: for k, f in enumerate(self.fcts): if k == 0: comp_range = f.domain; - p_domain = random_points(nr_points=len(f.domain), scale=SCALE, centre=origin + k*offset); + p_domain = random_points(nr_points=len(f.domain), scale=SCALE, centre=origin + k*offset, tol=0.5); else: p_domain = p_codomain; - p_codomain = random_points(nr_points=len(f.codomain), scale=SCALE, centre=origin + (k+1)*offset); + p_codomain = random_points(nr_points=len(f.codomain), scale=SCALE, centre=origin + (k+1)*offset, tol=0.5); # range of composition so far: comp_range_next = [y for x, y in f.fct if x in comp_range]; if k == 0: - axs.scatter(p_domain[:, 0], p_domain[:, 1], label='', color='black', marker='o'); + axs.scatter(p_domain[:, 0], p_domain[:, 1], label='', color='blue', marker='o'); if show_labels: for i, p in enumerate(p_domain): x_name = f.domain[i]; @@ -136,7 +136,7 @@ class Functions: for j, p in enumerate(p_codomain): y = f.codomain[j]; marker = 'o' if (y in comp_range_next) else '.'; - axs.scatter([p[0]], [p[1]], label='', color='black', marker=marker); + axs.scatter([p[0]], [p[1]], label='', color='blue', marker=marker); y_name = f.codomain[j]; if show_labels: axs.annotate(text=f'{y_name}', xy=p, textcoords='offset points', xytext=ANNOTATE_OFFSET, ha='center', size=FONTSIZE_PTS); @@ -146,7 +146,7 @@ class Functions: q = p_codomain[j]; x = f.domain[i]; if k == 0 or (x in comp_range): - axs.plot([p[0], q[0]], [p[1], q[1]], label='', color='black', linewidth=1); + axs.plot([p[0], q[0]], [p[1], q[1]], label='', color='blue', linewidth=1); else: axs.plot([p[0], q[0]], [p[1], q[1]], label='', color='red', linestyle='--', linewidth=1); @@ -165,6 +165,7 @@ class Functions: # update range of composition: comp_range = comp_range_next; + axs.set_aspect('equal') return fig, axs; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -188,7 +189,7 @@ def random_points( scale: tuple[float, float] = (1., 1.), centre: tuple[float, float] = (0., 0.), force: bool = False, - tol: float = 0.2, + tol: float = 0.1, ) -> NDArray[Shape['*, 2'], Float]: theta = np.linspace(start=0, stop=2*np.pi, num=nr_points, endpoint=False); r_min = 0.25; diff --git a/src/maths/sets/__init__.py b/src/maths/sets/__init__.py index e1903e7..f09d679 100644 --- a/src/maths/sets/__init__.py +++ b/src/maths/sets/__init__.py @@ -12,8 +12,8 @@ from src.maths.sets.random import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __all__ = [ + 'Letters', 'random_function', 'randomset_alphabet', - 'randomset_greek', 'randomset_integers', ]; diff --git a/src/maths/sets/random.py b/src/maths/sets/random.py index c1140fe..d472e76 100644 --- a/src/maths/sets/random.py +++ b/src/maths/sets/random.py @@ -15,9 +15,9 @@ from src.thirdparty.maths import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __all__ = [ + 'Letters', 'randomset_integers', 'randomset_alphabet', - 'randomset_greek', 'random_function', ]; @@ -25,29 +25,68 @@ __all__ = [ # CONSTANTS / VARIABLES # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -ALPHA = 'abcdefghijklmnopqrstuvwxyz'; -GREEK = 'αβγδεζηθικλμνξοπρςτυφχψω'; +# local usage only T1 = TypeVar('T1'); T2 = TypeVar('T2'); +class Letters(Enum): + # 'abcdefghijklmnopqrstuvwxyz' + ROMAN = [chr(97 + n) for n in range(26)]; + # 'αβγδεζηθικλμνξοπρςτυφχψω' + GREEK = [chr(945 + n) for n in range(25)] + # 'אבגדהוזחטיךכלםמןנסעףפץצקרשת' (but <—) + HEBREW = [chr(1488 + n) for n in range(27)]; + SYMBOLS = [ + r'$|0\rangle$', + r'$|\uparrow\rangle$', + r'$|\downarrow\rangle$', + r'$\sqrt{2}$', + r'$\pi$', + r'$e$', + r'$\frac{1}{137}$', + r'$\infty$', + # r'$-\infty$', + r'$\clubsuit$', + # r'$\diamondsuit$', + r'$\heartsuit$', + # r'$\spadesuit$', + r'$\hbar$', + ]; + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # METHODS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -def randomset_integers(N: int = -1, low: int = 1, high: int = 1) -> list[int]: +def randomset_integers( + N: int = -1, + low: int = 1, + high: int = 1, + shuffle: bool = False, +) -> list[int]: if N == -1: N = random.randint(low, high); - return list(range(1, N+1)); + values = list(range(1, N+1)); + if shuffle: + return sample(values, size=N, replace=False); + return values; -def randomset_alphabet(N: int = -1, low: int = 1, high: int = 1) -> list[int]: - if N == -1: +def randomset_alphabet( + mode: Letters, + N: int = -1, + low: int = 1, + high: int = 1, + shuffle: bool = False, + full: bool = False, +) -> list[str]: + letters: list[str] = mode.value; + if full: + N = len(letters); + elif N == -1: + high = min(low+1, high) N = random.randint(low, high); - return list([a for k, a in enumerate(ALPHA) if k < N]); - -def randomset_greek(N: int = -1, low: int = 1, high: int = 1) -> list[int]: - if N == -1: - N = random.randint(low, high); - return list([a for k, a in enumerate(GREEK) if k < N]); + if shuffle: + return sample(letters, size=N, replace=False); + return letters[:N]; def random_function( X: list[T1], diff --git a/src/thirdparty/maths.py b/src/thirdparty/maths.py index b7fbf06..28b31ab 100644 --- a/src/thirdparty/maths.py +++ b/src/thirdparty/maths.py @@ -9,6 +9,29 @@ from fractions import Fraction; import math; import numpy as np; import random; +from typing import TypeVar; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# MODIFICATIONS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# local usage only +T = TypeVar('T'); + +def sample( + X: list[T], + size: int = 1, + replace: bool = True, +) -> list[T]: + ''' + @inputs + - `X` - a list + - `size` - desired sample size + - `replace` - optional replacement + + @returns a sample from an uniformly distributed set. + ''' + return np.random.choice(X, size=size, replace=replace).tolist(); # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # EXPORTS @@ -19,4 +42,5 @@ __all__ = [ 'math', 'np', 'random', + 'sample', ]; diff --git a/src/widgets/function_diagrams.py b/src/widgets/function_diagrams.py index e370d26..596d7cf 100644 --- a/src/widgets/function_diagrams.py +++ b/src/widgets/function_diagrams.py @@ -9,6 +9,7 @@ from __future__ import annotations; from src.thirdparty.types import *; from src.thirdparty.plots import *; +from src.thirdparty.maths import *; from src.thirdparty.render import *; from src.maths.diagrams import *; @@ -32,11 +33,13 @@ class FunctionDiagramWidget(): N_max: int; fnames: list[str]; setnames: list[str]; + setsfrom: list[list[Any]]; def __init__( self, fnames: list[str], setnames: list[str], + setsfrom: dict[str, list[Any]] | list[list[Any]] = [], N: Optional[int] = None, ): self.state = None; @@ -45,6 +48,19 @@ class FunctionDiagramWidget(): assert len(setnames) == len(fnames) + 1, f'The number of sets must be {self.N_max+1}.'; self.fnames = fnames; self.setnames = setnames; + # fix set contents: + if isinstance(setsfrom, dict): + default = randomset_alphabet(mode=Letters.ROMAN, shuffle=True, full=True); + setsfrom = [ setsfrom.get(name, default) for name in setnames ]; + if len(setsfrom) == 0: + setsfrom = [ + randomset_alphabet(mode=Letters.SYMBOLS, shuffle=True, full=True), + randomset_alphabet(mode=Letters.ROMAN, shuffle=True, full=True), + randomset_alphabet(mode=Letters.HEBREW, shuffle=True, full=True), + randomset_alphabet(mode=Letters.GREEK, shuffle=True, full=True), + ]; + n = len(setsfrom); + self.setsfrom = [ setsfrom[k % n] for k, name in enumerate(setnames) ]; def run(self): if self.N is None: @@ -59,7 +75,7 @@ class FunctionDiagramWidget(): cardinalities = [ kwargs[f'card_{k}'] for k in range(N+1)]; injective = [ kwargs[f'injective_{k}'] for k in range(N)]; surjective = [ kwargs[f'surjective_{k}'] for k in range(N)]; - X = [ randomset_alphabet(N=card) for card in cardinalities ]; + X = [ XX[:card] for XX, card in zip(self.setsfrom, cardinalities) ]; comp = Functions(*[ Function( name = (f'{self.fnames[k]}', f'{self.setnames[k]}', f'{self.setnames[k+1]}'), @@ -85,38 +101,47 @@ class FunctionDiagramWidget(): return; def handler_wrapper(self, N: int): - show_labels = widgets.Checkbox(description='Labels anzeigen?', style={'description_width': 'initial'}, visible=True); + button_refresh = widgets.ToggleButton(description='Neu laden'); + button_show_labels = widgets.Checkbox( + description = 'Labels anzeigen?', + value = True, + style = { + 'description_width': 'initial', + }, + visible = True + ); control_nr = widgets.IntSlider(value=N); controls_card = [ widgets.IntSlider( - value = None, - description = f'|{self.setnames[k]}|', + # value = None, + value = min(3,len(XX)), + description = f'|{setname}|', min = 1, - max = 24, + max = len(XX), ) - for k in range(N+1) + for setname, XX in zip(self.setnames[:(N+1)], self.setsfrom[:(N+1)]) ]; controls_injective = [ widgets.Checkbox( - value = None, - description = f'{self.fnames[k]} injektiv?', + value = False, + description = f'{fname} injektiv?', style = { 'description_width': 'initial', }, visible = True, ) - for k in range(N) + for fname in self.fnames[:N] ]; controls_surjective = [ widgets.Checkbox( - value = None, - description = f'{self.fnames[k]} surjectiv?', + value = False, + description = f'{fname} surjectiv?', style = { 'description_width': 'initial', }, visible = True, ) - for k in range(N) + for fname in self.fnames[:N] ]; # controls_func = [None for _ in range(2*N)]; # controls_func[::2] = controls_injective; @@ -136,19 +161,33 @@ class FunctionDiagramWidget(): # for k in range(N) # ]; ui = widgets.VBox( - [show_labels] \ + [ button_refresh ] \ + + [ button_show_labels ] \ + controls_card \ + [ - widgets.HBox([control_injective, control_surjective]) - for control_injective, control_surjective in zip(controls_injective, controls_surjective) - ] + widgets.HBox( + [ + widgets.VBox( + [control_injective, control_surjective], + layout = { + 'display': 'flex', + 'align_items': 'stretch', + 'width': '100%', + 'overflow': 'hidden', + }, + ) + for control_injective, control_surjective in zip(controls_injective, controls_surjective) + ], + ) + ], ); display(ui); display(widgets.interactive_output( f = self.handler_plot, - controls = { 'N': control_nr, 'show_labels': show_labels } \ + controls = { 'N': control_nr, 'show_labels': button_show_labels } \ | { f'card_{k}': controls_card[k] for k in range(N+1) } \ | { f'injective_{k}': controls_injective[k] for k in range(N) } \ - | { f'surjective_{k}': controls_surjective[k] for k in range(N) } + | { f'surjective_{k}': controls_surjective[k] for k in range(N) } \ + | { 'refresh': button_refresh } )); return;