kgsim 0.3.2__tar.gz → 0.3.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.
- {kgsim-0.3.2 → kgsim-0.3.4}/PKG-INFO +1 -1
- {kgsim-0.3.2 → kgsim-0.3.4}/pyproject.toml +1 -1
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/dhybridr/dhybridr.py +66 -4
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/dhybridr/turbulence.py +22 -22
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/fields/__init__.py +1 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/fields/scalar.py +22 -12
- kgsim-0.3.4/src/kgsim/fields/structure.py +161 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/fields/vector.py +15 -5
- {kgsim-0.3.2 → kgsim-0.3.4}/README.md +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/__init__.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/athena/__init__.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/athena/athena.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/athena/athena_read.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/dhybridr/__init__.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/dhybridr/anvil_submit.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/dhybridr/initializer.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/dhybridr/io.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/exceptions.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/fields/calc.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/particles.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/simulation.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/templates/3dHybridR/config +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/templates/3dHybridR/input/input +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/templates/3dHybridR/sim_init_3D.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/templates/3dHybridR/submit_anvil.sh +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/templates/__init__.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/templates/dHybridR/input/input +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/templates/dHybridR/sim_init.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/templates/dHybridR/submit_anvil.sh +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/tristan/__init__.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/tristan/kappa.py +0 -0
- {kgsim-0.3.2 → kgsim-0.3.4}/src/kgsim/tristan/tristan.py +0 -0
|
@@ -7,11 +7,11 @@ from glob import glob
|
|
|
7
7
|
from os import system
|
|
8
8
|
from os.path import exists
|
|
9
9
|
from h5py import File as h5File
|
|
10
|
-
from numpy import mean, linspace, diff, exp, array, prod, inf, vstack, nanmin, \
|
|
11
|
-
|
|
10
|
+
from numpy import mean, linspace, diff, exp, array, prod, inf, vstack, nanmin, nanmax, append, \
|
|
11
|
+
ndarray, arange
|
|
12
12
|
from numpy.random import choice
|
|
13
13
|
|
|
14
|
-
from kgsim.fields import ScalarField, VectorField, Az
|
|
14
|
+
from kgsim.fields import ScalarField, VectorField, Az, single_point_single_lag_structure_function
|
|
15
15
|
from kgsim.simulation import GenericSimulation, SimulationGroup
|
|
16
16
|
from kgsim.particles import Species, Particle
|
|
17
17
|
from kgsim.dhybridr.io import dHybridRinput, dHybridRout
|
|
@@ -22,7 +22,7 @@ from kgsim.templates import dHybridRtemplate
|
|
|
22
22
|
from kplot import show, func_video
|
|
23
23
|
from kbasic.bar import verbose_bar
|
|
24
24
|
from kbasic.strings import purple
|
|
25
|
-
from kbasic.parsing import Folder
|
|
25
|
+
from kbasic.parsing import Folder, Path, ensure_path
|
|
26
26
|
from kbasic.vectors import Vector
|
|
27
27
|
|
|
28
28
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
@@ -35,6 +35,28 @@ track_keys = [
|
|
|
35
35
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
36
36
|
# >-|===|> Functions <|===|-<
|
|
37
37
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
38
|
+
def create_dhybridr_data_file(data: ndarray, path: Path) -> h5File:
|
|
39
|
+
"""docstring"""
|
|
40
|
+
with h5File(path, 'a') as file:
|
|
41
|
+
file['DATA'] = data
|
|
42
|
+
for i,si in enumerate(data.shape):
|
|
43
|
+
file['AXIS'][f'X{i+1} AXIS'] = [0., si]
|
|
44
|
+
return file
|
|
45
|
+
|
|
46
|
+
def save_dhybridr_scalar_field(field: ScalarField, destination: Path):
|
|
47
|
+
"""docstring"""
|
|
48
|
+
assert exists(destination)
|
|
49
|
+
assert hasattr(field.parent, 'iters')
|
|
50
|
+
for f,it in zip(field, field.parent.iters):
|
|
51
|
+
fn = destination / f'{str(it).zfill(8)}_{field.name}.h5'
|
|
52
|
+
create_dhybridr_data_file(f, fn)
|
|
53
|
+
|
|
54
|
+
def save_dhybridr_vector_field(field: VectorField, destination: Path):
|
|
55
|
+
"""docstring"""
|
|
56
|
+
save_dhybridr_scalar_field(field.x, destination/'x')
|
|
57
|
+
save_dhybridr_scalar_field(field.y, destination/'y')
|
|
58
|
+
save_dhybridr_scalar_field(field.z, destination/'z')
|
|
59
|
+
|
|
38
60
|
# simulation parsing
|
|
39
61
|
def extract_energy(file_name: str) -> tuple:
|
|
40
62
|
"""docstring"""
|
|
@@ -45,9 +67,13 @@ def extract_energy(file_name: str) -> tuple:
|
|
|
45
67
|
dlne = diff(lne)[0]
|
|
46
68
|
E = exp(lne)
|
|
47
69
|
return E, fE, dlne
|
|
70
|
+
|
|
71
|
+
|
|
48
72
|
def iters(simulation) -> list[int]:
|
|
49
73
|
"""Grab the iterations of each snapshot for a simulation"""
|
|
50
74
|
return [int(fn[-11:-3]) for fn in simulation.density.file_names]
|
|
75
|
+
|
|
76
|
+
|
|
51
77
|
def times(simulation: GenericSimulation, ndigits: int = 7) -> list[float]:
|
|
52
78
|
"""return the time (in simulation units) of each measurement
|
|
53
79
|
|
|
@@ -61,6 +87,8 @@ def times(simulation: GenericSimulation, ndigits: int = 7) -> list[float]:
|
|
|
61
87
|
"""
|
|
62
88
|
x = list(array(iters(simulation)).astype(float) * simulation.dt)
|
|
63
89
|
return [round(xi, ndigits) for xi in x]
|
|
90
|
+
|
|
91
|
+
|
|
64
92
|
def particle_video(
|
|
65
93
|
sim,
|
|
66
94
|
particles: list[str] | int,
|
|
@@ -139,9 +167,11 @@ class dHybridRparticle(Particle):
|
|
|
139
167
|
Particle.__init__(self, species=species, tag=tag)
|
|
140
168
|
self.loaded: bool = False
|
|
141
169
|
if load: self.load()
|
|
170
|
+
|
|
142
171
|
def __len__(self) -> int:
|
|
143
172
|
assert self.loaded, f"Tried to take the length of {self} without loading first"
|
|
144
173
|
return len(self.x)
|
|
174
|
+
|
|
145
175
|
def load(self):
|
|
146
176
|
"""docstring"""
|
|
147
177
|
with h5File(self.species.path) as file:
|
|
@@ -158,6 +188,20 @@ class dHybridRparticle(Particle):
|
|
|
158
188
|
self.B = Vector(self.B1, self.B2, self.B3)
|
|
159
189
|
self.E = Vector(self.E1, self.E2, self.E3)
|
|
160
190
|
self.loaded: bool = True
|
|
191
|
+
|
|
192
|
+
def structure_function(self, l):
|
|
193
|
+
"""docstring"""
|
|
194
|
+
u = abs(self.parent.u)
|
|
195
|
+
xcells = (self.x // self.parent.dx).astype(int)
|
|
196
|
+
ycells = (self.y // self.parent.dy).astype(int)
|
|
197
|
+
pis = arange(len(self.parent)-1) * self.species.pipsi
|
|
198
|
+
return array([
|
|
199
|
+
single_point_single_lag_structure_function(
|
|
200
|
+
xcells[i], ycells[i], 1, u[bi], l
|
|
201
|
+
) for i,bi in verbose_bar(zip(pis, range(len(self.parent))), self.parent.verbose)
|
|
202
|
+
])
|
|
203
|
+
|
|
204
|
+
|
|
161
205
|
class dHybridRspecies(Species):
|
|
162
206
|
def __init__(
|
|
163
207
|
self,
|
|
@@ -176,11 +220,15 @@ class dHybridRspecies(Species):
|
|
|
176
220
|
if self.input.track_dump:
|
|
177
221
|
self.path = parent.path+f"/Output/Tracks/Sp{self.nstr}/track_Sp{self.nstr}.h5"
|
|
178
222
|
self.load()
|
|
223
|
+
|
|
179
224
|
def __repr__(self) -> str: return self.name
|
|
225
|
+
|
|
180
226
|
def __str__(self) -> str: return self.name
|
|
227
|
+
|
|
181
228
|
def __len__(self) -> int:
|
|
182
229
|
assert self.loaded, f"Tried to take the length of {self} without loading first"
|
|
183
230
|
return len(self.tags)
|
|
231
|
+
|
|
184
232
|
def __getitem__(self, key):
|
|
185
233
|
match key:
|
|
186
234
|
case str(tag): return dHybridRparticle(self, tag)
|
|
@@ -193,6 +241,7 @@ class dHybridRspecies(Species):
|
|
|
193
241
|
1 if di is None else di
|
|
194
242
|
)
|
|
195
243
|
]
|
|
244
|
+
|
|
196
245
|
def load(self):
|
|
197
246
|
"""docstring"""
|
|
198
247
|
with h5File(self.path) as file:
|
|
@@ -209,6 +258,8 @@ class dHybridRspecies(Species):
|
|
|
209
258
|
])
|
|
210
259
|
if len(particle_list)==1: return particle_list[0]
|
|
211
260
|
else: return particle_list
|
|
261
|
+
|
|
262
|
+
|
|
212
263
|
class dHybridR(GenericSimulation):
|
|
213
264
|
"""
|
|
214
265
|
A simulation class to interact with dHybridR simulations in python
|
|
@@ -304,11 +355,13 @@ class dHybridR(GenericSimulation):
|
|
|
304
355
|
self.dy: float = self.input.boxsize[1]/self.input.ncells[1]
|
|
305
356
|
self.dz: float = self.input.boxsize[2]/self.input.ncells[2] \
|
|
306
357
|
if len(self.input.boxsize)==3 else inf
|
|
358
|
+
|
|
307
359
|
def run(self, initializer: dHybridRinitializer, submit_script: AnvilSubmitScript) -> None:
|
|
308
360
|
"""docstring"""
|
|
309
361
|
initializer.prepare_simulation()
|
|
310
362
|
submit_script.write()
|
|
311
363
|
system(f"sh {submit_script.path}")
|
|
364
|
+
|
|
312
365
|
def parse_output(self) -> None:
|
|
313
366
|
"""docstring"""
|
|
314
367
|
self.out = dHybridRout(self.path+"/out")
|
|
@@ -413,6 +466,15 @@ class dHybridR(GenericSimulation):
|
|
|
413
466
|
self.start = time()
|
|
414
467
|
self.iter = array(iters(self))
|
|
415
468
|
self.time = array(times(self))
|
|
469
|
+
|
|
470
|
+
def save_field(self, field: ScalarField | VectorField) -> None:
|
|
471
|
+
"""docstring"""
|
|
472
|
+
destination = self.output / f"Postfacto/{field.name}"
|
|
473
|
+
system(f"mkdir -p {destination}")
|
|
474
|
+
match field:
|
|
475
|
+
case ScalarField(): save_dhybridr_scalar_field(field, destination)
|
|
476
|
+
case VectorField(): save_dhybridr_vector_field(field, destination)
|
|
477
|
+
|
|
416
478
|
def magnetic_potential_extrema(self):
|
|
417
479
|
"""docstring"""
|
|
418
480
|
n, x = inf, -inf
|
|
@@ -45,28 +45,28 @@ def non_thermal_slope(E, f, mach):
|
|
|
45
45
|
[slope, b], _ = curve_fit(line, log10(E), log10(f/E))
|
|
46
46
|
return slope, b
|
|
47
47
|
|
|
48
|
-
def S(p: int, u: ndarray, l: int):
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
48
|
+
# def S(p: int, u: ndarray, l: int):
|
|
49
|
+
# """
|
|
50
|
+
# Compute structure function of power p, of field u, at lag l
|
|
51
|
+
# S_p(u, l) = <(u(x) - u(x + l))^p>
|
|
52
|
+
# p: int - order of structure function
|
|
53
|
+
# u: Array - field to take structure function of
|
|
54
|
+
# l: int - lag, i.e. how many pixels away should the function look
|
|
55
|
+
# """
|
|
56
|
+
# (Nx, Ny) = u.shape
|
|
57
|
+
# assert l < min(u.shape), f"l ({l}) is larger than smallest dimension ({min(u.shape)})"
|
|
58
|
+
# grid = mgrid[-Nx//2:Nx//2, -Ny//2:Ny//2]
|
|
59
|
+
# grid = hypot(grid[0], grid[1])
|
|
60
|
+
# grid = where(grid//1 == l, True, False)
|
|
61
|
+
# in_annulus = lambda i, j: roll(grid, (i - Nx//2, j - Ny//2), axis=(0,1))
|
|
62
|
+
# # for some reason this needs to be transposed
|
|
63
|
+
# return array([
|
|
64
|
+
# [
|
|
65
|
+
# mean(
|
|
66
|
+
# (u[i, j] - u[in_annulus(i, j)]) ** p
|
|
67
|
+
# ) for i in arange(Nx)
|
|
68
|
+
# ] for j in progress_bar(arange(Ny))
|
|
69
|
+
# ]).T
|
|
70
70
|
|
|
71
71
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
72
72
|
# >-|===|> Classes <|===|-<
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
# >-|===|> Imports <|===|-<
|
|
4
4
|
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
5
5
|
from typing import Optional, Self, Callable
|
|
6
|
-
from glob import glob
|
|
7
6
|
from functools import cached_property
|
|
8
7
|
from os.path import isdir, isfile
|
|
9
8
|
import logging
|
|
@@ -63,7 +62,7 @@ class ScalarField:
|
|
|
63
62
|
caching: bool = False,
|
|
64
63
|
verbose: bool = False,
|
|
65
64
|
debug: bool = False,
|
|
66
|
-
|
|
65
|
+
) -> None:
|
|
67
66
|
self.name: str = name
|
|
68
67
|
self.latex: str = latex
|
|
69
68
|
self.parent = parent
|
|
@@ -82,36 +81,39 @@ class ScalarField:
|
|
|
82
81
|
#if its a folder
|
|
83
82
|
case Folder() | str() if isdir(source):
|
|
84
83
|
self.path = Folder(source)
|
|
85
|
-
self.single = False
|
|
86
84
|
example_file: File = self.path.children[0]
|
|
87
85
|
if example_file.extension==".h5": self._from_folder_of_h5(self.path)
|
|
88
86
|
#otherwise its a file
|
|
89
87
|
case File() | str() if isfile(source):
|
|
90
88
|
self.path = File(source)
|
|
91
|
-
self.single = True
|
|
92
89
|
if self.path.extension=="h5": self._from_h5(self.path)
|
|
93
90
|
else: self._from_csv(self.path)
|
|
94
91
|
#or if its already been read
|
|
95
92
|
case Array():
|
|
96
|
-
self.single = True
|
|
97
93
|
self._from_numpy(array(source))
|
|
98
94
|
case _: raise TypeError(f"{source} of wrong type: {type(source)}")
|
|
99
95
|
# read the stats file
|
|
100
96
|
if parent: self.stats = Folder(f"{self.parent.path}/stats")
|
|
101
97
|
if self.stats: self._read_stats()
|
|
102
|
-
|
|
98
|
+
|
|
99
|
+
def __len__(self) -> int:
|
|
100
|
+
if hasattr(self, 'array'):
|
|
101
|
+
return 1 if len(self.array.shape)==2 else len(self.array)
|
|
102
|
+
return len(self.file_names)
|
|
103
|
+
|
|
103
104
|
def __iter__(self) -> Self:
|
|
104
|
-
assert not self.single, "Cannot iterate through single scalar field"
|
|
105
105
|
self.index = 0
|
|
106
106
|
return self
|
|
107
|
+
|
|
107
108
|
def __next__(self) -> NDArray:
|
|
108
109
|
if self.index < len(self):
|
|
109
110
|
i = self.index
|
|
110
111
|
self.index += 1
|
|
111
112
|
return self[i]
|
|
112
113
|
else: raise StopIteration
|
|
114
|
+
|
|
113
115
|
def __getitem__(self, item: int|slice|ArrayLike) -> NDArray:
|
|
114
|
-
if self
|
|
116
|
+
if hasattr(self, 'array'): return self.array[item]
|
|
115
117
|
match item:
|
|
116
118
|
case int():
|
|
117
119
|
return self.cache[item] if self.caching and item in self.cache \
|
|
@@ -132,32 +134,35 @@ class ScalarField:
|
|
|
132
134
|
self.cache[i] if self.caching and i in self.cache \
|
|
133
135
|
else self.reader(self.file_names[i], i) for i in item
|
|
134
136
|
])
|
|
137
|
+
|
|
135
138
|
def _from_folder_of_h5(self, path: Folder) -> None:
|
|
136
139
|
self.file_names: list = sorted(self.path.glob("*.h5"))
|
|
137
140
|
self.reader: Callable = self._read_h5_file
|
|
138
141
|
self.shape = self[0].shape
|
|
139
142
|
self.ndims = len(self.shape)
|
|
140
143
|
self.size = prod(self.shape) * len(self)
|
|
144
|
+
|
|
141
145
|
def _from_h5(self, file:str) -> None:
|
|
142
146
|
self.file = file.path if isinstance(file, File) else file
|
|
143
147
|
self.array = self._read_h5_file(file, 0)
|
|
144
148
|
self.shape = self.array.shape
|
|
145
149
|
self.ndims = len(self.shape)
|
|
146
150
|
self.size = prod(self.shape) * len(self)
|
|
151
|
+
|
|
147
152
|
def _read_h5_file(self, file:str, item) -> NDArray:
|
|
148
153
|
with h5File(file, 'r') as f:
|
|
149
154
|
output = array(f["DATA"][:])
|
|
150
155
|
#GODDMANIT I HATE THAT IT DOES Y,X and not X,Y
|
|
151
156
|
if self.caching: self.cache[item] = output
|
|
152
157
|
return output
|
|
153
|
-
|
|
154
|
-
...
|
|
158
|
+
|
|
155
159
|
def _read_csv_file(self, file:str) -> None: pass
|
|
160
|
+
|
|
156
161
|
def _from_numpy(self, arr:ndarray) -> None:
|
|
157
|
-
self.single = True
|
|
158
162
|
self.array = arr
|
|
159
163
|
self.shape = arr.shape
|
|
160
164
|
self.ndims = len(self.shape)
|
|
165
|
+
|
|
161
166
|
def _read_stats(self) -> None:
|
|
162
167
|
ensure_path(self.stats.path)
|
|
163
168
|
self.statsFile = File(f"{self.stats.path}/{self.name}.csv")
|
|
@@ -187,6 +192,7 @@ class ScalarField:
|
|
|
187
192
|
self.min = nanmin(data[0])
|
|
188
193
|
self.max = nanmax(data[1])
|
|
189
194
|
savetxt(self.statsFile.path, data, delimiter=',', header="min,max,median,mean,std")
|
|
195
|
+
|
|
190
196
|
@cached_property #cached property only is used if self.min not set by stats file
|
|
191
197
|
def min(self) -> Number:
|
|
192
198
|
"""docstring"""
|
|
@@ -195,6 +201,7 @@ class ScalarField:
|
|
|
195
201
|
y = append(frame, x)
|
|
196
202
|
x = nanmin(y)
|
|
197
203
|
return x
|
|
204
|
+
|
|
198
205
|
@cached_property #cached property only is used if self.max not set by stats file
|
|
199
206
|
def max(self) -> Number:
|
|
200
207
|
"""docstring"""
|
|
@@ -203,17 +210,20 @@ class ScalarField:
|
|
|
203
210
|
y = append(frame, x)
|
|
204
211
|
x = nanmax(y)
|
|
205
212
|
return x
|
|
213
|
+
|
|
206
214
|
@cached_property
|
|
207
215
|
def extrema(self) -> tuple[Number]:
|
|
208
216
|
"""docstring"""
|
|
209
217
|
return self.min, self.max
|
|
218
|
+
|
|
210
219
|
def show(self, item:int, **kwargs) -> None:
|
|
211
220
|
"""docstring"""
|
|
212
221
|
if hasattr(self.parent, 'dx'):
|
|
213
222
|
x_ticks = arange(0, self.parent.input.boxsize[0], self.parent.dx)
|
|
214
223
|
y_ticks = arange(0, self.parent.input.boxsize[1], self.parent.dy)
|
|
215
|
-
show(self[item], x=x_ticks, y=y_ticks, **kwargs)
|
|
224
|
+
return show(self[item], x=x_ticks, y=y_ticks, **kwargs)
|
|
216
225
|
assert False
|
|
226
|
+
|
|
217
227
|
def movie(self, file_name=None, norm='none', cmap=default_cmap, func=None,**kwds) -> None:
|
|
218
228
|
"""docstring"""
|
|
219
229
|
show_video(
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""calculate the structure function of arrays and fields"""
|
|
2
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
3
|
+
# >-|===|> Imports <|===|-<
|
|
4
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
5
|
+
from math import sqrt
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from kbasic import verbose_bar, Tex
|
|
8
|
+
from numpy import ndarray, array, mgrid, random, floor_divide, hypot, where, roll, interp, log10, \
|
|
9
|
+
argsort
|
|
10
|
+
from numpy import abs as npabs
|
|
11
|
+
from numpy import mean as npmean
|
|
12
|
+
|
|
13
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
14
|
+
# >-|===|> Functions <|===|-<
|
|
15
|
+
# !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
|
|
16
|
+
def select_random_pixels(image: ndarray, fill_fraction):
|
|
17
|
+
"""select fill_fraction of pixels of image randomly"""
|
|
18
|
+
(Nx, Ny) = image.shape
|
|
19
|
+
if fill_fraction==1:
|
|
20
|
+
X2D, Y2D = mgrid[:Nx, :Ny]
|
|
21
|
+
x, y = X2D.flatten(), Y2D.flatten()
|
|
22
|
+
return x, y
|
|
23
|
+
pixel_numbers = random.choice(
|
|
24
|
+
image.size,
|
|
25
|
+
size=int((image.size*fill_fraction)//1),
|
|
26
|
+
replace=False
|
|
27
|
+
)
|
|
28
|
+
x = floor_divide(pixel_numbers, Nx)
|
|
29
|
+
y = pixel_numbers % Nx
|
|
30
|
+
return x, y
|
|
31
|
+
|
|
32
|
+
def single_point_single_lag_structure_function(i, j, p, u, l):
|
|
33
|
+
"""docstring"""
|
|
34
|
+
(Nx, Ny) = u.shape
|
|
35
|
+
# prepare annulus selection function
|
|
36
|
+
grid = mgrid[-Nx//2:Nx//2, -Ny//2:Ny//2]
|
|
37
|
+
grid = hypot(grid[0], grid[1])
|
|
38
|
+
grid = where(grid//1 == l, True, False)
|
|
39
|
+
in_annulus = lambda i, j: roll(grid, (i - Nx//2, j - Ny//2), axis=(0,1))
|
|
40
|
+
return npmean(npabs(u[i,j] - u[in_annulus(i,j)])**p)
|
|
41
|
+
|
|
42
|
+
def single_structure_function(p, u, l, sample, verbose, _is_multi: bool = False):
|
|
43
|
+
"""
|
|
44
|
+
for a single value of l, return the structure function
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
p (int): structure function order
|
|
48
|
+
u (ndarray): the field to compute the structure function for
|
|
49
|
+
l (int): lag
|
|
50
|
+
sample (str|float): 'all' if you want to sample every point else the fraction
|
|
51
|
+
of possible points to select
|
|
52
|
+
verbose (bool): whether to provide verbose output
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
ndarray: The p^th order structure function of u with lag l
|
|
56
|
+
"""
|
|
57
|
+
(Nx, Ny) = u.shape
|
|
58
|
+
# select the pixels to calculate the structure function for
|
|
59
|
+
x_selected, y_selected = select_random_pixels(u, 1 if sample=='all' else sample)
|
|
60
|
+
# prepare annulus selection function
|
|
61
|
+
grid = mgrid[-Nx//2:Nx//2, -Ny//2:Ny//2]
|
|
62
|
+
grid = hypot(grid[0], grid[1])
|
|
63
|
+
grid = where(grid//1 == l, True, False)
|
|
64
|
+
in_annulus = lambda i, j: roll(grid, (i - Nx//2, j - Ny//2), axis=(0,1))
|
|
65
|
+
if sample=='all':
|
|
66
|
+
return array([
|
|
67
|
+
# for pixel i,j
|
|
68
|
+
npmean(
|
|
69
|
+
# take the mean of |u_ij - u_annulus|^p for all values of u in the annulus
|
|
70
|
+
npabs(u[i, j] - u[in_annulus(i, j)]) ** p
|
|
71
|
+
) for i,j in verbose_bar(zip(x_selected, y_selected), verbose, total=len(x_selected))
|
|
72
|
+
]).reshape(u.shape)
|
|
73
|
+
br = verbose_bar(
|
|
74
|
+
zip(x_selected, y_selected),
|
|
75
|
+
verbose,
|
|
76
|
+
desc=Tex(rf"calculate \mathcal[S]^{p}(\ell={l})").string,
|
|
77
|
+
total=len(x_selected),
|
|
78
|
+
position=1,
|
|
79
|
+
leave=False
|
|
80
|
+
)
|
|
81
|
+
# take the mean of |u_ij - u_annulus|^p for all selected pixels
|
|
82
|
+
return npmean(array([
|
|
83
|
+
npmean(
|
|
84
|
+
npabs(u[i, j] - u[in_annulus(i, j)]) ** p
|
|
85
|
+
) for i,j in br
|
|
86
|
+
]))
|
|
87
|
+
|
|
88
|
+
def S(
|
|
89
|
+
p: int,
|
|
90
|
+
u: ndarray,
|
|
91
|
+
l: int|ndarray|list,
|
|
92
|
+
sample: str|float = "all",
|
|
93
|
+
verbose: bool = True
|
|
94
|
+
):
|
|
95
|
+
"""structure function factory
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
p (int): _description_
|
|
99
|
+
u (ndarray): _description_
|
|
100
|
+
l (int | ndarray | list): _description_
|
|
101
|
+
sample (str | float, optional): _description_. Defaults to "all".
|
|
102
|
+
verbose (bool, optional): _description_. Defaults to True.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
_type_: _description_
|
|
106
|
+
"""
|
|
107
|
+
match l:
|
|
108
|
+
# if l is a single number
|
|
109
|
+
case int(): return single_structure_function(p,u,l,sample,verbose)
|
|
110
|
+
# if we want to calculate a series of l's
|
|
111
|
+
case Iterable():
|
|
112
|
+
lbar = verbose_bar(l, verbose, total=len(l), position=0)
|
|
113
|
+
return array([
|
|
114
|
+
single_structure_function(p,u,li,sample,False,_is_multi=True) for li in lbar
|
|
115
|
+
])
|
|
116
|
+
|
|
117
|
+
def log_interp(x_interp, x, y):
|
|
118
|
+
"""perform interpolation in logspace"""
|
|
119
|
+
return 10**interp(log10(x_interp), log10(x), log10(y))
|
|
120
|
+
|
|
121
|
+
def sonic_scale(u, sample=0.01, tolerance=0.05, Ntries=10, return_spectrum=False, verbose=True):
|
|
122
|
+
"""compute the lag at which S(1, u, l)=1"""
|
|
123
|
+
du = lambda li: S(1, u, li, verbose=False, sample=sample)
|
|
124
|
+
|
|
125
|
+
left = 1
|
|
126
|
+
Sl = du(left)
|
|
127
|
+
right = int(max(u.shape)//sqrt(2))
|
|
128
|
+
Sr = du(right)
|
|
129
|
+
assert Sl < 1 and Sr > 1, f"Sonic point {'above' if Sr < 1 else 'below'} inertial range of \
|
|
130
|
+
{left}-{right} and {Sl}-{Sr}."
|
|
131
|
+
|
|
132
|
+
guess = int(log_interp(1, [Sl, Sr], [left, right])//1)
|
|
133
|
+
Sg = du(guess)
|
|
134
|
+
Ng = 0
|
|
135
|
+
|
|
136
|
+
all_l, all_s = [left, guess, right], [Sl, Sg, Sr]
|
|
137
|
+
|
|
138
|
+
while npabs(Sg-1) > tolerance:
|
|
139
|
+
if Sg>1:
|
|
140
|
+
Sr=Sg
|
|
141
|
+
right=guess
|
|
142
|
+
else:
|
|
143
|
+
Sl=Sg
|
|
144
|
+
left=guess
|
|
145
|
+
|
|
146
|
+
if left-right==1:
|
|
147
|
+
if verbose: print("didn't hit tolerance, but guess range is 1")
|
|
148
|
+
break
|
|
149
|
+
guess = int(log_interp(1, [Sl, Sr], [left, right])//1)
|
|
150
|
+
all_l.append(guess)
|
|
151
|
+
Sg = du(guess)
|
|
152
|
+
all_s.append(Sg)
|
|
153
|
+
Ng+=1
|
|
154
|
+
if Ng>=Ntries:
|
|
155
|
+
if verbose: print(f"didn't hit tolerance of {tolerance} in {Ntries} tries")
|
|
156
|
+
if verbose: print(f"S({guess})={Sg}")
|
|
157
|
+
break
|
|
158
|
+
if return_spectrum:
|
|
159
|
+
sort = argsort(all_l)
|
|
160
|
+
return guess, array(all_l)[sort], array(all_s)[sort]
|
|
161
|
+
return guess
|
|
@@ -64,13 +64,15 @@ class VectorField:
|
|
|
64
64
|
self.caching = caching
|
|
65
65
|
# set self.components, self.ndims, self.size
|
|
66
66
|
self._parse_components(components)
|
|
67
|
+
|
|
67
68
|
def __len__(self) -> int: return min([len(c) for c in self.components])
|
|
69
|
+
|
|
68
70
|
def __abs__(self) -> NDArray | ScalarField:
|
|
69
71
|
if hasattr(self, 'magnitude'):
|
|
70
|
-
return self
|
|
71
|
-
if (p:=self.path / "Intensity")
|
|
72
|
+
return getattr(self, 'magnitude')
|
|
73
|
+
if type(p:=self.path / "Intensity")==Folder:
|
|
72
74
|
self.magnitude = ScalarField(
|
|
73
|
-
p,
|
|
75
|
+
p,
|
|
74
76
|
name=self.name+'_magnitude',
|
|
75
77
|
latex=f"$|{self.latex}|$",
|
|
76
78
|
parent=self.parent,
|
|
@@ -79,8 +81,9 @@ class VectorField:
|
|
|
79
81
|
debug=self.debug
|
|
80
82
|
)
|
|
81
83
|
return self.magnitude
|
|
84
|
+
self.magnitude = _vector_mag(self)
|
|
85
|
+
return self.magnitude
|
|
82
86
|
|
|
83
|
-
return _vector_mag(self)
|
|
84
87
|
def __getitem__(self, item: int|slice) -> NDArray:
|
|
85
88
|
match type(item):
|
|
86
89
|
case int(): return array([c[item] for c in self.components])
|
|
@@ -97,6 +100,7 @@ class VectorField:
|
|
|
97
100
|
c[i] for c in self.components
|
|
98
101
|
] for i in item_iters
|
|
99
102
|
])
|
|
103
|
+
|
|
100
104
|
def _parse_components(self, source: tuple) -> None:
|
|
101
105
|
"""
|
|
102
106
|
Parse user input components into class attributes
|
|
@@ -153,6 +157,7 @@ class VectorField:
|
|
|
153
157
|
for j in range(other.shape[1])]
|
|
154
158
|
for k in verbose_bar(range(len(other)), self.verbose, desc="constructing B...")])
|
|
155
159
|
return sum(A * B, axis=2)
|
|
160
|
+
|
|
156
161
|
def cross(self, other: Self, k:int) -> NDArray:
|
|
157
162
|
if type(other)==VectorField:
|
|
158
163
|
assert self.ndims==3, "only 3D vector fields can be crossed at this time"
|
|
@@ -162,11 +167,13 @@ class VectorField:
|
|
|
162
167
|
self.z[k]*other.x[k] - self.x[k]*other.z[k],
|
|
163
168
|
self.x[k]*other.y[k] - self.y[k]*other.x[k]
|
|
164
169
|
])
|
|
170
|
+
|
|
165
171
|
@cached_property
|
|
166
172
|
def potential(self) -> NDArray:
|
|
167
173
|
match len(self.x.shape):
|
|
168
174
|
case 2:
|
|
169
175
|
return array([Az(self.x[i], self.y[i], dx=self.dx, dy=self.dy) for i in range(len(self))])
|
|
176
|
+
|
|
170
177
|
def calc_perp(self, item=None) -> NDArray:
|
|
171
178
|
if not item:
|
|
172
179
|
self.perp = array([
|
|
@@ -185,6 +192,7 @@ class VectorField:
|
|
|
185
192
|
]
|
|
186
193
|
self.perp = array([hypot(self.perpendicular[0][j], self.perpendicular[1][j]) for j in item_iters])
|
|
187
194
|
else: raise TypeError(f"calc_perp only takes ints, slices, or None for item, not {type(item)}-type objects")
|
|
195
|
+
|
|
188
196
|
def set_parallel(self, component:str) -> None:
|
|
189
197
|
match component.lower():
|
|
190
198
|
case 'x':
|
|
@@ -196,6 +204,7 @@ class VectorField:
|
|
|
196
204
|
case 'z':
|
|
197
205
|
self.parallel = None if not hasattr(self, 'z') else self.z
|
|
198
206
|
self.perpendicular = self.x, self.y
|
|
207
|
+
|
|
199
208
|
def movie(self, mode='mag', norm='none', cmap=default_cmap, **kwrg) -> None:
|
|
200
209
|
match mode.lower():
|
|
201
210
|
case 'mag'|'magnitude'|'abs':
|
|
@@ -214,6 +223,7 @@ class VectorField:
|
|
|
214
223
|
)
|
|
215
224
|
def reveal_thyself(s, **kwargs): return array([self.parallel[i] for i in range(len(self))])
|
|
216
225
|
reveal_thyself(self if self.parent is None else self.parent, **kwrg)
|
|
226
|
+
|
|
217
227
|
def quiver(
|
|
218
228
|
self,
|
|
219
229
|
ind: int,
|
|
@@ -225,7 +235,7 @@ class VectorField:
|
|
|
225
235
|
transpose: bool = False,
|
|
226
236
|
#everything else goes into matplotlib command
|
|
227
237
|
**kwargs
|
|
228
|
-
|
|
238
|
+
) -> None:
|
|
229
239
|
if not ax: ax = gca()
|
|
230
240
|
match x, y:
|
|
231
241
|
case None, None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|