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.
Files changed (27) hide show
  1. {kbasic-0.1.38 → kbasic-0.1.40}/PKG-INFO +1 -1
  2. {kbasic-0.1.38 → kbasic-0.1.40}/pyproject.toml +2 -2
  3. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/Tex.py +15 -14
  4. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/array.py +8 -6
  5. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/bar.py +23 -31
  6. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/basic.py +75 -29
  7. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/shell.py +24 -25
  8. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/strings.py +1 -0
  9. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/typing.py +2 -2
  10. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/user_input.py +28 -12
  11. kbasic-0.1.40/src/kbasic/vectors.py +214 -0
  12. kbasic-0.1.38/src/kbasic/vectors.py +0 -145
  13. {kbasic-0.1.38 → kbasic-0.1.40}/README.md +0 -0
  14. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/__init__.py +0 -0
  15. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/audio/__init__.py +0 -0
  16. {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
  17. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/audio/lib/success.mp3 +0 -0
  18. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/audio/sound.py +0 -0
  19. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/environment/Keyan.py +0 -0
  20. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/environment/__init__.py +0 -0
  21. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/environment/anvil.py +0 -0
  22. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/environment/defaultPC.py +0 -0
  23. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/__init__.py +0 -0
  24. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/log.py +0 -0
  25. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/parser.py +0 -0
  26. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/toml.py +0 -0
  27. {kbasic-0.1.38 → kbasic-0.1.40}/src/kbasic/parsing/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kbasic
3
- Version: 0.1.38
3
+ Version: 0.1.40
4
4
  Summary: Keyan's basic utility functions.
5
5
  Author: Keyan Gootkin
6
6
  Author-email: Keyan Gootkin <keyangootkin@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kbasic"
3
- version = "0.1.38"
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
- # >-|===|> Imports <|===|-<
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, hope this doesn't fuck anything up!
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
- # >-|===|> Definitions <|===|-<
12
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
11
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
12
+ # >-|===|> Definitions <|===|-<
13
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
13
14
  l2t = LatexNodes2Text().latex_to_text
14
15
 
15
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
- # >-|===|> Functions <|===|-<
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
- # >-|===|> Classes <|===|-<
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
- ) -> tuple[NDArray, NDArray, NDArray]:
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
- ) -> tuple[NDArray]:
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
- # >-|===|> Imports <|===|-<
3
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
- from kbasic.typing import Number
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
- # >-|===|> Types <|===|-<
13
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
14
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
- # >-|===|> Definitions <|===|-<
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
- bar = border + (full-1)*green(block) + yellow(block) + empty*black(block, "faint") + border
39
- return bar
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
- for x in tqdm(iterator, **kwargs):
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
- # >-|===|> Decorators <|===|-<
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
- return None
40
- self.path: str = str(builtinPath(path).resolve())
41
- self.master = master if not isinstance(master, str) else File(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: return self.path
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
- def copy(self, destination:str):
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 = File(destination, master=self.master)
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 = File(self.path, master=self.master)
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 file.writable:
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 = str()
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 Folder(self.children[i]) if isdir(self.children[i]) else File(self.children[i])
185
+ return self.children[i]
148
186
  raise StopIteration
149
187
  def __add__(self, other):
150
188
  match other:
151
189
  case str():
152
- p = f"{self.path}/{other}"
153
- return Path(p)
154
- case File(): return self.children+[other]
155
- case Folder(): return self.children + other.children
156
- case list(): return self.children + other
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
- # >-|===|> Imports <|===|-<
3
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
- from kbasic.bar import redirect_to_tqdm
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
- # >-|===|> Types <|===|-<
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
- # >-|===|> Functions <|===|-<
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
- # >-|===|> Decorators <|===|-<
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
- iter = int(await asyncio.to_thread(anvil, f"ls /anvil/scratch/{username}/sims/{name}/Output/Fields/Magnetic/Total/x/")[-1][5:-3])
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_):
@@ -1,3 +1,4 @@
1
+ # pylint: skip-file
1
2
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
3
  # >-|===|> Imports <|===|-<
3
4
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -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
- # >-|===|> Imports <|===|-<
3
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
- from kbasic.typing import Number
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
- # >-|===|> Functions <|===|-<
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: i=0
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, default_answer: str = "") -> None:
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