kbasic 0.1.38__tar.gz → 0.1.40__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kbasic-0.1.38 → kbasic-0.1.40}/PKG-INFO +1 -1
- {kbasic-0.1.38 → kbasic-0.1.40}/pyproject.toml +2 -2
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/Tex.py +15 -14
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/array.py +8 -6
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/bar.py +23 -31
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/basic.py +75 -29
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/shell.py +24 -25
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/strings.py +1 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/typing.py +2 -2
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/user_input.py +28 -12
- kbasic-0.1.40/src/kbasic/vectors.py +214 -0
- kbasic-0.1.38/src/kbasic/vectors.py +0 -145
- {kbasic-0.1.38 → kbasic-0.1.40}/README.md +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/__init__.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/audio/__init__.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/audio/lib/Caroline Rose - year of the slug - 01 everything in its right place.wav +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/audio/lib/success.mp3 +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/audio/sound.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/environment/Keyan.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/environment/__init__.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/environment/anvil.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/environment/defaultPC.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/__init__.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/log.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/parser.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/toml.py +0 -0
- {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "kbasic"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.40"
|
|
4
4
|
description = "Keyan's basic utility functions."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -26,4 +26,4 @@ dev = [
|
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[tool.pytest.ini_options]
|
|
29
|
-
addopts = "-v --maxfail=1 --cov=kbasic"
|
|
29
|
+
addopts = "-v --maxfail=1 --cov=kbasic --import-mode=importlib"
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
#
|
|
2
|
-
# >-|===|>
|
|
3
|
-
#
|
|
1
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
2
|
+
# >-|===|> Imports <|===|-<
|
|
3
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
4
4
|
from fractions import Fraction
|
|
5
|
-
import warnings
|
|
6
|
-
# Syntax warnings show up everytime you \something so we ignore all of em,
|
|
5
|
+
import warnings
|
|
6
|
+
# Syntax warnings show up everytime you \something so we ignore all of em,
|
|
7
|
+
# hope this doesn't fuck anything up!
|
|
7
8
|
warnings.filterwarnings(action='ignore', category=SyntaxWarning)
|
|
8
9
|
from pylatexenc.latex2text import LatexNodes2Text
|
|
9
10
|
|
|
10
|
-
#
|
|
11
|
-
# >-|===|>
|
|
12
|
-
#
|
|
11
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
12
|
+
# >-|===|> Definitions <|===|-<
|
|
13
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
13
14
|
l2t = LatexNodes2Text().latex_to_text
|
|
14
15
|
|
|
15
|
-
#
|
|
16
|
-
# >-|===|>
|
|
17
|
-
#
|
|
16
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
17
|
+
# >-|===|> Functions <|===|-<
|
|
18
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
18
19
|
def texfraction(num) -> str:
|
|
19
20
|
f = Fraction(str(num))
|
|
20
21
|
return r"$\frac{" + str(f.numerator) + "}{" + str(f.denominator) + r"}$"
|
|
21
22
|
|
|
22
|
-
#
|
|
23
|
-
# >-|===|>
|
|
24
|
-
#
|
|
23
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
24
|
+
# >-|===|> Classes <|===|-<
|
|
25
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
25
26
|
class Tex(str):
|
|
26
27
|
def __init__(self, x: str) -> None:
|
|
27
28
|
x = x.strip(' $')
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
"""pure array operations"""
|
|
1
2
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
2
3
|
# >-|===|> Imports <|===|-<
|
|
3
4
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
4
|
-
from kbasic.typing import Number, ArrayLike, NDArray
|
|
5
5
|
from typing import Callable
|
|
6
|
+
from kbasic.typing import Number, ArrayLike, NDArray
|
|
6
7
|
from numpy import argmin, any, all, absolute, hypot, logspace, log10, where, \
|
|
7
8
|
nanmean, nanmin, nanmax, sqrt, linspace, nanstd, array, isnan, \
|
|
8
9
|
isfinite, arange, mgrid, r_, c_, zeros, delete, unravel_index
|
|
@@ -97,7 +98,7 @@ def interpolate2d(
|
|
|
97
98
|
data: ArrayLike, factor: int,
|
|
98
99
|
method: str = 'linear',
|
|
99
100
|
periodic: bool = True
|
|
100
|
-
) -> NDArray:
|
|
101
|
+
) -> NDArray:
|
|
101
102
|
"""interpolate 2d data on a regular grid by an even factor
|
|
102
103
|
|
|
103
104
|
Args:
|
|
@@ -125,7 +126,7 @@ def kspec1d(
|
|
|
125
126
|
image: ArrayLike,
|
|
126
127
|
bins: int = 100,
|
|
127
128
|
return_bin_edges: bool = False
|
|
128
|
-
|
|
129
|
+
) -> tuple[NDArray, NDArray, NDArray]:
|
|
129
130
|
"""Take a 2d image and turn it into a 1d fft
|
|
130
131
|
|
|
131
132
|
Args:
|
|
@@ -151,9 +152,10 @@ def kspec1d(
|
|
|
151
152
|
if not return_bin_edges: return kx, ks, kerr
|
|
152
153
|
return kgrid, kx, ks, kerr
|
|
153
154
|
def kspec3d(
|
|
154
|
-
cube: ArrayLike,
|
|
155
|
+
cube: ArrayLike,
|
|
155
156
|
parallel_axis: int = 0
|
|
156
|
-
|
|
157
|
+
) -> tuple[NDArray]:
|
|
158
|
+
"""docstring"""
|
|
157
159
|
# Set up parallel axis
|
|
158
160
|
n_par = cube.shape[parallel_axis]
|
|
159
161
|
parallel = arange(-(n_par//2), n_par//2+1)
|
|
@@ -179,4 +181,4 @@ def kspec3d(
|
|
|
179
181
|
) for i in range(n_perp-1)
|
|
180
182
|
] for j in tqdm(range(n_par), position=1, total=n_par)
|
|
181
183
|
])
|
|
182
|
-
return parallel[n_par//2:], perp, k.T[:, n_par//2:]
|
|
184
|
+
return parallel[n_par//2:], perp, k.T[:, n_par//2:]
|
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
from kbasic.strings import green, yellow, black
|
|
1
|
+
"""progress bar code"""
|
|
2
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
3
|
+
# >-|===|> Imports <|===|-<
|
|
4
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
6
5
|
from collections.abc import Iterable
|
|
7
6
|
from contextlib import contextmanager
|
|
8
7
|
import inspect
|
|
8
|
+
from kbasic.typing import Number
|
|
9
|
+
from kbasic.strings import green, yellow, black
|
|
9
10
|
from tqdm import tqdm
|
|
10
11
|
|
|
11
|
-
#
|
|
12
|
-
# >-|===|>
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# >-|===|> Functions <|===|-<
|
|
19
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
20
|
-
def bar(
|
|
21
|
-
x: Number, total: Number,
|
|
22
|
-
width: int = 20, border: str = "|", block: str = "▉", color: str = 'white'
|
|
23
|
-
) -> str:
|
|
12
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
13
|
+
# >-|===|> Functions <|===|-<
|
|
14
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
15
|
+
def bar_string(
|
|
16
|
+
x: Number, total: Number,
|
|
17
|
+
width: int = 20, border: str = "|", block: str = "▉", color: bool = True
|
|
18
|
+
) -> str:
|
|
24
19
|
"""create a string representing a progress bar set at 100 * x / total % full.
|
|
25
20
|
|
|
26
21
|
Args:
|
|
@@ -35,8 +30,9 @@ def bar(
|
|
|
35
30
|
"""
|
|
36
31
|
full = int((x/total) * width // 1)
|
|
37
32
|
empty = width - full
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
if color: output = (full-1)*green(block) + yellow(block) + empty*black(block, "faint")
|
|
34
|
+
else: output = (full-1)*block + empty*" "
|
|
35
|
+
return border + output + border
|
|
40
36
|
@contextmanager
|
|
41
37
|
def redirect_to_tqdm():
|
|
42
38
|
"""maybe make print statements show up below the bar without fucking everything up?
|
|
@@ -48,7 +44,7 @@ def redirect_to_tqdm():
|
|
|
48
44
|
# If tqdm.tqdm.write raises error, use builtin print
|
|
49
45
|
try:
|
|
50
46
|
tqdm.write(*args, **kwargs)
|
|
51
|
-
except:
|
|
47
|
+
except Exception:
|
|
52
48
|
old_print(*args, ** kwargs)
|
|
53
49
|
|
|
54
50
|
try:
|
|
@@ -61,23 +57,19 @@ def progress_bar(iterator: Iterable, **kwargs):
|
|
|
61
57
|
"""tqdm with print redirected to tqdm.write
|
|
62
58
|
"""
|
|
63
59
|
with redirect_to_tqdm():
|
|
64
|
-
|
|
65
|
-
yield x
|
|
60
|
+
yield from tqdm(iterator, **kwargs)
|
|
66
61
|
def verbose_bar(iterator: Iterable, verbose: bool, **kwargs):
|
|
67
62
|
"""just a progress bar if verbose is true.
|
|
68
63
|
"""
|
|
69
64
|
return progress_bar(iterator, **kwargs) if verbose else iterator
|
|
70
65
|
|
|
71
|
-
#
|
|
72
|
-
# >-|===|>
|
|
73
|
-
#
|
|
74
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
75
|
-
# >-|===|> Classes <|===|-<
|
|
76
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
66
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
67
|
+
# >-|===|> Classes <|===|-<
|
|
68
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
77
69
|
class ProgressBar(tqdm):
|
|
78
70
|
def __init__(self, *args, **kwargs):
|
|
79
71
|
super().__init__(self, *args, **kwargs)
|
|
80
72
|
self.iter = self.initial
|
|
81
|
-
def update(self, iter: int):
|
|
73
|
+
def update(self, iter: int):
|
|
82
74
|
super().update(n=iter-self.iter)
|
|
83
|
-
self.iter = iter
|
|
75
|
+
self.iter = iter
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
3
3
|
# >-|===|> Imports <|===|-<
|
|
4
4
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
5
|
-
from os import remove
|
|
6
|
-
from os.path import split, splitext, splitroot, exists, isdir, isfile
|
|
5
|
+
from os import remove, system, walk
|
|
6
|
+
from os.path import split, splitext, splitroot, exists, isdir, isfile, abspath, \
|
|
7
|
+
expanduser, expandvars, basename
|
|
7
8
|
from shutil import copy, move, copytree, rmtree
|
|
8
9
|
from typing import Self, Optional
|
|
9
10
|
from glob import glob
|
|
10
11
|
from pathlib import Path as builtinPath
|
|
11
12
|
from kbasic.parsing.utils import ensure_path
|
|
12
13
|
from kbasic.user_input import yesno
|
|
14
|
+
from kbasic.strings import cyan
|
|
15
|
+
from kbasic.typing import Array
|
|
13
16
|
|
|
14
17
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
15
18
|
# >-|===|> Definitions <|===|-<
|
|
@@ -33,25 +36,27 @@ class File:
|
|
|
33
36
|
verbose (bool, optional): should this file be annoying. Defaults to
|
|
34
37
|
False.
|
|
35
38
|
"""
|
|
39
|
+
self.loaded: bool = False
|
|
36
40
|
if path is None: return None
|
|
37
41
|
if type(path)==type(self):
|
|
38
|
-
self = path
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
self.master =
|
|
42
|
+
self.path = path.path
|
|
43
|
+
else:
|
|
44
|
+
self.path: str = abspath(expandvars(expanduser(path)))
|
|
45
|
+
self.master = File(master)
|
|
42
46
|
self.verbose = verbose
|
|
43
|
-
self.loaded: bool = False
|
|
44
47
|
parentpath, self.name = split(self.path)
|
|
45
48
|
self.title, self.extension = splitext(self.name)
|
|
46
49
|
self.drive, self.root, _ = splitroot(self.path)
|
|
47
50
|
self.parent = Folder(parentpath)
|
|
48
|
-
self.grandparent = self.parent.parent
|
|
49
|
-
self.greatgrandparent = self.parent.parent.parent
|
|
50
51
|
self.lines = []
|
|
51
|
-
def __repr__(self) -> str:
|
|
52
|
+
def __repr__(self) -> str:
|
|
53
|
+
return self.path
|
|
52
54
|
def __str__(self) -> str:
|
|
53
55
|
if not self.loaded: self.read()
|
|
54
56
|
return "\n".join(self.lines)
|
|
57
|
+
def __eq__(self, other) -> bool:
|
|
58
|
+
if type(other) != File: return False
|
|
59
|
+
return (self.path == other.path) & (self.lines == other.lines)
|
|
55
60
|
def __add__(self, other):
|
|
56
61
|
match other:
|
|
57
62
|
case list():
|
|
@@ -73,29 +78,36 @@ class File:
|
|
|
73
78
|
bool: _description_
|
|
74
79
|
"""
|
|
75
80
|
return exists(self.path)
|
|
76
|
-
|
|
81
|
+
@property
|
|
82
|
+
def writable(self) -> bool:
|
|
83
|
+
"""whether or not we can write this type of file with this object"""
|
|
84
|
+
return self.extension not in unreadable_file_types
|
|
85
|
+
def copy(self, destination: Optional[str] = None):
|
|
77
86
|
"""_summary_
|
|
78
87
|
|
|
79
88
|
Args:
|
|
80
89
|
destination (str): _description_
|
|
81
90
|
"""
|
|
91
|
+
if destination is None: destination = str(self.parent / f"{self.title}-copy{self.extension}")
|
|
82
92
|
copy(self.path, destination)
|
|
93
|
+
return File(destination, verbose=self.verbose, master=self)
|
|
83
94
|
def move(self, destination:str):
|
|
84
95
|
"""_summary_
|
|
85
96
|
|
|
86
97
|
Args:
|
|
87
98
|
destination (str): _description_
|
|
88
99
|
"""
|
|
100
|
+
if self.verbose: print(cyan(f'moving {self.path} to {destination}...'))
|
|
89
101
|
move(self.path, destination)
|
|
90
|
-
self
|
|
91
|
-
def update(self) -> None:
|
|
102
|
+
self.__init__(destination, master=self.master)
|
|
103
|
+
def update(self) -> None: # think of a better name for this function
|
|
92
104
|
"""_summary_
|
|
93
105
|
"""
|
|
94
|
-
if self.verbose: print(f'updating {self.name}...')
|
|
106
|
+
if self.verbose: print(cyan(f'updating {self.name}...'))
|
|
95
107
|
assert self.master is not None, "No master copy to update from."
|
|
96
108
|
if self.exists: self.delete(interactive=False)
|
|
97
109
|
self.master.copy(self.path)
|
|
98
|
-
self
|
|
110
|
+
self.__init__(self.path, master=self.master)
|
|
99
111
|
def delete(self, interactive=True) -> None:
|
|
100
112
|
"""summary"""
|
|
101
113
|
if interactive and not yesno(
|
|
@@ -108,34 +120,60 @@ class File:
|
|
|
108
120
|
if not self.exists or self.extension in unreadable_file_types: return []
|
|
109
121
|
with open(self.path, 'r') as file:
|
|
110
122
|
self.lines = [f.strip('\n') for f in file.readlines()]
|
|
123
|
+
if self.lines==['']: self.lines = []
|
|
111
124
|
self.loaded = True
|
|
112
125
|
def load(self) -> None:
|
|
113
126
|
"""docstring"""
|
|
114
127
|
self.read()
|
|
115
|
-
def save(self, interactive=True):
|
|
128
|
+
def save(self, interactive=True) -> None:
|
|
116
129
|
"""summary"""
|
|
117
130
|
if interactive and not yesno(
|
|
118
131
|
f"Are you sure you want to permanently overwrite {self.path}?\n"
|
|
119
132
|
):
|
|
120
133
|
return None
|
|
121
134
|
with open(self.path, 'w+') as file:
|
|
122
|
-
if not
|
|
135
|
+
if not self.writable:
|
|
123
136
|
raise PermissionError(
|
|
124
137
|
f"attempted to save unwritable file: {self.path}"
|
|
125
138
|
)
|
|
126
|
-
file.writelines("\n".join(self.lines))
|
|
139
|
+
if len(self.lines) > 0: file.writelines("\n".join(self.lines))
|
|
140
|
+
return None
|
|
141
|
+
def write(self, text: str | Array, interactive=False) -> None:
|
|
142
|
+
"""add text to this File.lines then save the file"""
|
|
143
|
+
match text:
|
|
144
|
+
case str():
|
|
145
|
+
self.lines.append(text)
|
|
146
|
+
case _ if type(text) in Array.types:
|
|
147
|
+
self.lines += list(text)
|
|
148
|
+
case _:
|
|
149
|
+
raise TypeError(f"""
|
|
150
|
+
must supply either string or an array of strings
|
|
151
|
+
was given text of type: {type(text)}
|
|
152
|
+
""")
|
|
153
|
+
self.save(interactive=interactive)
|
|
154
|
+
def touch(self) -> None:
|
|
155
|
+
"""summary"""
|
|
156
|
+
system(f"touch {self.path}")
|
|
127
157
|
|
|
128
158
|
class Folder:
|
|
129
159
|
def __init__(
|
|
130
160
|
self, path: str|Self,
|
|
131
161
|
master: Optional[Self] = None
|
|
132
162
|
) -> None:
|
|
163
|
+
if path is None: return None
|
|
133
164
|
if type(path)==type(self):
|
|
134
165
|
path = path.path
|
|
135
|
-
self.path =
|
|
166
|
+
self.path = abspath(expandvars(expanduser(path)))
|
|
136
167
|
self.master = master if not isinstance(master, str) else Folder(master)
|
|
137
168
|
self.parentpath, self.name = split(self.path)
|
|
138
169
|
def __repr__(self) -> str: return self.path
|
|
170
|
+
def __str__(self) -> str:
|
|
171
|
+
output = []
|
|
172
|
+
for root, dirs, files in walk(self.path):
|
|
173
|
+
level = root.replace(self.path, '').count('/')
|
|
174
|
+
output.append(f'{'----'*level}{basename(root)}')
|
|
175
|
+
for f in files: output.append(f'|{'----'*(level+1)}> {f}')
|
|
176
|
+
return '\n'.join(output)
|
|
139
177
|
def __len__(self) -> int: return len(self.children)
|
|
140
178
|
def __iter__(self):
|
|
141
179
|
self.index = 0
|
|
@@ -144,16 +182,18 @@ class Folder:
|
|
|
144
182
|
if self.index < len(self):
|
|
145
183
|
i = self.index
|
|
146
184
|
self.index += 1
|
|
147
|
-
return
|
|
185
|
+
return self.children[i]
|
|
148
186
|
raise StopIteration
|
|
149
187
|
def __add__(self, other):
|
|
150
188
|
match other:
|
|
151
189
|
case str():
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
case Folder():
|
|
156
|
-
|
|
190
|
+
return Path(f"{self.path}/{other}")
|
|
191
|
+
case File():
|
|
192
|
+
return self.children+[other]
|
|
193
|
+
case Folder():
|
|
194
|
+
return self.children + other.children
|
|
195
|
+
case list():
|
|
196
|
+
return self.children + other
|
|
157
197
|
case _:
|
|
158
198
|
raise NotImplementedError(
|
|
159
199
|
f"can't add object of type: {type(other)} to a Folder object: {repr(self)}"
|
|
@@ -161,29 +201,35 @@ class Folder:
|
|
|
161
201
|
def __radd__(self, other):
|
|
162
202
|
if other==0: return [self]
|
|
163
203
|
return self.__add__(other)
|
|
204
|
+
def __truediv__(self, other):
|
|
205
|
+
match other:
|
|
206
|
+
case str(): return Path(f"{self.path}/{other}")
|
|
164
207
|
@property
|
|
165
208
|
def parent(self) -> Self:
|
|
166
209
|
"""summary"""
|
|
167
|
-
return Folder(self.parentpath)
|
|
210
|
+
return Folder(self.parentpath) if len(self.parentpath) > 3 else None
|
|
168
211
|
@property
|
|
169
212
|
def exists(self) -> bool:
|
|
170
213
|
"""summary"""
|
|
171
214
|
return exists(self.path)
|
|
172
215
|
def glob(self, pattern: str):
|
|
173
216
|
"""summary"""
|
|
174
|
-
return glob(self.path+pattern)
|
|
217
|
+
return glob(self.path+'/'+pattern)
|
|
175
218
|
@property
|
|
176
219
|
def children(self) -> list:
|
|
177
220
|
"""summary"""
|
|
178
|
-
return self.glob("
|
|
221
|
+
return [Path(p) for p in self.glob("*")]
|
|
179
222
|
def ls(self) -> None:
|
|
180
223
|
"""summary"""
|
|
181
|
-
print("\n".join(self.children))
|
|
224
|
+
print("\n".join([p.path for p in self.children]))
|
|
182
225
|
def make(self) -> None:
|
|
183
226
|
"""summary"""
|
|
184
227
|
ensure_path(self.path)
|
|
185
228
|
def copy(self, destination:str) -> None:
|
|
186
229
|
"""summary"""
|
|
230
|
+
if destination is None: destination = str(self.parent / f"{self.name}-copy")
|
|
231
|
+
copy(self.path, destination)
|
|
232
|
+
return File(destination, verbose=self.verbose, master=self)
|
|
187
233
|
copytree(self.path, destination)
|
|
188
234
|
def revert(self) -> None:
|
|
189
235
|
"""summary"""
|
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
from kbasic.audio import success
|
|
6
|
-
from kbasic.environment import isAnvil
|
|
7
|
-
if isAnvil: from kbasic.environment.anvil import anvil_user
|
|
8
|
-
from typing import Any
|
|
1
|
+
"""utilities for interacting with shell"""
|
|
2
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
3
|
+
# >-|===|> Imports <|===|-<
|
|
4
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
9
5
|
from subprocess import check_output, DEVNULL
|
|
10
|
-
from tqdm import tqdm
|
|
11
6
|
from time import sleep
|
|
12
7
|
import asyncio
|
|
8
|
+
from tqdm import tqdm
|
|
9
|
+
from kbasic.bar import redirect_to_tqdm
|
|
10
|
+
from kbasic.audio import success
|
|
11
|
+
from kbasic.environment import isAnvil
|
|
12
|
+
if isAnvil: from kbasic.environment.anvil import anvil_user
|
|
13
13
|
|
|
14
|
-
#
|
|
15
|
-
# >-|===|>
|
|
16
|
-
#
|
|
17
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
18
|
-
# >-|===|> Definitions <|===|-<
|
|
19
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
14
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
15
|
+
# >-|===|> Definitions <|===|-<
|
|
16
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
20
17
|
bad: list[str] = ['\x1b[31m', '\x1b[34m', '\x1b[m']
|
|
21
18
|
_USERNAME_: str = "x-kgootkin" if not isAnvil else anvil_user
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
#
|
|
19
|
+
|
|
20
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
21
|
+
# >-|===|> Functions <|===|-<
|
|
22
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
25
23
|
def parse_shell_output(output: str | list[str]) -> str | list[str]:
|
|
26
24
|
"""take the output of a shell command and make it nice
|
|
27
25
|
|
|
@@ -79,12 +77,9 @@ def anvil_queue(username=_USERNAME_):
|
|
|
79
77
|
return anvil(f"squeue -u {username}")
|
|
80
78
|
qs = anvil_queue
|
|
81
79
|
|
|
82
|
-
#
|
|
83
|
-
# >-|===|>
|
|
84
|
-
#
|
|
85
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
86
|
-
# >-|===|> Classes <|===|-<
|
|
87
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
80
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
81
|
+
# >-|===|> Classes <|===|-<
|
|
82
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
88
83
|
class AnvilJob:
|
|
89
84
|
def __init__(self, queue_row: str, sep="DISTINCTSEPERATOR") -> None:
|
|
90
85
|
self.sep = sep
|
|
@@ -125,7 +120,11 @@ async def get_anvil_jobs_async(username=_USERNAME_):
|
|
|
125
120
|
if type(q)==str: return []
|
|
126
121
|
return [AnvilJob(x) for x in q[1:]]
|
|
127
122
|
async def get_anvil_sim_iter(name: str, username=_USERNAME_):
|
|
128
|
-
|
|
123
|
+
"""docstring"""
|
|
124
|
+
iter = int(await asyncio.to_thread(
|
|
125
|
+
anvil,
|
|
126
|
+
f"ls /anvil/scratch/{username}/sims/{name}/Output/Fields/Magnetic/Total/x/"
|
|
127
|
+
)[-1][5:-3])
|
|
129
128
|
return iter
|
|
130
129
|
class AnvilQueue:
|
|
131
130
|
def __init__(self, username=_USERNAME_):
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
11
11
|
from numpy import int8, uint8, int16, uint16, int32, uint32, int64, uint64, \
|
|
12
12
|
float16, float32, float64, longdouble, complex64, complex128, \
|
|
13
|
-
clongdouble
|
|
13
|
+
clongdouble, ndarray
|
|
14
14
|
from numpy.typing import NDArray, ArrayLike
|
|
15
15
|
from collections.abc import Callable, Generator
|
|
16
16
|
from typing import Any
|
|
@@ -18,7 +18,7 @@ from typing import Any
|
|
|
18
18
|
# >-|===|> Types <|===|-<
|
|
19
19
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
20
20
|
class Array:
|
|
21
|
-
types: list = [NDArray, list, set, tuple, Generator]
|
|
21
|
+
types: list = [NDArray, ndarray, list, set, tuple, Generator]
|
|
22
22
|
class Number:
|
|
23
23
|
types: list = [
|
|
24
24
|
int, int8, uint8, int16, uint16, int32, uint32, int64, uint64,
|
|
@@ -1,19 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
|
|
1
|
+
"""handle user inputs"""
|
|
2
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
3
|
+
# >-|===|> Imports <|===|-<
|
|
4
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
5
5
|
from typing import Any
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from tempfile import TemporaryFile
|
|
8
|
+
import sys
|
|
9
|
+
from kbasic.typing import Number
|
|
6
10
|
|
|
7
|
-
#
|
|
8
|
-
# >-|===|>
|
|
9
|
-
#
|
|
11
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
12
|
+
# >-|===|> Functions <|===|-<
|
|
13
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
14
|
+
@contextmanager
|
|
15
|
+
def simulate_user_input(*user_input: tuple[str]):
|
|
16
|
+
"""change stdin to simulate the user entering a string"""
|
|
17
|
+
with TemporaryFile(mode='a+') as answers:
|
|
18
|
+
answers.write("\n".join(user_input))
|
|
19
|
+
answers.seek(0)
|
|
20
|
+
orig = sys.stdin
|
|
21
|
+
sys.stdin = answers
|
|
22
|
+
yield
|
|
23
|
+
sys.stdin = orig
|
|
10
24
|
def parse_user_input(response: str, sep: str = ',') -> Any:
|
|
25
|
+
"""turn user supplied strings into appropriate python objects"""
|
|
11
26
|
try: return Number(response)
|
|
12
|
-
except ValueError:
|
|
27
|
+
except ValueError: _ = None
|
|
13
28
|
match response:
|
|
14
|
-
case str(x) if ',' in x:
|
|
29
|
+
case str(x) if ',' in x:
|
|
15
30
|
return tuple(parse_user_input(xi.strip(), sep=sep) for xi in response.split(sep))
|
|
16
|
-
case _:
|
|
31
|
+
case _:
|
|
17
32
|
return response.lower().strip()
|
|
18
33
|
def yesno(prompt: str):
|
|
19
34
|
"""
|
|
@@ -38,6 +53,7 @@ def yesno(prompt: str):
|
|
|
38
53
|
raise ValueError("need a response with either y or n in it.")
|
|
39
54
|
|
|
40
55
|
return retry_yesno()
|
|
41
|
-
def interactive_set_attribute(obj: Any, attr: str
|
|
56
|
+
def interactive_set_attribute(obj: Any, attr: str) -> None:
|
|
57
|
+
"""prompt the user to set an objects attribute"""
|
|
42
58
|
res: Any = parse_user_input(input(f"Set a value for {repr(obj)}.{attr}:\n\t"))
|
|
43
|
-
setattr(obj, attr, res)
|
|
59
|
+
setattr(obj, attr, res)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Implement vector classes for vector analysis"""
|
|
2
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
3
|
+
# >-|===|> Imports <|===|-<
|
|
4
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
5
|
+
from typing import Self
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
from functools import total_ordering
|
|
8
|
+
from numpy import array, cos, sin, sqrt, exp, sum
|
|
9
|
+
from kbasic.typing import Number, Array
|
|
10
|
+
|
|
11
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
12
|
+
# >-|===|> Functions <|===|-<
|
|
13
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
14
|
+
def hamilton_product(q1, q2):
|
|
15
|
+
"""the hamilton product between two quaternions"""
|
|
16
|
+
a1, b1, c1, d1 = q1
|
|
17
|
+
a2, b2, c2, d2 = q2
|
|
18
|
+
return array([
|
|
19
|
+
a1*a2 - b1*b2 - c1*c2 - d1*d2,
|
|
20
|
+
a1*b2 + b1*a2 + c1*d2 - d1*c2,
|
|
21
|
+
a1*c2 - b1*d2 + c1*a2 + d1*b2,
|
|
22
|
+
a1*d2 + b1*c2 - c1*b2 + d1*a2
|
|
23
|
+
], dtype=float)
|
|
24
|
+
|
|
25
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
26
|
+
# >-|===|> Classes <|===|-<
|
|
27
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
28
|
+
class VectorBase:
|
|
29
|
+
def __init__(self, *components) -> None:
|
|
30
|
+
match components[0]:
|
|
31
|
+
case x if type(x) in Number.types:
|
|
32
|
+
self.components = components
|
|
33
|
+
case x if type(x) in Array.types:
|
|
34
|
+
self.components = tuple(array(x) for x in components)
|
|
35
|
+
case Generator():
|
|
36
|
+
self.components = tuple(components[0])
|
|
37
|
+
case x: raise TypeError(f"given components of type: {type(x)}")
|
|
38
|
+
#i always forget which one i choose :)
|
|
39
|
+
self.ndims = self.dim = self.dimensions = len(components)
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return f"Vector{str(self.components)}"
|
|
42
|
+
def __len__(self) -> int:
|
|
43
|
+
return len(self.components[0]) if type(self.components[0]) in Array.types else 1
|
|
44
|
+
def __abs__(self) -> Number:
|
|
45
|
+
return sqrt(sum(array(self.components)**2, axis=0))
|
|
46
|
+
def __eq__(self, other) -> bool:
|
|
47
|
+
match other:
|
|
48
|
+
case VectorBase():
|
|
49
|
+
if len(self) > 1 or len(other) > 1:
|
|
50
|
+
return all(all(s==o) for s,o in zip(self, other))
|
|
51
|
+
return all(s==o for s, o in zip(self, other))
|
|
52
|
+
case x if type(x) in Array.types:
|
|
53
|
+
return all(all(s==o) for s, o in zip(self, other))
|
|
54
|
+
case _:
|
|
55
|
+
return False
|
|
56
|
+
def __getitem__(self, item):
|
|
57
|
+
match item:
|
|
58
|
+
case int():
|
|
59
|
+
return Vector(c[item] for c in self.components)
|
|
60
|
+
case (int(), *_):
|
|
61
|
+
return [Vector(c[i] for c in self.components) for i in item]
|
|
62
|
+
case slice():
|
|
63
|
+
start = item.start if item.start else 0
|
|
64
|
+
stop = item.stop if item.stop else len(self)
|
|
65
|
+
step = item.step if item.step else 1
|
|
66
|
+
return [Vector(c[i] for c in self.components) for i in range(start, stop, step)]
|
|
67
|
+
def __iter__(self) -> Self:
|
|
68
|
+
self._index = 0
|
|
69
|
+
return self
|
|
70
|
+
def __next__(self) -> Number:
|
|
71
|
+
try:
|
|
72
|
+
value = self.components[self._index]
|
|
73
|
+
self._index += 1
|
|
74
|
+
return value
|
|
75
|
+
except IndexError as err: raise StopIteration from err
|
|
76
|
+
def __add__(self, other) -> Self:
|
|
77
|
+
match other:
|
|
78
|
+
case VectorBase():
|
|
79
|
+
return Vector(xs+xo for xs, xo in zip(self.components, other.components))
|
|
80
|
+
case x if type(x) in Number.types:
|
|
81
|
+
return Vector(xi+other for xi in self)
|
|
82
|
+
case x if type(x) in Array.types:
|
|
83
|
+
return Vector(xs+xo for xs, xo in zip(self.components, other))
|
|
84
|
+
def __sub__(self, other) -> Self:
|
|
85
|
+
match other:
|
|
86
|
+
case x if type(x) in Number.types:
|
|
87
|
+
return type(self)(x-other for x in self)
|
|
88
|
+
case x if type(x) in Array.types:
|
|
89
|
+
return type(self)(xs-xo for xs, xo in zip(self.components, other))
|
|
90
|
+
case VectorBase():
|
|
91
|
+
return type(self)(xs-xo for xs, xo in zip(self.components, other.components))
|
|
92
|
+
def __mul__(self, other) -> Self|Number:
|
|
93
|
+
match other:
|
|
94
|
+
case VectorBase():
|
|
95
|
+
return sum(list(c1*c2 for c1,c2 in zip(self, other)), axis=0)
|
|
96
|
+
case x if type(x) in Number.types:
|
|
97
|
+
return type(self)(c*other for c in self)
|
|
98
|
+
case x if type(x) in Array.types:
|
|
99
|
+
return type(self)(c1*c2 for c1, c2 in zip(self, other))
|
|
100
|
+
def __truediv__(self, other) -> Self:
|
|
101
|
+
match other:
|
|
102
|
+
case VectorBase():
|
|
103
|
+
return Vector(c1 / c2 for c1, c2 in zip(self, other))
|
|
104
|
+
case x if type(x) in Number.types:
|
|
105
|
+
return type(self)(c / other for c in self)
|
|
106
|
+
case x if type(x) in Array.types:
|
|
107
|
+
return type(self)(c1 / c2 for c1, c2 in zip(self, other))
|
|
108
|
+
def __floordiv__(self, other) -> Self:
|
|
109
|
+
match other:
|
|
110
|
+
case x if type(x) in Number.types:
|
|
111
|
+
return type(self)(c//other for c in self)
|
|
112
|
+
case x if type(x) in Array.types:
|
|
113
|
+
return type(self)(c1//c2 for c1, c2 in zip(self, other))
|
|
114
|
+
def __radd__(self, other) -> Self:
|
|
115
|
+
match other:
|
|
116
|
+
case 0: return self
|
|
117
|
+
case _: return self.__add__(other)
|
|
118
|
+
def __rsub__(self, other) -> Self:
|
|
119
|
+
match other:
|
|
120
|
+
case 0: return self
|
|
121
|
+
case _: return self.__sub__(other)
|
|
122
|
+
def __rmul__(self, other) -> Self|Number:
|
|
123
|
+
match other:
|
|
124
|
+
case 0: return self
|
|
125
|
+
case _: return self.__mul__(other)
|
|
126
|
+
def __rtruediv__(self, other) -> Self:
|
|
127
|
+
match other:
|
|
128
|
+
case x if type(x) in Number.types:
|
|
129
|
+
return type(self)(other/c for c in self)
|
|
130
|
+
case x if type(x) in Array.types:
|
|
131
|
+
return type(self)(c2/c1 for c1, c2 in zip(self, other))
|
|
132
|
+
def __rfloordiv__(self, other) -> Self:
|
|
133
|
+
match other:
|
|
134
|
+
case x if type(x) in Number.types:
|
|
135
|
+
return type(self)(other//c for c in self)
|
|
136
|
+
case x if type(x) in Array.types:
|
|
137
|
+
return type(self)(c2//c1 for c1, c2 in zip(self, other))
|
|
138
|
+
class Vector(VectorBase):
|
|
139
|
+
def __new__(cls, *components) -> Self: # the vector factory
|
|
140
|
+
match components:
|
|
141
|
+
# vector
|
|
142
|
+
case (VectorBase(),):
|
|
143
|
+
return components[0]
|
|
144
|
+
# generator
|
|
145
|
+
case (Generator(),):
|
|
146
|
+
return Vector(tuple(components[0]))
|
|
147
|
+
# single array
|
|
148
|
+
case (x,) if type(x) in Array.types:
|
|
149
|
+
return Vector(*x)
|
|
150
|
+
# 2D
|
|
151
|
+
case (x, y):
|
|
152
|
+
return R2(x, y)
|
|
153
|
+
# 3D
|
|
154
|
+
case (x, y, z):
|
|
155
|
+
return R3(x, y, z)
|
|
156
|
+
# several numbers
|
|
157
|
+
case x if all(type(xi) in Number.types for xi in x):
|
|
158
|
+
return VectorBase(*components)
|
|
159
|
+
# several arrays
|
|
160
|
+
case x if all(type(xi) in Array.types for xi in x):
|
|
161
|
+
return VectorBase(*components)
|
|
162
|
+
case x:
|
|
163
|
+
raise TypeError(f"given components:\n{components}\nof type: {type(x)}")
|
|
164
|
+
class Norm(VectorBase):
|
|
165
|
+
def __init__(self, *components):
|
|
166
|
+
self.components = components
|
|
167
|
+
gen = (x/abs(self) for x in self)
|
|
168
|
+
super().__init__(gen)
|
|
169
|
+
class Quaternion(Norm):
|
|
170
|
+
def __init__(self, angle, axis):
|
|
171
|
+
q0 = cos(angle/2)
|
|
172
|
+
self.axis = Vector(*axis)
|
|
173
|
+
self.axis *= sin(angle/2)
|
|
174
|
+
self.axis *= sqrt(1-q0**2)/abs(self.axis)
|
|
175
|
+
super().__init__(q0, *self.axis.components)
|
|
176
|
+
class R2(VectorBase):
|
|
177
|
+
def __init__(self, *components):
|
|
178
|
+
super().__init__(*components)
|
|
179
|
+
self.x, self.y = self.components
|
|
180
|
+
self.magnitude = abs(self)
|
|
181
|
+
# self.direction = Norm(*self.components) if self.magnitude>0 else None
|
|
182
|
+
def rotate(self, angle) -> Self:
|
|
183
|
+
"""docstring"""
|
|
184
|
+
r = self.x + 1j*self.y
|
|
185
|
+
r_rotated = r * exp(1j*angle)
|
|
186
|
+
self.__init__(r_rotated.real, r_rotated.imag)
|
|
187
|
+
return self
|
|
188
|
+
class R3(VectorBase):
|
|
189
|
+
def __init__(self, *components):
|
|
190
|
+
super().__init__(*components)
|
|
191
|
+
self.x, self.y, self.z = self.components
|
|
192
|
+
self.magnitude = abs(self)
|
|
193
|
+
# self.direction = Norm(*self.components) if self.magnitude>0 else None
|
|
194
|
+
def from_spherical(radius: Number, azimuth: Number, latitude: Number) -> Self:
|
|
195
|
+
"""docstring"""
|
|
196
|
+
x = radius * cos(azimuth) * sin(latitude)
|
|
197
|
+
y = radius * sin(azimuth) * sin(latitude)
|
|
198
|
+
z = radius * cos(latitude)
|
|
199
|
+
return R3(x, y, z)
|
|
200
|
+
def rotate(self, angle, axis):
|
|
201
|
+
"""docstring"""
|
|
202
|
+
axis = array(axis)
|
|
203
|
+
qs = array([0, *self.components])
|
|
204
|
+
rotation = array(Norm(cos(angle/2), *(axis*sin(angle/2))).components)
|
|
205
|
+
rotation_inverse = array([rotation[0], *-rotation[1:]])
|
|
206
|
+
rotated_vector = hamilton_product(hamilton_product(rotation, qs), rotation_inverse)[1:]
|
|
207
|
+
self = R3(*rotated_vector)
|
|
208
|
+
return self
|
|
209
|
+
def cross(self, other: Self) -> Self:
|
|
210
|
+
"""Compute self cross other"""
|
|
211
|
+
x = self.y*other.z - self.z*other.y
|
|
212
|
+
y = -(self.x*other.z - self.z*other.x)
|
|
213
|
+
z = self.x*other.y - self.y-other.z
|
|
214
|
+
return R3(x, y, z)
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
2
|
-
# >-|===|> Imports <|===|-<
|
|
3
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
4
|
-
from typing import Self
|
|
5
|
-
from collections.abc import Generator
|
|
6
|
-
from numpy import array, cos, sin, sqrt, exp
|
|
7
|
-
from kbasic.typing import Number, Array
|
|
8
|
-
|
|
9
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
10
|
-
# >-|===|> Definitions <|===|-<
|
|
11
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
12
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
13
|
-
# >-|===|> Functions <|===|-<
|
|
14
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
15
|
-
def hamilton_product(q1, q2):
|
|
16
|
-
a1, b1, c1, d1 = q1
|
|
17
|
-
a2, b2, c2, d2 = q2
|
|
18
|
-
return array([
|
|
19
|
-
a1*a2 - b1*b2 - c1*c2 - d1*d2,
|
|
20
|
-
a1*b2 + b1*a2 + c1*d2 - d1*c2,
|
|
21
|
-
a1*c2 - b1*d2 + c1*a2 + d1*b2,
|
|
22
|
-
a1*d2 + b1*c2 - c1*b2 + d1*a2
|
|
23
|
-
], dtype=float)
|
|
24
|
-
def organize_components(components) -> tuple[float|int]:
|
|
25
|
-
match components:
|
|
26
|
-
case (x, *_) if type(x) in Number.types: return components
|
|
27
|
-
case (x,) if type(x) in Array.types: return tuple(x)
|
|
28
|
-
case (Generator(),): return tuple(components[0])
|
|
29
|
-
case (Vector(),): return components[0].components
|
|
30
|
-
case _: raise TypeError(f"{components} cannot be matched")
|
|
31
|
-
|
|
32
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
33
|
-
# >-|===|> Classes <|===|-<
|
|
34
|
-
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
35
|
-
class Vector:
|
|
36
|
-
def __init__(self, *components) -> None:
|
|
37
|
-
self.components = organize_components(components)
|
|
38
|
-
self.ndims = self.dim = self.dimensions = len(components) #i always forget which one i choose :)
|
|
39
|
-
def __repr__(self) -> str:
|
|
40
|
-
return str(self.components)
|
|
41
|
-
def __len__(self) -> int:
|
|
42
|
-
return self.dimensions
|
|
43
|
-
def __abs__(self) -> Number:
|
|
44
|
-
return sqrt(sum(array(self.components)**2))
|
|
45
|
-
def __iter__(self) -> Self:
|
|
46
|
-
self._index = 0
|
|
47
|
-
return self
|
|
48
|
-
def __next__(self) -> Number:
|
|
49
|
-
try:
|
|
50
|
-
value = self.components[self._index]
|
|
51
|
-
self._index += 1
|
|
52
|
-
return value
|
|
53
|
-
except IndexError as err: raise StopIteration from err
|
|
54
|
-
def __add__(self, other) -> Self:
|
|
55
|
-
match other:
|
|
56
|
-
case Vector(): return type(self)(xs+xo for xs, xo in zip(self.components, other.components))
|
|
57
|
-
case x if type(x) in Number.types: return type(self)(x+other for x in self.components)
|
|
58
|
-
case x if type(x) in Array.types: return type(self)(xs+xo for xs, xo in zip(self.components, other))
|
|
59
|
-
def __sub__(self, other) -> Self:
|
|
60
|
-
match other:
|
|
61
|
-
case x if type(x) in Number.types: return type(self)(x-other for x in self.components)
|
|
62
|
-
case x if type(x) in Array.types: return type(self)(xs-xo for xs, xo in zip(self.components, other))
|
|
63
|
-
case Vector(): return type(self)(xs-xo for xs, xo in zip(self.components, other.components))
|
|
64
|
-
def __mul__(self, other) -> Self|Number:
|
|
65
|
-
match other:
|
|
66
|
-
case Vector(): return sum([c1*c2 for c1,c2 in zip(self.components, other.components)])
|
|
67
|
-
case x if type(x) in Number.types: return type(self)(c*other for c in self.components)
|
|
68
|
-
def __truediv__(self, other) -> Self:
|
|
69
|
-
match other:
|
|
70
|
-
case x if type(x) in Number.types: return type(self)(c/other for c in self.components)
|
|
71
|
-
def __floordiv__(self, other) -> Self:
|
|
72
|
-
match other:
|
|
73
|
-
case x if type(x) in Number.types: return type(self)(c//other for c in self.components)
|
|
74
|
-
def __radd__(self, other) -> Self:
|
|
75
|
-
match other:
|
|
76
|
-
case 0: return self
|
|
77
|
-
case _: return self.__add__(other)
|
|
78
|
-
def __rsub__(self, other) -> Self:
|
|
79
|
-
match other:
|
|
80
|
-
case 0: return self
|
|
81
|
-
case _: return self.__sub__(other)
|
|
82
|
-
def __rmul__(self, other) -> Self|Number:
|
|
83
|
-
match other:
|
|
84
|
-
case 0: return self
|
|
85
|
-
case _: return self.__mul__(other)
|
|
86
|
-
def __rtruediv__(self, other) -> Self:
|
|
87
|
-
match other:
|
|
88
|
-
case 0: return self
|
|
89
|
-
case _: return self.__truediv__(other)
|
|
90
|
-
def __rfloordiv__(self, other) -> Self:
|
|
91
|
-
match other:
|
|
92
|
-
case 0: return self
|
|
93
|
-
case _: return self.__floordiv__(other)
|
|
94
|
-
class Norm(Vector):
|
|
95
|
-
def __init__(self, *components):
|
|
96
|
-
components: tuple = organize_components(components)
|
|
97
|
-
Vector.__init__(self, *components)
|
|
98
|
-
gen = (x/abs(self) for x in self.components)
|
|
99
|
-
Vector.__init__(self, gen)
|
|
100
|
-
class Quaternion(Norm):
|
|
101
|
-
def __init__(self, angle, axis):
|
|
102
|
-
q0 = cos(angle/2)
|
|
103
|
-
self.axis = sin(angle/2)*Vector(axis)
|
|
104
|
-
self.axis *= sqrt(1-q0**2)/abs(self.axis)
|
|
105
|
-
Norm.__init__(self, q0, *self.axis.components)
|
|
106
|
-
class R2(Vector):
|
|
107
|
-
def __init__(self, *components):
|
|
108
|
-
self.components: tuple[Number] = organize_components(components)
|
|
109
|
-
self.x, self.y = self.components
|
|
110
|
-
Vector.__init__(self, *components)
|
|
111
|
-
self.magnitude = abs(self)
|
|
112
|
-
self.direction = Norm(*self.components) if self.magnitude>0 else None
|
|
113
|
-
def rotate(self, angle):
|
|
114
|
-
r = self.x + 1j*self.y
|
|
115
|
-
r_rotated = r * exp(1j*angle)
|
|
116
|
-
self = R2(r_rotated.real, r_rotated.imag)
|
|
117
|
-
class R3(Vector):
|
|
118
|
-
def __init__(self, *components):
|
|
119
|
-
self.components: tuple[Number] = organize_components(components)
|
|
120
|
-
self.x, self.y, self.z = self.components
|
|
121
|
-
Vector.__init__(self, *self.components)
|
|
122
|
-
self.magnitude = abs(self)
|
|
123
|
-
self.direction = Norm(*self.components) if self.magnitude>0 else None
|
|
124
|
-
def from_spherical(radius: Number, azimuth: Number, latitude: Number) -> Self:
|
|
125
|
-
x = radius * cos(azimuth) * sin(latitude)
|
|
126
|
-
y = radius * sin(azimuth) * sin(latitude)
|
|
127
|
-
z = radius * cos(latitude)
|
|
128
|
-
return R3(x, y, z)
|
|
129
|
-
def rotate(self, angle, axis):
|
|
130
|
-
axis = array(axis)
|
|
131
|
-
qs = array([0, *self.components])
|
|
132
|
-
rotation = array(Norm([cos(angle/2), *(axis*sin(angle/2))]).components)
|
|
133
|
-
rotation_inverse = array([rotation[0], *-rotation[1:]])
|
|
134
|
-
rotated_vector = hamilton_product(hamilton_product(rotation, qs), rotation_inverse)[1:]
|
|
135
|
-
self = R3(*rotated_vector)
|
|
136
|
-
class Matrix:
|
|
137
|
-
def __init__(self, arr):
|
|
138
|
-
self.array = arr
|
|
139
|
-
def __mul__(self, other):
|
|
140
|
-
match other:
|
|
141
|
-
case Vector():
|
|
142
|
-
res = [
|
|
143
|
-
[self.array[i,j]*other.components[i] for i in range(len(other))]
|
|
144
|
-
for j in range(len(other))]
|
|
145
|
-
return res
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|