kbasic 0.1.2__tar.gz → 0.1.4__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,7 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kbasic
3
- Version: 0.1.2
4
- Summary: Add your description here
3
+ Version: 0.1.4
4
+ Summary: Keyan's basic utility functions.
5
5
  Requires-Dist: numpy>=2.4.2
6
6
  Requires-Dist: pylatexenc>=2.10
7
7
  Requires-Dist: scipy>=1.17.0
@@ -9,3 +9,8 @@ Requires-Dist: tqdm>=4.67.3
9
9
  Requires-Python: >=3.11
10
10
  Description-Content-Type: text/markdown
11
11
 
12
+ # KBasic
13
+ The core utility functions that Keyan is sick of rewriting in every project :-)
14
+ ```
15
+ pip install kbasic
16
+ ```
kbasic-0.1.4/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # KBasic
2
+ The core utility functions that Keyan is sick of rewriting in every project :-)
3
+ ```
4
+ pip install kbasic
5
+ ```
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "kbasic"
3
- version = "0.1.2"
4
- description = "Add your description here"
3
+ version = "0.1.4"
4
+ description = "Keyan's basic utility functions."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
@@ -0,0 +1,46 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
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!
7
+ warnings.filterwarnings(action='ignore', category=SyntaxWarning)
8
+ from pylatexenc.latex2text import LatexNodes2Text
9
+
10
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
11
+ # >-|===|> Types <|===|-<
12
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
13
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
14
+ # >-|===|> Definitions <|===|-<
15
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
+ l2t = LatexNodes2Text().latex_to_text
17
+
18
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
19
+ # >-|===|> Functions <|===|-<
20
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
21
+ def texfraction(num) -> str:
22
+ f = Fraction(str(num))
23
+ return r"$\frac{" + str(f.numerator) + "}{" + str(f.denominator) + r"}$"
24
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
25
+ # >-|===|> Decorators <|===|-<
26
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
27
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
28
+ # >-|===|> Classes <|===|-<
29
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
30
+ class Tex(str):
31
+ def __init__(self, x):
32
+ x = x.strip(' $')
33
+ if x[-1]=='\n': x = x[:-1]
34
+ if x[-2:]=='.0': x = x[:-2]
35
+ # replace wonky \ letter commands with raw versions
36
+ # WARNING: DOES NOT WORK WITH \U OR \X BECAUSE THESE ARE UNICODE THINGS AND IDK HOW TO OVERRIDE THAT BEHAVIOR
37
+ 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")
38
+ # make compatible with fstring
39
+ x = x.replace("[", "{").replace("]", "}")
40
+ # get rid of extraneous .0's
41
+ x = x.replace(".0 ", " ").replace(".0}", "}").replace(".0$", "$").replace(".0\n", "\n")
42
+ self.string = fr"{l2t(x)}"
43
+ self.wrap = "$"+self.string+"$"
44
+
45
+ def __repr__(self): return self.string.strip("$")
46
+ def __str__(self): return self.string
@@ -0,0 +1,8 @@
1
+ from kbasic.audio import *
2
+ from kbasic.array import *
3
+ from kbasic.bar import *
4
+ from kbasic.shell import *
5
+ from kbasic.user_input import *
6
+ from kbasic.Tex import *
7
+ from kbasic.typing import *
8
+ from kbasic.vectors import *
@@ -0,0 +1,79 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ from typing import Callable
5
+ from numpy import ndarray, argmin, abs, where, nanmean, nanmin, nanmax, linspace, nanstd, array, isnan, arange, mgrid
6
+ from scipy.interpolate import RegularGridInterpolator
7
+
8
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
9
+ # >-|===|> Types <|===|-<
10
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
11
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
12
+ # >-|===|> Definitions <|===|-<
13
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
14
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
+ # >-|===|> Functions <|===|-<
16
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
17
+ def tile(arr: ndarray) -> ndarray:
18
+ """take an image and create a 3x3 grid of that image"""
19
+ return np.r_[np.c_[arr, arr, arr], np.c_[arr, arr, arr], np.c_[arr, arr, arr]]
20
+ def where_closest(arr:ndarray, x): return int(argmin(abs(arr-x)))
21
+ def where_between(arr:ndarray, low, high): return where((arr>=low)&(arr<=high))
22
+ def bin_this(
23
+ x, y,
24
+ n_bins: int = 50,
25
+ func: Callable = nanmean
26
+ ) -> tuple[ndarray]:
27
+ """a function to rebin x, y data and calculate errors.
28
+
29
+ Args:
30
+ x: the axis you want to bin
31
+ y: the data associated with each bin
32
+ n_bins (int, optional): desired number of bins. Defaults to 50.
33
+ func (Callable, optional): the function to call on the members of each bin. Defaults to numpy.nanmean.
34
+
35
+ Returns:
36
+ x_binned (ndarray): the bins.
37
+ y_binned (ndarray): func(members of each bin).
38
+ bin_errors (ndarray): standard error of the mean for each bin
39
+ """
40
+ xbins = linspace(nanmin(x),nanmax(x),n_bins)
41
+ Y,error = list(),list()
42
+ for i,low_edge in enumerate(xbins[:-1]):
43
+ high_edge = xbins[i+1]
44
+ mask = (low_edge<x)&(x<high_edge)
45
+ yin = y[mask]
46
+ Y.append(func(yin))
47
+ error.append(nanstd(yin)/sqrt(len(yin)))
48
+ x_binned, y_binned, bin_errors = array(xbins)[:-1],array(Y),array(error)
49
+ return x_binned, y_binned, bin_errors
50
+ def nan_clip(*args):
51
+ """
52
+ take a series of arrays and only return the indicies where ALL members are finite
53
+ """
54
+ mask = ~any([isnan(a) for a in args], axis=0)
55
+ nanless_args = tuple([array(a)[mask] for a in args])
56
+ return nanless_args
57
+ def interpolate2d(
58
+ data, factor: int,
59
+ method: str = 'linear'
60
+ ) -> ndarray:
61
+ """interpolate 2d data on a regular grid by an even factor
62
+
63
+ Args:
64
+ data (ndarray[ndarray]): 2d data on a regular grid
65
+ factor (int): how many new grid points per old grid points
66
+ method (str, optional): the interpolation method. Defaults to 'linear'.
67
+
68
+ Returns:
69
+ ndarray[ndarray]: an interpolated grid of data
70
+ """
71
+ Nx, Ny = array(data).shape
72
+ return RegularGridInterpolator((arange(Nx), arange(Ny)), data, method=method)(mgrid[:Nx-1:1/factor, :Ny-1:1/factor].T).T
73
+
74
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
75
+ # >-|===|> Decorators <|===|-<
76
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
77
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
78
+ # >-|===|> Classes <|===|-<
79
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -0,0 +1,28 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ from os import system
5
+
6
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
7
+ # >-|===|> Types <|===|-<
8
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
9
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
10
+ # >-|===|> Definitions <|===|-<
11
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
12
+ audioDir = '/Users/keyan/code/packages/keyutils/audio/lib/'
13
+ rightplace = '"Caroline Rose - year of the slug - 01 everything in its right place.wav"'
14
+ success = 'success.mp3'
15
+
16
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
17
+ # >-|===|> Functions <|===|-<
18
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
19
+ def play(file: str):
20
+ system(f"afplay {audioDir}{file}")
21
+ def success(): system(f"afplay {audioDir}success.mp3")
22
+
23
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
24
+ # >-|===|> Decorators <|===|-<
25
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
26
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
27
+ # >-|===|> Classes <|===|-<
28
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -0,0 +1,81 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ from kbasic.typing import Number
5
+ from contextlib import contextmanager
6
+ import inspect
7
+ from tqdm import tqdm
8
+
9
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
10
+ # >-|===|> Types <|===|-<
11
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
12
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
13
+ # >-|===|> Definitions <|===|-<
14
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
+ # >-|===|> Functions <|===|-<
17
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
18
+ def bar(
19
+ x: Number, total: Number,
20
+ width: int = 20, border="|", block="▉"
21
+ ) -> str:
22
+ """create a string representing a progress bar set at 100 * x / total % full.
23
+
24
+ Args:
25
+ x (Number): x out of total
26
+ total (_type_): x out of total
27
+ width (int, optional): how many characters wide the bar should be. Defaults to 20.
28
+ border (str, optional): the character to represent the border of the bar. Defaults to "|".
29
+ block (str, optional): the character representing a full part of the bar. Defaults to "▉".
30
+
31
+ Returns:
32
+ str: a progress bar
33
+ """
34
+ full = int((x/total) * width // 1)
35
+ empty = width - full
36
+ bar = border + full*block + empty*" " + border
37
+ return bar
38
+ @contextmanager
39
+ def redirect_to_tqdm():
40
+ """maybe make print statements show up below the bar without fucking everything up?
41
+ idk tbh im not sure how this works exactly I got it from stack exchange.
42
+ """
43
+ # Store builtin print
44
+ old_print = print
45
+ def new_print(*args, **kwargs):
46
+ # If tqdm.tqdm.write raises error, use builtin print
47
+ try:
48
+ tqdm.write(*args, **kwargs)
49
+ except:
50
+ old_print(*args, ** kwargs)
51
+
52
+ try:
53
+ # Globaly replace print with new_print
54
+ inspect.builtins.print = new_print
55
+ yield
56
+ finally:
57
+ inspect.builtins.print = old_print
58
+ def progress_bar(iterator, **kwargs):
59
+ """tqdm with print redirected to tqdm.write
60
+ """
61
+ with redirect_to_tqdm():
62
+ for x in tqdm(iterator, **kwargs):
63
+ yield x
64
+ def verbose_bar(iterator, verbose, **kwargs):
65
+ """just a progress bar if verbose is true.
66
+ """
67
+ return progress_bar(iterator, **kwargs) if verbose else iterator
68
+
69
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
70
+ # >-|===|> Decorators <|===|-<
71
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
72
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
73
+ # >-|===|> Classes <|===|-<
74
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
75
+ class ProgressBar(tqdm):
76
+ def __init__(self, *args, **kwargs):
77
+ super().__init__(self, *args, **kwargs)
78
+ self.iter = self.initial
79
+ def update(self, iter: int):
80
+ super().update(n=iter-self.iter)
81
+ self.iter = iter
@@ -0,0 +1,159 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ #pysim imports
5
+ from kbasic.user_input import yesno
6
+ #nonpysim imports
7
+ import numpy as np
8
+ from glob import glob
9
+ from shutil import copy, move, copytree, rmtree
10
+ from os.path import isdir, isfile, exists, abspath
11
+ from os import mkdir, remove
12
+ from functools import cached_property
13
+
14
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
+ # >-|===|> Definitions <|===|-<
16
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
17
+ unreadable_file_types = ['gz', 'tar', 'zip']
18
+
19
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
20
+ # >-|===|> Functions <|===|-<
21
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
22
+ def ensure_path(path: str) -> None:
23
+ """make sure that a path exists
24
+
25
+ Args:
26
+ path (str): the path you want to exist
27
+ """
28
+ parts = path.strip().split('/')
29
+ for i in range(len(parts)):
30
+ if "/".join(parts[:i]) in "/home/x-kgootkin/": continue
31
+ if not exists("/".join(parts[:i])):
32
+ mkdir("/".join(parts[:i]))
33
+
34
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
35
+ # >-|===|> Classes <|===|-<
36
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
37
+ class File:
38
+ def __init__(self, path:str, master=None, executable:bool=False, verbose:bool=False) -> None:
39
+ """A convenience class to deal with file io
40
+
41
+ Args:
42
+ path (str): the location of the file.
43
+ master (str, optional): the path to the template to restore this file from if necessary. Defaults to None.
44
+ executable (bool, optional): is this file an executable. Defaults to False.
45
+ verbose (bool, optional): should this file be annoying. Defaults to False.
46
+ """
47
+ self.path: str = abspath(path.replace("\\", "/").replace('//', '/'))
48
+ pathlist: list[str] = self.path.split("/")
49
+ self.parent = Folder("/".join(pathlist[:-1]))
50
+ self.grandparent = Folder("/".join(pathlist[:-2])) if len(pathlist)>3 else None
51
+ self.greatgrandparent = Folder("/".join(pathlist[:-3])) if len(pathlist)>4 else None
52
+ self.name = self.path.split("/")[-1]
53
+ self.extension = self.name.split(".")[-1] if "." in self.name else None
54
+ self.master = master if not isinstance(master, str) else File(master)
55
+ self.executable = executable
56
+ self.verbose = verbose
57
+ def __repr__(self) -> str: return self.path
58
+ def __str__(self) -> str: return "\n".join(self.lines)
59
+ def __add__(self, other):
60
+ match other:
61
+ case list():
62
+ other.append(self)
63
+ return other
64
+ case File(): return [self, other]
65
+
66
+ case _: raise NotImplementedError(f"can't add object of type: {type(other)} to a Folder object: {repr(self)}")
67
+ def __radd__(self, other):
68
+ if other==0: return self
69
+ return self.__add__(other)
70
+ @property
71
+ def exists(self) -> bool: return exists(self.path)
72
+ def copy(self, destination:str): copy(self.path, destination)
73
+ def move(self, destination:str):
74
+ move(self.path, destination)
75
+ self = File.__init__(destination, master=self.master, executable=self.executable)
76
+ def update(self) -> None:
77
+ if self.verbose: print(f'updating {self.name}...')
78
+ assert self.master is not None, "No master copy to update from."
79
+ if self.exists: self.delete(interactive=False)
80
+ self.master.copy(self.path)
81
+ self = File(self.path, master=self.master)
82
+ def delete(self, interactive=True) -> None:
83
+ if interactive and not yesno(f"Are you sure you want to permanently delete {self.path} and all of its contents?\n"): return None
84
+ else: remove(self.path)
85
+ def read(self) -> None:
86
+ if not self.exists: return []
87
+ with open(self.path, 'r') as file:
88
+ self.lines = [f.strip('\n') for f in file.readlines()]
89
+ def save(self, interactive=True):
90
+ if interactive and not yesno(f"Are you sure you want to permanently overwrite {self.path}?\n"):
91
+ return None
92
+ with open(self.path, 'w+') as file:
93
+ if not file.writable: raise PermissionError(f"attempted to save unwritable file: {self.path}")
94
+ file.writelines("\n".join(self.lines))
95
+
96
+ class Folder:
97
+ def __init__(self, path:str, master=None) -> None:
98
+ self.path = '/' if path=='' else abspath(path.replace("\\", "/").replace('//', '/'))
99
+ self.name = self.path.split("/")[-1] if len(self.path.split('/')[-1])>0 else self.path.split("/")[-2]
100
+ self.master = master if not isinstance(master, str) else Folder(master)
101
+ def __repr__(self) -> str: return self.path
102
+ def __len__(self) -> int: return len(self.children)
103
+ def __iter__(self):
104
+ self.index = 0
105
+ return self
106
+ def __next__(self):
107
+ if self.index < len(self):
108
+ i = self.index
109
+ self.index += 1
110
+ return Folder(self.children[i]) if isdir(self.children[i]) else File(self.children[i])
111
+ else: raise StopIteration
112
+ def __add__(self, other):
113
+ match other:
114
+ case str():
115
+ p = f"{self.path}/{other}"
116
+ return Folder(p) if isdir(p) else File(p) if isfile(p) else None
117
+ case File(): return self.children+[other]
118
+ case Folder(): return self.children + other.children
119
+ case list(): return self.children + other
120
+ case _: raise NotImplementedError(f"can't add object of type: {type(other)} to a Folder object: {repr(self)}")
121
+ def __radd__(self, other):
122
+ if other==0: return [self]
123
+ return self.__add__(other)
124
+ @property
125
+ def exists(self) -> bool: return exists(self.path)
126
+ @property
127
+ def children(self) -> list: return glob(self.path+"/*" if self.path!='/' else "/*")
128
+ def ls(self) -> None: print("\n".join(self.children))
129
+ def make(self) -> None: ensure_path(self.path)
130
+ def copy(self, destination:str) -> None: copytree(self.path, destination)
131
+ def revert(self) -> None:
132
+ assert self.master, "No master copy to update from."
133
+ if self.exists: self.delete(interactive=False)
134
+ self.master.copy(self.path)
135
+ self = Folder(self.path, master=self.master)
136
+ def delete(self, interactive=True) -> None:
137
+ if interactive and not yesno(f"Are you sure you want to permanently delete {self.path} and all of its contents?\n"):
138
+ return None
139
+ rmtree(self.path)
140
+
141
+ def parse(path: str | list[str]) -> Folder | File:
142
+ """take a path or list of paths and turn them into Folder or File objects as appropriate.
143
+
144
+ Args:
145
+ path (str | list[str]): the path you want to be a File/Folder object
146
+
147
+ Raises:
148
+ FileNotFoundError: if you can't match path
149
+
150
+ Returns:
151
+ Folder | File: path as a Folder/File.
152
+ """
153
+ match path:
154
+ case str():
155
+ if isdir(path): return Folder(path)
156
+ if isfile(path): return File(path)
157
+ case list()|np.ndarray():
158
+ return [Folder(p) if isdir(p) else File(p) if isfile(p) else None for p in path]
159
+ raise FileNotFoundError(f"Unable to parse path(s): {path}")
@@ -1,17 +1,27 @@
1
- from subprocess import check_output, DEVNULL
2
- import numpy as np
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
3
4
  from kbasic.bar import ProgressBar, redirect_to_tqdm
4
5
  from kbasic.audio import success
5
- from pysim.dhybridr.io import dHybridRinput
6
+ # from kgsim.dhybridr.io import dHybridRinput
7
+ from subprocess import check_output, DEVNULL
6
8
  from tqdm import tqdm
7
9
  from time import sleep
8
-
9
10
  import asyncio
11
+ import numpy as np
10
12
 
13
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
14
+ # >-|===|> Types <|===|-<
15
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
17
+ # >-|===|> Definitions <|===|-<
18
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
11
19
  _USERNAME_ = 'x-kgootkin'
12
-
13
20
  bad = ['\x1b[31m', '\x1b[34m', '\x1b[m']
14
21
 
22
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
23
+ # >-|===|> Functions <|===|-<
24
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
25
  def parse_shell_output(output):
16
26
  match output:
17
27
  case str(): return output
@@ -22,7 +32,6 @@ def parse_shell_output(output):
22
32
  if b in output[i]:
23
33
  output[i] = output[i].strip(b)
24
34
  return output
25
-
26
35
  def system(cmd: str):
27
36
  print(cmd)
28
37
  command = cmd.split(' ') if type(cmd)==str else cmd
@@ -37,17 +46,19 @@ def system(cmd: str):
37
46
  for j in range(start_quote+1, end_quote+1): del command[j]
38
47
  output = parse_shell_output(check_output(command, stderr=DEVNULL).decode().splitlines())
39
48
  return output
40
-
41
49
  def anvil(cmd: str):
42
50
  output = parse_shell_output(check_output(['ssh', 'x-kgootkin@anvil.rcac.purdue.edu', *cmd.split(' ')], stderr=DEVNULL).decode().splitlines())
43
51
  return output
44
-
45
52
  async def anvil_async(cmd: str): asyncio.to_thread(anvil, cmd)
46
-
47
-
48
53
  def anvil_queue(username=_USERNAME_): return anvil(f"squeue -u {username}")
49
- qs = anvil_queue
50
-
54
+ qs = anvil_queue
55
+
56
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
57
+ # >-|===|> Decorators <|===|-<
58
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
59
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
60
+ # >-|===|> Classes <|===|-<
61
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
51
62
  class AnvilJob:
52
63
  def __init__(self, queue_row: str, sep="DISTINCTSEPERATOR"):
53
64
  self.sep = sep
@@ -66,32 +77,28 @@ class AnvilJob:
66
77
 
67
78
  def __repr__(self): return "-"*30 + f"\n{self.name}: {self.status}\n\t{self.time}/{self.time_limit}"
68
79
 
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
-
80
+ # def update(self, input=True, iter=True):
81
+ # match input, iter:
82
+ # case True, True:
83
+ # 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/")
84
+ # input_lines = "\n".join(x).split(self.sep)[0].split('\n')
85
+ # self.input = dHybridRinput(input_lines)
86
+ # self.iter = int(x[-1][5:-3])
87
+ # case True, False:
88
+ # self.input = dHybridRinput(anvil(f"cat /anvil/scratch/{self.username}/sims/{self.name}/input/input"))
89
+ # case False, True:
90
+ # self.iter = int(anvil(f"ls /anvil/scratch/{self.username}/sims/{self.name}/Output/Fields/Magnetic/Total/x/")[-1][5:-3])
81
91
  def get_anvil_jobs(username=_USERNAME_):
82
92
  q = anvil(f"squeue -u {username}")
83
93
  if type(q)==str: return []
84
94
  return [AnvilJob(x) for x in q[1:]]
85
-
86
95
  async def get_anvil_jobs_async(username=_USERNAME_):
87
96
  q = await asyncio.to_thread(anvil, f"squeue -u {username}")
88
97
  if type(q)==str: return []
89
98
  return [AnvilJob(x) for x in q[1:]]
90
-
91
99
  async def get_anvil_sim_iter(name: str):
92
100
  iter = int(await asyncio.to_thread(anvil, f"ls /anvil/scratch/{username}/sims/{name}/Output/Fields/Magnetic/Total/x/")[-1][5:-3])
93
101
  return iter
94
-
95
102
  class AnvilQueue:
96
103
  def __init__(self, username=_USERNAME_):
97
104
  self.username = username
@@ -0,0 +1,38 @@
1
+ """
2
+ I don't like type checking in python, so the way i decided to do it is if i want
3
+ to see if something looks like a number (int, float, complex or numpy variants)
4
+ i would say ->
5
+ if type(x) in Number.types: do_something()
6
+ elif type(x) in Iterable.types: do_something_else()
7
+ """
8
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
9
+ # >-|===|> Imports <|===|-<
10
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
11
+ from numpy import ndarray, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float16, float32, float64, longdouble, complex64, complex128, clongdouble
12
+
13
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
14
+ # >-|===|> Types <|===|-<
15
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
+ class Number:
17
+ types: list = [
18
+ int, int8, uint8, int16, uint16, int32, uint32, int64, uint64,
19
+ float, float16, float32, float64, longdouble,
20
+ complex, complex64, complex128, clongdouble
21
+ ]
22
+ class Iterable:
23
+ types: list = [
24
+ list, set, dict, tuple, ndarray
25
+ ]
26
+
27
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
28
+ # >-|===|> Definitions <|===|-<
29
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
30
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
31
+ # >-|===|> Functions <|===|-<
32
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
33
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
34
+ # >-|===|> Decorators <|===|-<
35
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
36
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
37
+ # >-|===|> Classes <|===|-<
38
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -0,0 +1,42 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
5
+ # >-|===|> Types <|===|-<
6
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
7
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
8
+ # >-|===|> Definitions <|===|-<
9
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
10
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
11
+ # >-|===|> Functions <|===|-<
12
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
13
+ def yesno(prompt: str):
14
+ """
15
+ prompt the user to either reply yes or no
16
+ :param prompt: the yes/no question to be answered
17
+ :return: True if yes False if no
18
+ """
19
+ response = input(prompt).lower()
20
+ if 'y' in response and not 'n' in response:
21
+ return True
22
+ elif 'n' in response and not 'y' in response:
23
+ return False
24
+ else:
25
+ def retry_yesno():
26
+ retry_prompt = "Sorry I couldn't read that please respond with yes or no\n" + prompt
27
+ retry_response = input(retry_prompt).lower()
28
+ if 'y' in retry_response and not 'n' in retry_response:
29
+ return True
30
+ elif 'n' in retry_response and not 'y' in retry_response:
31
+ return False
32
+ else:
33
+ raise ValueError("need a response with either y or n in it.")
34
+
35
+ return retry_yesno()
36
+
37
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
38
+ # >-|===|> Decorators <|===|-<
39
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
40
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
41
+ # >-|===|> Classes <|===|-<
42
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -1,16 +1,14 @@
1
1
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
2
  # >-|===|> Imports <|===|-<
3
3
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
- from numpy import array, ndarray, cos, arccos, sin, arcsin, sqrt, float64, float32, exp, pi, complex128
4
+ from numpy import array, cos, sin, sqrt, exp
5
5
  from typing import Self
6
6
  from collections.abc import Generator
7
+ from kbasic.typing import Number, Iterable
8
+
7
9
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
8
10
  # >-|===|> Definitions <|===|-<
9
11
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
10
- class Number:
11
- types: list = [float, float64, float32, int, complex, complex128]
12
- class Iterable:
13
- types: list = [list, ndarray, tuple, Generator]
14
12
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
13
  # >-|===|> Functions <|===|-<
16
14
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -30,6 +28,7 @@ def organize_components(components) -> tuple[float|int]:
30
28
  case (Generator(),): return tuple(components[0])
31
29
  case (Vector(),): return components[0].components
32
30
  case _: raise TypeError(f"{components} cannot be matched")
31
+
33
32
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
34
33
  # >-|===|> Classes <|===|-<
35
34
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -92,7 +91,6 @@ class Vector:
92
91
  match other:
93
92
  case 0: return self
94
93
  case _: return self.__floordiv__(other)
95
-
96
94
  class Norm(Vector):
97
95
  def __init__(self, *components):
98
96
  components: tuple = organize_components(components)
@@ -105,7 +103,6 @@ class Quaternion(Norm):
105
103
  self.axis = sin(angle/2)*Vector(axis)
106
104
  self.axis *= sqrt(1-q0**2)/abs(self.axis)
107
105
  Norm.__init__(self, q0, *self.axis.components)
108
-
109
106
  class R2(Vector):
110
107
  def __init__(self, *components):
111
108
  self.components: tuple[Number] = organize_components(components)
@@ -136,7 +133,6 @@ class R3(Vector):
136
133
  rotation_inverse = array([rotation[0], *-rotation[1:]])
137
134
  rotated_vector = hamilton_product(hamilton_product(rotation, qs), rotation_inverse)[1:]
138
135
  self.__init__(*rotated_vector)
139
-
140
136
  class Matrix:
141
137
  def __init__(self, array):
142
138
  self.array = array
kbasic-0.1.2/README.md DELETED
File without changes
@@ -1,23 +0,0 @@
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
@@ -1,6 +0,0 @@
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 *
@@ -1,25 +0,0 @@
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
@@ -1,8 +0,0 @@
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")
@@ -1,43 +0,0 @@
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
File without changes
@@ -1,13 +0,0 @@
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
- ]
@@ -1,23 +0,0 @@
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()