kbasic 0.1.0__tar.gz → 0.1.2__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.
@@ -1,7 +1,11 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kbasic
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Add your description here
5
+ Requires-Dist: numpy>=2.4.2
6
+ Requires-Dist: pylatexenc>=2.10
7
+ Requires-Dist: scipy>=1.17.0
8
+ Requires-Dist: tqdm>=4.67.3
5
9
  Requires-Python: >=3.11
6
10
  Description-Content-Type: text/markdown
7
11
 
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "kbasic"
3
+ version = "0.1.2"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "numpy>=2.4.2",
9
+ "pylatexenc>=2.10",
10
+ "scipy>=1.17.0",
11
+ "tqdm>=4.67.3",
12
+ ]
13
+
14
+ [build-system]
15
+ requires = ["uv_build>=0.10.2,<0.11.0"]
16
+ build-backend = "uv_build"
17
+
18
+ [dependency-groups]
19
+ dev = [
20
+ "pytest>=9.0.2",
21
+ "pytest-cov>=7.0.0",
22
+ ]
23
+
24
+ [tool.pytest.ini_options]
25
+ addopts = "-v --maxfail=1 --cov=kbasic"
@@ -0,0 +1,23 @@
1
+ import warnings
2
+ # Syntax warnings show up everytime you \something so we ignore all of em, hope this doesn't fuck anything up!
3
+ warnings.filterwarnings(action='ignore', category=SyntaxWarning)
4
+ from pylatexenc.latex2text import LatexNodes2Text
5
+ l2t = LatexNodes2Text().latex_to_text
6
+
7
+ class Tex(str):
8
+ def __init__(self, x):
9
+ x = x.strip(' $')
10
+ if x[-1]=='\n': x = x[:-1]
11
+ if x[-2:]=='.0': x = x[:-2]
12
+ # replace wonky \ letter commands with raw versions
13
+ # WARNING: DOES NOT WORK WITH \U OR \X BECAUSE THESE ARE UNICODE THINGS AND IDK HOW TO OVERRIDE THAT BEHAVIOR
14
+ x = x.replace("\a", r"\a").replace("\b", r"\b").replace("\f", r"\f").replace("\n", r"\n").replace("\r", r"\r").replace("\t", r"\t").replace("\v", r"\v")
15
+ # make compatible with fstring
16
+ x = x.replace("[", "{").replace("]", "}")
17
+ # get rid of extraneous .0's
18
+ x = x.replace(".0 ", " ").replace(".0}", "}").replace(".0$", "$").replace(".0\n", "\n")
19
+ self.string = fr"{l2t(x)}"
20
+ self.wrap = "$"+self.string+"$"
21
+
22
+ def __repr__(self): return self.string.strip("$")
23
+ def __str__(self): return self.string
@@ -0,0 +1,6 @@
1
+ from kbasic.Tex import Tex
2
+ from kbasic.array import bin_this, nan_clip, where_between, where_closest
3
+ from kbasic.bar import progress_bar, verbose_bar
4
+ from kbasic.user_input import yesno
5
+ from kbasic.vectors import *
6
+ from kbasic.audio import *
@@ -0,0 +1,25 @@
1
+ import numpy as np
2
+ from scipy.interpolate import RegularGridInterpolator
3
+
4
+ def where_closest(arr:np.ndarray, x): return np.argmin(np.abs(arr-x))
5
+ def where_between(arr:np.ndarray, low, high): return np.where((arr>=low)&(arr<=high))
6
+
7
+ def bin_this(x, y, n_bins=50, func=np.nanmean):
8
+ xbins = np.linspace(np.nanmin(x),np.nanmax(x),n_bins)
9
+ Y,error = list(),list()
10
+ for i,low_edge in enumerate(xbins[:-1]):
11
+ high_edge = xbins[i+1]
12
+ mask = (low_edge<x)&(x<high_edge)
13
+ yin = y[mask]
14
+ Y.append(func(yin))
15
+ error.append(np.nanstd(yin)/np.sqrt(len(yin)))
16
+ return np.array(xbins)[:-1],np.array(Y),np.array(error)
17
+
18
+ def nan_clip(*args):
19
+ mask = ~np.any([np.isnan(a) for a in args], axis=0)
20
+ nanless_args = tuple([np.array(a)[mask] for a in args])
21
+ return nanless_args
22
+
23
+ def interpolate2d(data, factor, method='cubic'):
24
+ Nx, Ny = np.array(data).shape
25
+ return RegularGridInterpolator((np.arange(Nx), np.arange(Ny)), data, method=method)(np.mgrid[:Nx-1:1/factor, :Ny-1:1/factor].T).T
@@ -0,0 +1 @@
1
+ from kbasic.audio.sound import *
@@ -0,0 +1,8 @@
1
+ from os import system
2
+ audioDir = '/Users/keyan/code/packages/keyutils/audio/lib/'
3
+ rightplace = '"Caroline Rose - year of the slug - 01 everything in its right place.wav"'
4
+ success = 'success.mp3'
5
+
6
+ def play(file: str):
7
+ system(f"afplay {audioDir}{file}")
8
+ def success(): system(f"afplay {audioDir}success.mp3")
@@ -0,0 +1,43 @@
1
+ from contextlib import contextmanager
2
+ import inspect
3
+ from tqdm import tqdm
4
+
5
+ def bar(x, total, width: int = 20, border="|", block="▉"):
6
+ full = int((x/total) * width // 1)
7
+ empty = width - full
8
+ bar = border + full*block + empty*" " + border
9
+ return bar
10
+
11
+ @contextmanager
12
+ def redirect_to_tqdm():
13
+ # Store builtin print
14
+ old_print = print
15
+ def new_print(*args, **kwargs):
16
+ # If tqdm.tqdm.write raises error, use builtin print
17
+ try:
18
+ tqdm.write(*args, **kwargs)
19
+ except:
20
+ old_print(*args, ** kwargs)
21
+
22
+ try:
23
+ # Globaly replace print with new_print
24
+ inspect.builtins.print = new_print
25
+ yield
26
+ finally:
27
+ inspect.builtins.print = old_print
28
+
29
+ def progress_bar(iterator, **kwargs):
30
+ with redirect_to_tqdm():
31
+ for x in tqdm(iterator, **kwargs):
32
+ yield x
33
+
34
+ def verbose_bar(iterator, verbose, **kwargs):
35
+ return progress_bar(iterator, **kwargs) if verbose else iterator
36
+
37
+ class ProgressBar(tqdm):
38
+ def __init__(self, *args, **kwargs):
39
+ super().__init__(self, *args, **kwargs)
40
+ self.iter = self.initial
41
+ def update(self, iter: int):
42
+ super().update(n=iter-self.iter)
43
+ self.iter = iter
@@ -0,0 +1,133 @@
1
+ from subprocess import check_output, DEVNULL
2
+ import numpy as np
3
+ from kbasic.bar import ProgressBar, redirect_to_tqdm
4
+ from kbasic.audio import success
5
+ from pysim.dhybridr.io import dHybridRinput
6
+ from tqdm import tqdm
7
+ from time import sleep
8
+
9
+ import asyncio
10
+
11
+ _USERNAME_ = 'x-kgootkin'
12
+
13
+ bad = ['\x1b[31m', '\x1b[34m', '\x1b[m']
14
+
15
+ def parse_shell_output(output):
16
+ match output:
17
+ case str(): return output
18
+ case [x]: return x
19
+ case [x, *_]:
20
+ for i in range(len(output)):
21
+ for b in bad:
22
+ if b in output[i]:
23
+ output[i] = output[i].strip(b)
24
+ return output
25
+
26
+ def system(cmd: str):
27
+ print(cmd)
28
+ command = cmd.split(' ') if type(cmd)==str else cmd
29
+ print(command)
30
+ for i in range(len(command)-1):
31
+ if command[i].startswith('"'):
32
+ print(command[i])
33
+ start_quote = i
34
+ end_quote = i+1
35
+ while not command[end_quote].endswith('"'): end_quote+=1
36
+ command[i] = " ".join(command[start_quote:end_quote+1])
37
+ for j in range(start_quote+1, end_quote+1): del command[j]
38
+ output = parse_shell_output(check_output(command, stderr=DEVNULL).decode().splitlines())
39
+ return output
40
+
41
+ def anvil(cmd: str):
42
+ output = parse_shell_output(check_output(['ssh', 'x-kgootkin@anvil.rcac.purdue.edu', *cmd.split(' ')], stderr=DEVNULL).decode().splitlines())
43
+ return output
44
+
45
+ async def anvil_async(cmd: str): asyncio.to_thread(anvil, cmd)
46
+
47
+
48
+ def anvil_queue(username=_USERNAME_): return anvil(f"squeue -u {username}")
49
+ qs = anvil_queue
50
+
51
+ class AnvilJob:
52
+ def __init__(self, queue_row: str, sep="DISTINCTSEPERATOR"):
53
+ self.sep = sep
54
+ [
55
+ jobid, username, account, name, nodes, cpus, time_limit, status, time
56
+ ] = queue_row.split()
57
+ self.jobid = int(jobid)
58
+ self.username = str(username)
59
+ self.account = str(account)
60
+ self.name = str(name)
61
+ self.nodes = int(nodes)
62
+ self.cpus = int(cpus)
63
+ self.time_limit = str(time_limit)
64
+ self.status = str(status)
65
+ self.time = str(time)
66
+
67
+ def __repr__(self): return "-"*30 + f"\n{self.name}: {self.status}\n\t{self.time}/{self.time_limit}"
68
+
69
+ def update(self, input=True, iter=True):
70
+ match input, iter:
71
+ case True, True:
72
+ x = anvil(f"cat /anvil/scratch/{self.username}/sims/{self.name}/input/input; echo {self.sep}; ls /anvil/scratch/{self.username}/sims/{self.name}/Output/Fields/Magnetic/Total/x/")
73
+ input_lines = "\n".join(x).split(self.sep)[0].split('\n')
74
+ self.input = dHybridRinput(input_lines)
75
+ self.iter = int(x[-1][5:-3])
76
+ case True, False:
77
+ self.input = dHybridRinput(anvil(f"cat /anvil/scratch/{self.username}/sims/{self.name}/input/input"))
78
+ case False, True:
79
+ self.iter = int(anvil(f"ls /anvil/scratch/{self.username}/sims/{self.name}/Output/Fields/Magnetic/Total/x/")[-1][5:-3])
80
+
81
+ def get_anvil_jobs(username=_USERNAME_):
82
+ q = anvil(f"squeue -u {username}")
83
+ if type(q)==str: return []
84
+ return [AnvilJob(x) for x in q[1:]]
85
+
86
+ async def get_anvil_jobs_async(username=_USERNAME_):
87
+ q = await asyncio.to_thread(anvil, f"squeue -u {username}")
88
+ if type(q)==str: return []
89
+ return [AnvilJob(x) for x in q[1:]]
90
+
91
+ async def get_anvil_sim_iter(name: str):
92
+ iter = int(await asyncio.to_thread(anvil, f"ls /anvil/scratch/{username}/sims/{name}/Output/Fields/Magnetic/Total/x/")[-1][5:-3])
93
+ return iter
94
+
95
+ class AnvilQueue:
96
+ def __init__(self, username=_USERNAME_):
97
+ self.username = username
98
+ @property
99
+ def jobs(self): return get_anvil_jobs()
100
+ def monitor(self, sep="DISTINCTSEPERATOR"):
101
+ with redirect_to_tqdm():
102
+ js = self.jobs
103
+ print(js, self.jobs)
104
+ pbars=[tqdm(position=i, leave=True, desc=js[i].name, postfix=f"{js[i].time}/{js[i].time_limit}") for i in range(len(js))]
105
+ for i in range(len(js)):
106
+ j = js[i]
107
+ j.update()
108
+ pbars[i].total = j.input.niter
109
+ pbars[i].n = j.iter
110
+ pbars[i].refresh()
111
+ while len(js:=self.jobs) > 0:
112
+ x = anvil(f";echo {sep};".join([f"ls /anvil/scratch/{self.username}/sims/{js[i].name}/Output/Fields/Magnetic/Total/x/" for i in range(len(js))]))
113
+ sep_ind = 0
114
+ for i in range(len(js)):
115
+ if i==range(len(js))[-1]:
116
+ sep_ind=0
117
+ else:
118
+ while x[sep_ind]!=sep: sep_ind+=1
119
+ it = int(x[sep_ind-1][5:-3])
120
+ pbars[i].n = it
121
+ pbars[i].postfix = f"{js[i].time}/{js[i].time_limit}"
122
+ pbars[i].refresh()
123
+ sep_ind+=1
124
+ def sound_when_running(self, i=0):
125
+ while len(js:=self.jobs) > 0:
126
+ j = js[i]
127
+ if j.status=='R': return success()
128
+ def sound_when_queue_clear(self):
129
+ while len(js:=self.jobs)>0: sleep(20)
130
+ return success()
131
+ def sound_monitor(self):
132
+ self.sound_when_running()
133
+ self.sound_when_queue_clear()
@@ -0,0 +1,13 @@
1
+ from numpy import ndarray, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float16, float32, float64, longdouble, complex64, complex128, clongdouble
2
+
3
+ class Number:
4
+ types: list = [
5
+ int, int8, uint8, int16, uint16, int32, uint32, int64, uint64,
6
+ float, float16, float32, float64, longdouble,
7
+ complex, complex64, complex128, clongdouble
8
+ ]
9
+
10
+ class Iterable:
11
+ types: list = [
12
+ list, set, dict, tuple, ndarray
13
+ ]
@@ -0,0 +1,23 @@
1
+ def yesno(prompt: str):
2
+ """
3
+ prompt the user to either reply yes or no
4
+ :param prompt: the yes/no question to be answered
5
+ :return: True if yes False if no
6
+ """
7
+ response = input(prompt).lower()
8
+ if 'y' in response and not 'n' in response:
9
+ return True
10
+ elif 'n' in response and not 'y' in response:
11
+ return False
12
+ else:
13
+ def retry_yesno():
14
+ retry_prompt = "Sorry I couldn't read that please respond with yes or no\n" + prompt
15
+ retry_response = input(retry_prompt).lower()
16
+ if 'y' in retry_response and not 'n' in retry_response:
17
+ return True
18
+ elif 'n' in retry_response and not 'y' in retry_response:
19
+ return False
20
+ else:
21
+ raise ValueError("need a response with either y or n in it.")
22
+
23
+ return retry_yesno()
@@ -0,0 +1,146 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ from numpy import array, ndarray, cos, arccos, sin, arcsin, sqrt, float64, float32, exp, pi, complex128
5
+ from typing import Self
6
+ from collections.abc import Generator
7
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
8
+ # >-|===|> Definitions <|===|-<
9
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
10
+ class Number:
11
+ types: list = [float, float64, float32, int, complex, complex128]
12
+ class Iterable:
13
+ types: list = [list, ndarray, tuple, Generator]
14
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
+ # >-|===|> Functions <|===|-<
16
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
17
+ def hamilton_product(q1, q2):
18
+ a1, b1, c1, d1 = q1
19
+ a2, b2, c2, d2 = q2
20
+ return array([
21
+ a1*a2 - b1*b2 - c1*c2 - d1*d2,
22
+ a1*b2 + b1*a2 + c1*d2 - d1*c2,
23
+ a1*c2 - b1*d2 + c1*a2 + d1*b2,
24
+ a1*d2 + b1*c2 - c1*b2 + d1*a2
25
+ ], dtype=float)
26
+ def organize_components(components) -> tuple[float|int]:
27
+ match components:
28
+ case (x, *_) if type(x) in Number.types: return components
29
+ case (x,) if type(x) in Iterable.types: return tuple(x)
30
+ case (Generator(),): return tuple(components[0])
31
+ case (Vector(),): return components[0].components
32
+ case _: raise TypeError(f"{components} cannot be matched")
33
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
34
+ # >-|===|> Classes <|===|-<
35
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
36
+ class Vector:
37
+ def __init__(self, *components) -> None:
38
+ self.components = organize_components(components)
39
+ self.ndims = self.dim = self.dimensions = len(components) #i always forget which one i choose :)
40
+ def __repr__(self) -> str:
41
+ return str(self.components)
42
+ def __len__(self) -> int:
43
+ return self.dimensions
44
+ def __abs__(self) -> Number:
45
+ return sqrt(sum(array(self.components)**2))
46
+ def __iter__(self) -> Self:
47
+ self._index = 0
48
+ return self
49
+ def __next__(self) -> Number:
50
+ try:
51
+ value = self.components[self._index]
52
+ self._index += 1
53
+ return value
54
+ except IndexError: raise StopIteration
55
+ def __add__(self, other) -> Self:
56
+ match other:
57
+ case Vector(): return type(self)(xs+xo for xs, xo in zip(self.components, other.components))
58
+ case x if type(x) in Number.types: return type(self)(x+other for x in self.components)
59
+ case x if type(x) in Iterable.types: return type(self)(xs+xo for xs, xo in zip(self.components, other))
60
+ def __sub__(self, other) -> Self:
61
+ match other:
62
+ case x if type(x) in Number.types: return type(self)(x-other for x in self.components)
63
+ case x if type(x) in Iterable.types: return type(self)(xs-xo for xs, xo in zip(self.components, other))
64
+ case Vector(): return type(self)(xs-xo for xs, xo in zip(self.components, other.components))
65
+ def __mul__(self, other) -> Self|Number:
66
+ match other:
67
+ case Vector(): return sum([c1*c2 for c1,c2 in zip(self.components, other.components)])
68
+ case x if type(x) in Number.types: return type(self)(c*other for c in self.components)
69
+ def __truediv__(self, other) -> Self:
70
+ match other:
71
+ case x if type(x) in Number.types: return type(self)(c/other for c in self.components)
72
+ def __floordiv__(self, other) -> Self:
73
+ match other:
74
+ case x if type(x) in Number.types: return type(self)(c//other for c in self.components)
75
+ def __radd__(self, other) -> Self:
76
+ match other:
77
+ case 0: return self
78
+ case _: return self.__add__(other)
79
+ def __rsub__(self, other) -> Self:
80
+ match other:
81
+ case 0: return self
82
+ case _: return self.__sub__(other)
83
+ def __rmul__(self, other) -> Self|Number:
84
+ match other:
85
+ case 0: return self
86
+ case _: return self.__mul__(other)
87
+ def __rtruediv__(self, other) -> Self:
88
+ match other:
89
+ case 0: return self
90
+ case _: return self.__truediv__(other)
91
+ def __rfloordiv__(self, other) -> Self:
92
+ match other:
93
+ case 0: return self
94
+ case _: return self.__floordiv__(other)
95
+
96
+ class Norm(Vector):
97
+ def __init__(self, *components):
98
+ components: tuple = organize_components(components)
99
+ Vector.__init__(self, *components)
100
+ gen = (x/abs(self) for x in self.components)
101
+ Vector.__init__(self, gen)
102
+ class Quaternion(Norm):
103
+ def __init__(self, angle, axis):
104
+ q0 = cos(angle/2)
105
+ self.axis = sin(angle/2)*Vector(axis)
106
+ self.axis *= sqrt(1-q0**2)/abs(self.axis)
107
+ Norm.__init__(self, q0, *self.axis.components)
108
+
109
+ class R2(Vector):
110
+ def __init__(self, *components):
111
+ self.components: tuple[Number] = organize_components(components)
112
+ self.x, self.y = self.components
113
+ Vector.__init__(self, *components)
114
+ self.magnitude = abs(self)
115
+ self.direction = Norm(*self.components) if self.magnitude>0 else None
116
+ def rotate(self, angle):
117
+ r = self.x + 1j*self.y
118
+ r_rotated = r * exp(1j*angle)
119
+ self.__init__(r_rotated.real, r_rotated.imag)
120
+ class R3(Vector):
121
+ def __init__(self, *components):
122
+ self.components: tuple[Number] = organize_components(components)
123
+ self.x, self.y, self.z = self.components
124
+ Vector.__init__(self, *self.components)
125
+ self.magnitude = abs(self)
126
+ self.direction = Norm(*self.components) if self.magnitude>0 else None
127
+ def from_spherical(radius: Number, azimuth: Number, latitude: Number) -> Self:
128
+ x = radius * cos(azimuth) * sin(latitude)
129
+ y = radius * sin(azimuth) * sin(latitude)
130
+ z = radius * cos(latitude)
131
+ return R3(x, y, z)
132
+ def rotate(self, angle, axis):
133
+ axis = array(axis)
134
+ qs = array([0, *self.components])
135
+ rotation = array(Norm([cos(angle/2), *(axis*sin(angle/2))]).components)
136
+ rotation_inverse = array([rotation[0], *-rotation[1:]])
137
+ rotated_vector = hamilton_product(hamilton_product(rotation, qs), rotation_inverse)[1:]
138
+ self.__init__(*rotated_vector)
139
+
140
+ class Matrix:
141
+ def __init__(self, array):
142
+ self.array = array
143
+ def __mul__(self, other):
144
+ match other:
145
+ case Vector():
146
+ res = [[self.array[i,j]*other.components[i] for i in range(len(other))] for j in range(len(other))]
@@ -1,11 +0,0 @@
1
- [project]
2
- name = "kbasic"
3
- version = "0.1.0"
4
- description = "Add your description here"
5
- readme = "README.md"
6
- requires-python = ">=3.11"
7
- dependencies = []
8
-
9
- [build-system]
10
- requires = ["uv_build>=0.10.2,<0.11.0"]
11
- build-backend = "uv_build"
@@ -1,2 +0,0 @@
1
- def hello() -> str:
2
- return "Hello from kbasic!"
File without changes
File without changes