kbasic 0.1.39__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.39 → kbasic-0.1.40}/PKG-INFO +1 -1
- {kbasic-0.1.39 → kbasic-0.1.40}/pyproject.toml +2 -2
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/Tex.py +15 -14
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/array.py +8 -6
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/bar.py +23 -31
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/parsing/basic.py +61 -33
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/shell.py +24 -25
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/strings.py +1 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/typing.py +2 -2
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/user_input.py +28 -12
- kbasic-0.1.40/src/kbasic/vectors.py +214 -0
- kbasic-0.1.39/src/kbasic/vectors.py +0 -145
- {kbasic-0.1.39 → kbasic-0.1.40}/README.md +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/__init__.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/audio/__init__.py +0 -0
- {kbasic-0.1.39 → 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.39 → kbasic-0.1.40}/src/kbasic/audio/lib/success.mp3 +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/audio/sound.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/environment/Keyan.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/environment/__init__.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/environment/anvil.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/environment/defaultPC.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/parsing/__init__.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/parsing/log.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/parsing/parser.py +0 -0
- {kbasic-0.1.39 → kbasic-0.1.40}/src/kbasic/parsing/toml.py +0 -0
- {kbasic-0.1.39 → 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,16 @@
|
|
|
2
2
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
3
3
|
# >-|===|> Imports <|===|-<
|
|
4
4
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
5
|
-
from os import remove
|
|
6
|
-
from os.path import split, splitext, splitroot, exists, isdir, isfile, abspath
|
|
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
|
|
13
15
|
from kbasic.typing import Array
|
|
14
16
|
|
|
15
17
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
@@ -34,24 +36,27 @@ class File:
|
|
|
34
36
|
verbose (bool, optional): should this file be annoying. Defaults to
|
|
35
37
|
False.
|
|
36
38
|
"""
|
|
39
|
+
self.loaded: bool = False
|
|
37
40
|
if path is None: return None
|
|
38
41
|
if type(path)==type(self):
|
|
39
|
-
self = path
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
self.master =
|
|
42
|
+
self.path = path.path
|
|
43
|
+
else:
|
|
44
|
+
self.path: str = abspath(expandvars(expanduser(path)))
|
|
45
|
+
self.master = File(master)
|
|
43
46
|
self.verbose = verbose
|
|
44
|
-
self.loaded: bool = False
|
|
45
47
|
parentpath, self.name = split(self.path)
|
|
46
48
|
self.title, self.extension = splitext(self.name)
|
|
47
49
|
self.drive, self.root, _ = splitroot(self.path)
|
|
48
50
|
self.parent = Folder(parentpath)
|
|
49
51
|
self.lines = []
|
|
50
|
-
def __repr__(self) -> str:
|
|
52
|
+
def __repr__(self) -> str:
|
|
51
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():
|
|
@@ -74,32 +79,35 @@ class File:
|
|
|
74
79
|
"""
|
|
75
80
|
return exists(self.path)
|
|
76
81
|
@property
|
|
77
|
-
def
|
|
82
|
+
def writable(self) -> bool:
|
|
78
83
|
"""whether or not we can write this type of file with this object"""
|
|
79
|
-
return self.extension in unreadable_file_types
|
|
80
|
-
def copy(self, destination:str):
|
|
84
|
+
return self.extension not in unreadable_file_types
|
|
85
|
+
def copy(self, destination: Optional[str] = None):
|
|
81
86
|
"""_summary_
|
|
82
87
|
|
|
83
88
|
Args:
|
|
84
89
|
destination (str): _description_
|
|
85
90
|
"""
|
|
91
|
+
if destination is None: destination = str(self.parent / f"{self.title}-copy{self.extension}")
|
|
86
92
|
copy(self.path, destination)
|
|
93
|
+
return File(destination, verbose=self.verbose, master=self)
|
|
87
94
|
def move(self, destination:str):
|
|
88
95
|
"""_summary_
|
|
89
96
|
|
|
90
97
|
Args:
|
|
91
98
|
destination (str): _description_
|
|
92
99
|
"""
|
|
100
|
+
if self.verbose: print(cyan(f'moving {self.path} to {destination}...'))
|
|
93
101
|
move(self.path, destination)
|
|
94
|
-
self
|
|
95
|
-
def update(self) -> None:
|
|
102
|
+
self.__init__(destination, master=self.master)
|
|
103
|
+
def update(self) -> None: # think of a better name for this function
|
|
96
104
|
"""_summary_
|
|
97
105
|
"""
|
|
98
|
-
if self.verbose: print(f'updating {self.name}...')
|
|
106
|
+
if self.verbose: print(cyan(f'updating {self.name}...'))
|
|
99
107
|
assert self.master is not None, "No master copy to update from."
|
|
100
108
|
if self.exists: self.delete(interactive=False)
|
|
101
109
|
self.master.copy(self.path)
|
|
102
|
-
self
|
|
110
|
+
self.__init__(self.path, master=self.master)
|
|
103
111
|
def delete(self, interactive=True) -> None:
|
|
104
112
|
"""summary"""
|
|
105
113
|
if interactive and not yesno(
|
|
@@ -112,6 +120,7 @@ class File:
|
|
|
112
120
|
if not self.exists or self.extension in unreadable_file_types: return []
|
|
113
121
|
with open(self.path, 'r') as file:
|
|
114
122
|
self.lines = [f.strip('\n') for f in file.readlines()]
|
|
123
|
+
if self.lines==['']: self.lines = []
|
|
115
124
|
self.loaded = True
|
|
116
125
|
def load(self) -> None:
|
|
117
126
|
"""docstring"""
|
|
@@ -123,21 +132,28 @@ class File:
|
|
|
123
132
|
):
|
|
124
133
|
return None
|
|
125
134
|
with open(self.path, 'w+') as file:
|
|
126
|
-
if not
|
|
135
|
+
if not self.writable:
|
|
127
136
|
raise PermissionError(
|
|
128
137
|
f"attempted to save unwritable file: {self.path}"
|
|
129
138
|
)
|
|
130
|
-
file.writelines("\n".join(self.lines))
|
|
139
|
+
if len(self.lines) > 0: file.writelines("\n".join(self.lines))
|
|
140
|
+
return None
|
|
131
141
|
def write(self, text: str | Array, interactive=False) -> None:
|
|
132
142
|
"""add text to this File.lines then save the file"""
|
|
133
143
|
match text:
|
|
134
|
-
case str():
|
|
144
|
+
case str():
|
|
135
145
|
self.lines.append(text)
|
|
136
|
-
case _ if type(text) in Array.types:
|
|
146
|
+
case _ if type(text) in Array.types:
|
|
137
147
|
self.lines += list(text)
|
|
138
|
-
case _:
|
|
139
|
-
raise TypeError(f"
|
|
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
|
+
""")
|
|
140
153
|
self.save(interactive=interactive)
|
|
154
|
+
def touch(self) -> None:
|
|
155
|
+
"""summary"""
|
|
156
|
+
system(f"touch {self.path}")
|
|
141
157
|
|
|
142
158
|
class Folder:
|
|
143
159
|
def __init__(
|
|
@@ -147,10 +163,17 @@ class Folder:
|
|
|
147
163
|
if path is None: return None
|
|
148
164
|
if type(path)==type(self):
|
|
149
165
|
path = path.path
|
|
150
|
-
self.path = abspath(
|
|
166
|
+
self.path = abspath(expandvars(expanduser(path)))
|
|
151
167
|
self.master = master if not isinstance(master, str) else Folder(master)
|
|
152
168
|
self.parentpath, self.name = split(self.path)
|
|
153
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)
|
|
154
177
|
def __len__(self) -> int: return len(self.children)
|
|
155
178
|
def __iter__(self):
|
|
156
179
|
self.index = 0
|
|
@@ -159,16 +182,18 @@ class Folder:
|
|
|
159
182
|
if self.index < len(self):
|
|
160
183
|
i = self.index
|
|
161
184
|
self.index += 1
|
|
162
|
-
return
|
|
185
|
+
return self.children[i]
|
|
163
186
|
raise StopIteration
|
|
164
187
|
def __add__(self, other):
|
|
165
188
|
match other:
|
|
166
189
|
case str():
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
case Folder():
|
|
171
|
-
|
|
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
|
|
172
197
|
case _:
|
|
173
198
|
raise NotImplementedError(
|
|
174
199
|
f"can't add object of type: {type(other)} to a Folder object: {repr(self)}"
|
|
@@ -176,32 +201,35 @@ class Folder:
|
|
|
176
201
|
def __radd__(self, other):
|
|
177
202
|
if other==0: return [self]
|
|
178
203
|
return self.__add__(other)
|
|
179
|
-
def
|
|
204
|
+
def __truediv__(self, other):
|
|
180
205
|
match other:
|
|
181
206
|
case str(): return Path(f"{self.path}/{other}")
|
|
182
207
|
@property
|
|
183
208
|
def parent(self) -> Self:
|
|
184
209
|
"""summary"""
|
|
185
|
-
return Folder(self.parentpath)
|
|
210
|
+
return Folder(self.parentpath) if len(self.parentpath) > 3 else None
|
|
186
211
|
@property
|
|
187
212
|
def exists(self) -> bool:
|
|
188
213
|
"""summary"""
|
|
189
214
|
return exists(self.path)
|
|
190
215
|
def glob(self, pattern: str):
|
|
191
216
|
"""summary"""
|
|
192
|
-
return glob(self.path+pattern)
|
|
217
|
+
return glob(self.path+'/'+pattern)
|
|
193
218
|
@property
|
|
194
219
|
def children(self) -> list:
|
|
195
220
|
"""summary"""
|
|
196
|
-
return self.glob("
|
|
221
|
+
return [Path(p) for p in self.glob("*")]
|
|
197
222
|
def ls(self) -> None:
|
|
198
223
|
"""summary"""
|
|
199
|
-
print("\n".join(self.children))
|
|
224
|
+
print("\n".join([p.path for p in self.children]))
|
|
200
225
|
def make(self) -> None:
|
|
201
226
|
"""summary"""
|
|
202
227
|
ensure_path(self.path)
|
|
203
228
|
def copy(self, destination:str) -> None:
|
|
204
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)
|
|
205
233
|
copytree(self.path, destination)
|
|
206
234
|
def revert(self) -> None:
|
|
207
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
|