-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsmall_functions.py
More file actions
216 lines (163 loc) · 6.75 KB
/
small_functions.py
File metadata and controls
216 lines (163 loc) · 6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Module for tiny helper functions, such as path-string manipulation.
small helpful functions
FUNCTIONS:
is_nondigit(path): False if path tail contains digits
timestep_in(path): true if 'timestep' is in path tail
check_simdir(path): param dict based on valid path (falsy {} if invalid)
a_in_b_dict(a, b): True if a[key] in b[key] for all b.keys
Created on Thu Aug 12 14:28:37 2021
@author: yoav
"""
import os
import re
import numpy as np
def is_nondigit(path) -> bool:
"""Return True if all characters in path tail are not digit."""
return not any(x.isdigit() for x in os.path.split(path)[-1])
def timestep_in(path) -> bool:
"""Return True if path tail contains "timestep"."""
return 'timestep' in os.path.split(path)[-1]
_pattern_vsd=re.compile('[^\W\d_]+|\d+') # from https://stackoverflow.com/questions/12409894
def valid_simulation_dict(path) -> dict:
"""Check path as a valid simulation directory, return {param: index}.
Returns a dictionary of {parameter: index} if valid, else a falsy {}.
A directory is a valid simulation directory if it is of the form
ab12c3... producing the dictionary {'ab': 12, 'c': 3, ...}
or of the form x####_ab12c3...
where the x#### is the simulation number which is skipped
"""
# validate parameter section by constructing parameter dictionary
# split to numeric-alpha words ['f','102','blah',987]
wordlist=_pattern_vsd.findall(os.path.split(os.path.realpath(path))[-1])
if wordlist and wordlist[0]=='x':
# Skip initial x:num (simulation number)
wordlist = wordlist[2:]
try:
params = {key: int(value_word) for key, value_word in
zip(wordlist[::2], wordlist[1::2])}
except (ValueError, TypeError):
# unable to construct dictionary
return {}
return params
def compile_tape_regex_option(option_list):
"""Return a regex pattern that can extract the option in option_list from a tape file using pattern.findall(tape)"""
if option_list is None or option_list is all:
return re.compile("^([\w\d]*)=(.*)",re.MULTILINE)
if type(option_list) is str:
re.compile(f"^({option_list})=(.*)",re.MULTILINE)
return re.compile(f"^({'|'.join(map(str,option_list))})=(.*)",re.MULTILINE)
def _string_to_value(value):
"""Conert string to value, int first, then double"""
try:
return int(value)
except ValueError:
try:
return float(value)
except ValueError:
return value
_pattern_tape_all=compile_tape_regex_option(None)
def get_tape_options(tape):
"""Extract tape as text to a dictionary of all options"""
options = _pattern_tape_all.findall(tape)
return {option: _string_to_value(value) for option,value in options}
_pattern_tape_adhesion=compile_tape_regex_option(['z_adhesion','adhesion_z','adhesion_cuttoff','adhesion_cutoff'])
def extract_tape_adhesion_plane(tape):
"""Get the top of the adhesion plane from tape (z0+cutoff).
This means vertex position below this value are in adhesion.
"""
z_adh, z_cutoff = _pattern_tape_adhesion.findall(tape)
return float(z_adh[1])+float(z_cutoff[1])
_pattern_tape_adhesion_2=compile_tape_regex_option(['z_adhesion','adhesion_z','adhesion_cuttoff','adhesion_cutoff','adhesion_radius'])
def from_tape_is_adhered_to_sphere(tape, pos):
"""Get the top of the adhesion sphere from tape (r+cutoff).
Returns boolean array from pos
"""
cutoff, radius, z0 = sorted(_pattern_tape_adhesion_2.findall(tape), key=lambda x: x[0])
return ((pos-[[0,0,float(z0[1])-float(radius[1])]])**2).sum(axis=-1)<(float(cutoff[1])+float(radius[1]))**2
def a_in_b_dict(a: dict, b: dict) -> bool:
"""Return True if for all keys a[key] is in b[key]."""
return all(a[key] in b[key] for key in b.keys())
def np_index(arr: np.ndarray, val):
"""Numpy array equivalent to a list's [].index method."""
return np.where(arr == val)[0][0]
def subspace_indices(sub_space, param_idx, keys):
"""Get indice of {'a': 2} in {'a': [1,2,3]}."""
return [np_index(sub_space[key], param_idx[key]) for key in keys]
def str_to_slice(string: str) -> slice:
"""Convert string representation to slice."""
if ':' not in string:
x = int(string)
if x == -1:
return slice(x, None)
return slice(x, x+1)
return slice(*map(lambda x: int(x.strip()) if x.strip() else None,
string.split(':')))
def _int_slice_to_slice(s):
"""Convert to slice: internal function for getitem."""
if type(s) is int:
if s == -1:
return slice(-1, None)
return slice(s, s+1)
return s
class _SliceGetter():
"""Slice()[::2] -> slice(None,None,2)."""
def __getitem__(self, s):
"""Return slice generated by [::]."""
if type(s) is tuple:
return tuple(_int_slice_to_slice(x) for x in s)
return _int_slice_to_slice(s)
Slice = _SliceGetter() # global to export: Slice[::2]
class _RangeGetter():
"""Range(5)[::2] -> range(5)[::2]."""
def __init__(self, N=None):
self.N = N
def slice_to_range(self, s):
if self.N is not None:
return range(self.N)[s]
else:
a, b, c = s.start, s.step, s.stop
b = b if b is not None else 1
a = a if a is not None else 0 if b > 0 else -1
c = c if c is not None else a+b+1 if b is not None else a+1
return range(a, c, b)
def __getitem__(self, s):
"""Return slice generated by [::]."""
if type(s) is tuple:
return tuple(self.slice_to_range(_int_slice_to_slice(x)) for x in s)
return self.slice_to_range(_int_slice_to_slice(s))
def __call__(self, N):
return _RangeGetter(N)
Range = _RangeGetter()
def len_slice_max(s: slice) -> int:
"""Get max length of slice.
If possibly inf, return None.
"""
start = s.start if s.start is not None else 0
step = s.step if s.step is not None else 1
stop = s.stop
if start < 0:
if stop is None:
return (-start)//step
elif stop > 0:
return max((-start)//step, stop//step)
elif stop < 0:
if stop > start:
return (stop-start)//step
else:
return 0
else: # start>=0
if stop is not None and stop > start:
return (stop-start)//step
return None
def upmask(mask1, mask2):
"""Given A[mask1]=B and B[mask2]=c, return mask3 such that A[mask3]=c"""
m = mask1.copy()
m[mask1] = mask2
return m
def normalize_axis(vectors, axis=-1):
"""Take an array, normalize the given axis e.g. A/norm(A,axis)"""
n = np.linalg.norm(vectors, axis=axis, keepdims=True)
n[n == 0] = 1
return vectors/n