xsection 0.0.0__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.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.1
2
+ Name: xsection
3
+ Version: 0.0.0
4
+ Author-email: "Claudio M. Perez" <50180406+claudioperez@users.noreply.github.com>
5
+ Project-URL: repository, http://github.com/BRACE2/xsection
6
+ Project-URL: documentation, https://brace2.github.io/xsection
7
+ Keywords: seismic,earthquake
8
+ Description-Content-Type: text/markdown
9
+
10
+ # `xsection`
11
+
@@ -0,0 +1,2 @@
1
+ # `xsection`
2
+
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "xsection"
3
+ version = "0.0.0"
4
+ authors = [
5
+ {name="Claudio M. Perez", email="50180406+claudioperez@users.noreply.github.com"}
6
+ ]
7
+
8
+ readme = "README.md"
9
+
10
+ keywords = [
11
+ "seismic",
12
+ "earthquake",
13
+ ]
14
+
15
+ [options]
16
+ #packages = find
17
+ install_requires = [
18
+ "numpy",
19
+ "scipy",
20
+ "tqdm",
21
+ "quakeio",
22
+ "matplotlib",
23
+ "plotly",
24
+ "pandas",
25
+ "control",
26
+ "sdof",
27
+ "opensees",
28
+ "quakeio"
29
+ ]
30
+
31
+ [project.urls]
32
+ repository = "http://github.com/BRACE2/xsection"
33
+ documentation = "https://brace2.github.io/xsection"
34
+
35
+ [project.scripts]
36
+ xsection = "xsection.__main__:main"
37
+
38
+ [build-system]
39
+ requires = [
40
+ "setuptools >= 52.0.2",
41
+ ]
42
+
43
+ build-backend = "setuptools.build_meta"
44
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env python
2
+ from setuptools import setup
3
+
4
+ if __name__ == "__main__":
5
+ setup(
6
+ # name="opensees",
7
+ # cmake_install_dir="src/opensees",
8
+ # cmake_args = ["-DDependencies=Conda"],
9
+ # package_dir = {"": "src"},
10
+ # packages = ["opensees", "opensees.emit", "opensees.units"],
11
+ # #packages = find_packages("src"),
12
+ # py_modules = [
13
+ # splitext(basename(path))[0] for path in glob("src/*.py")
14
+ # ] + [
15
+ # splitext(basename(path))[0] for path in glob("src/units/*.py")
16
+ # ] + [
17
+ # splitext(basename(path))[0] for path in glob("src/emit/*.py")
18
+ # ]
19
+ )
20
+
@@ -0,0 +1,131 @@
1
+ from cmath import isclose
2
+ from numpy import pi, log, sign
3
+ from numpy.linalg import eig
4
+ import numpy as np
5
+ import scipy.linalg as sl
6
+
7
+ def condeig(a): # TODO: make this match matlab source code for condeig
8
+ """
9
+ vals, vecs, conds = condeig(A) Computes condition numbers for the
10
+ eigenvalues of a matrix. The condition numbers are the reciprocals
11
+ of the cosines of the angles between the left and right eigenvectors.
12
+ Inspired by Arno Onken's Octave code for condeig.
13
+
14
+ https://github.com/macd/rogues/blob/master/rogues/utils/condeig.py
15
+ """
16
+ m, n = a.shape
17
+ # eigenvalues, left and right eigenvectors
18
+ lamr, vl, vr = sl.eig(a, left=True, right=True)
19
+ vl = vl.T.conj()
20
+ # Normalize vectors
21
+ for i in range(n):
22
+ vl[i, :] = vl[i, :] / np.sqrt(abs(vl[i, :] ** 2).sum())
23
+ # Condition numbers are reciprocal of the cosines (dot products) of the
24
+ # left eignevectors with the right eigenvectors.
25
+ c = abs(1 / np.diag(np.dot(vl, vr)))
26
+ return vr, lamr, c
27
+
28
+ def ComposeModes(dt, A, B, C, D, debug=False, **kwds)->dict:
29
+
30
+ n = A.shape[0]
31
+ m = C.shape[0]
32
+
33
+ v, d, cnd = condeig(A) # eigenvectors (d) & eiegenvalues (v) of the matrix A
34
+ # kit = np.log(d) # logarithm of the eigenvalues
35
+
36
+ # a) Determination of modal frequencies (Eqs. 3.46 & 3.39)
37
+ sj1 = np.log(d)/dt # dt is the time step
38
+ freq1 = (np.real(sj1*np.conj(sj1))**0.5)/(2*pi)
39
+
40
+ roots = []
41
+
42
+ # selection of proper roots
43
+ if np.isclose(freq1[0], freq1[1]):
44
+ roots.append(0)
45
+
46
+ if np.isclose(freq1[n-1], freq1[n-2]):
47
+ roots.append(n-1)
48
+
49
+ for i in range(2, n-2):
50
+ if np.isclose(freq1[i], freq1[i+1]) or np.isclose(freq1[i], freq1[i-1]):
51
+ roots.append(i)
52
+
53
+
54
+ # b) Determination of damping ratios (Eqs. 3.46 & 3.39)
55
+ damp1 = -((np.real(sj1))/(2*pi*freq1))
56
+
57
+ # Represent the identified frequency & damping information
58
+ # of the proper roots in a matrix
59
+
60
+ # NOTE: These values are stored in one array like this for legacy reasons,
61
+ # but this should be cleaned and a proper data structure should be used!
62
+ freqdmp = np.array([
63
+ [freq1[i], # first column: identified frequency
64
+ damp1[i], # second column: identified damping ratio
65
+ cnd[i]] # condition number of the eigenvalue
66
+ for i in roots
67
+ ])
68
+
69
+ # c) Determination of mode shapes
70
+ modes_raw = C@v # mode shapes (Eq. 3.40)
71
+
72
+ modeshape = np.zeros((m,len(roots)), dtype=complex)
73
+
74
+ # extract mode shapes from mod corresponding to a frequency
75
+ for q,root in enumerate(roots):
76
+ modeshape[:m,q] = modes_raw[:m, root]
77
+
78
+ for q,root in enumerate(roots):
79
+ om = np.argmax(abs(np.real(modeshape[:,q])))
80
+ mx = abs(np.real(modeshape[om,q]))
81
+ modeshape[:,q] = np.real(modeshape[:,q])/mx*sign(np.real(modeshape[om,q]))
82
+
83
+ if debug:
84
+ return locals()
85
+ return freqdmp, modeshape, None, v, d
86
+
87
+ def modes(dt, A, C):
88
+
89
+ # eigendecomp A
90
+ Psi,Gam,cnd = condeig(A) # eigenvectors (Psi) & eigenvalues (Gam) of the matrix A
91
+
92
+ # get damping and frequencies from eigendecomp of A
93
+ Lam = (np.log(Gam))/dt
94
+ Omega = np.real((Lam*np.conj(Lam))**0.5) # radians per second. taking the real part because np keeps a +0j.
95
+ freq = Omega/(2*pi) # cycles per second (Hz)
96
+ damp = -np.real(Lam)/Omega
97
+
98
+ # get modeshapes from C and eigendecomp of A
99
+ modeshape = C @ Psi
100
+
101
+ # nroots = int(len(freq)/2)
102
+ # # print(f"{freq=}")
103
+ # for i in range(nroots):
104
+ # assert np.isclose(freq[2*i],freq[2*i+1]) # make sure we have pairs of roots
105
+
106
+ # modes = {str(i):
107
+ # {'cnd': cnd[2*i], # condition number of the eigenvalue
108
+ # 'freq': freq[2*i], # identified frequency
109
+ # 'damp': damp[2*i], # identified damping ratio
110
+ # 'modeshape': modeshape[:,2*i]
111
+ # }
112
+ # for i in range(nroots)
113
+ # }
114
+
115
+ # return modes
116
+
117
+ # weed out unique roots: get indices of roots that only show up once, and
118
+ # the index of the first of each pair.
119
+ _, notroots = np.unique(freq.round(decimals=5), return_index=True)
120
+
121
+ # print(notroots)
122
+ modes = {str(i):
123
+ {'cnd': cnd[i], # condition number of the eigenvalue
124
+ 'freq': freq[i], # identified frequency
125
+ 'damp': damp[i], # identified damping ratio
126
+ 'modeshape': modeshape[:,i]
127
+ }
128
+ for i in range(len(freq)) if i not in notroots
129
+ }
130
+
131
+ return modes
@@ -0,0 +1 @@
1
+ from .system import system
@@ -0,0 +1,190 @@
1
+ import sys
2
+ import json
3
+
4
+ import ssid
5
+ import ssid.modes
6
+ import quakeio
7
+ import numpy as np
8
+ from .okid import parse_okid
9
+
10
+ HELP = """
11
+ ssid [-p|w <>...] <method> <event> <inputs> <outputs>
12
+ ssid [-p|w <>...] <method> -i <inputs> -o <outputs>
13
+
14
+ -i/--inputs FILE... Input data
15
+ -o/--outputs FILE... Output data
16
+
17
+ Methods:
18
+ <method> <outputs> <plots>
19
+ srim {dftmcs} {m}
20
+ okid {dftmcs} {m}
21
+ spec {ft} {a}
22
+ four {ft} {a}
23
+ test
24
+
25
+ Outputs options
26
+ -p/--plot
27
+ a/--accel-spect
28
+ -w/--write
29
+ ABCD system matrices
30
+ d damping
31
+ freq frequency
32
+ cycl cyclic frequency
33
+ t period
34
+ m modes
35
+ c condition-number
36
+ """
37
+
38
+ class JSON_Encoder(json.JSONEncoder):
39
+ def default(self, obj):
40
+ if isinstance(obj, np.ndarray):
41
+ return obj.tolist()
42
+ elif isinstance(obj, np.integer):
43
+ return int(obj)
44
+ elif isinstance(obj, np.floating):
45
+ return float(obj)
46
+ return json.JSONEncoder.default(self, obj)
47
+
48
+
49
+ def parse_srim(argi, config):
50
+ help="""
51
+ SRIM -- System Identification with Information Matrix
52
+
53
+ Parameters
54
+ no order of the observer Kalman ARX filter (formerly p).
55
+ r size of the state-space model used for
56
+ representing the system. (formerly orm/n)
57
+ """
58
+ config.update({"p" : 5, "orm": 4})
59
+
60
+ #argi = iter(args)
61
+ channels = [[], []]
62
+ for arg in argi:
63
+ if arg == "--arx-order":
64
+ config["no"] = int(next(argi))
65
+
66
+ elif arg == "--dt":
67
+ config["dt"] = float(next(argi))
68
+
69
+ elif arg == "--ss-size":
70
+ config["r"] = int(next(argi))
71
+
72
+ elif arg in ["--help", "-h"]:
73
+ print(help)
74
+ sys.exit()
75
+
76
+ elif arg == "--inputs":
77
+ inputs = next(argi)[1:-1].split(",")
78
+ if isinstance(inputs, str):
79
+ channels[0] = [int(inputs)]
80
+ else:
81
+ channels[0] = list(map(int, inputs))
82
+
83
+ elif arg == "--outputs":
84
+ outputs = next(argi)[1:-1].split(",")
85
+ if isinstance(outputs, str):
86
+ channels[1] = [int(outputs)]
87
+ else:
88
+ channels[1] = list(map(int, outputs))
89
+
90
+ elif arg == "--":
91
+ continue
92
+
93
+ else:
94
+ config["event_file"] = arg
95
+
96
+ event = quakeio.read(config["event_file"])
97
+ inputs = np.array([
98
+ event.match("l", station_channel=f"{i}").accel.data for i in channels[0]
99
+ ]).T
100
+ outputs = np.array([
101
+ event.match("l", station_channel=f"{i}").accel.data for i in channels[1]
102
+ #event.at(file_name=f"CHAN{i:03d}.V2").accel.data for i in channels[1]
103
+ ]).T
104
+
105
+ dt = event.at(station_channel=f"{channels[0][0]}").accel["time_step"]
106
+ config["dt"] = dt
107
+
108
+ A,B,C,D = ssid.system(input=inputs, output=outputs, full=True, **config)
109
+
110
+ ss_modes = ssid.modes.modes((A,B,C,D),dt)
111
+
112
+ output = [
113
+ {
114
+ "period": 1/mode["freq"],
115
+ "frequency": mode["freq"],
116
+ "damping": mode["damp"]
117
+ }
118
+ for mode in sorted(ss_modes.values(), key=lambda item: item["freq"])
119
+ ]
120
+
121
+ print(json.dumps(output, cls=JSON_Encoder, indent=4))
122
+ return config
123
+
124
+
125
+ def parse_args(args):
126
+ outputs = []
127
+ sub_parsers = {
128
+ "srim": parse_srim,
129
+ "test": parse_srim,
130
+ "okid": parse_okid,
131
+ "okid": parse_okid
132
+ }
133
+ config = {
134
+ "method": None,
135
+ "operation": None,
136
+ "protocol": None
137
+ }
138
+ config["channels"] = channels = [[], []]
139
+
140
+ argi = iter(args[1:])
141
+ for arg in argi:
142
+ if arg == "-p":
143
+ outputs.append(next(arg))
144
+
145
+ elif arg in ["--help", "-h"]:
146
+ print(HELP)
147
+ sys.exit()
148
+
149
+ elif arg == "--protocol":
150
+ config["protocol"] = next(arg)
151
+
152
+ elif arg == "--inputs":
153
+ inputs = next(argi)[1:-1].split(",")
154
+ if isinstance(inputs, str):
155
+ channels[0] = [int(inputs)]
156
+ else:
157
+ channels[0] = list(map(int, inputs))
158
+
159
+ elif arg == "--outputs":
160
+ outputs = next(argi)[1:-1].split(",")
161
+ if isinstance(outputs, str):
162
+ channels[1] = [int(outputs)]
163
+ else:
164
+ channels[1] = list(map(int, outputs))
165
+
166
+ # Positional args
167
+ elif config["method"] is None:
168
+ config["method"] = arg
169
+ break
170
+
171
+ # elif config["protocol"] and config["operation"] is None:
172
+ # config["operation"] = arg
173
+
174
+ elif arg == "--":
175
+ continue
176
+
177
+ else:
178
+ print(HELP)
179
+ sys.exit()
180
+
181
+
182
+ return sub_parsers[config.pop("method")](argi, config), outputs
183
+
184
+
185
+ def main():
186
+ parse_args(sys.argv)
187
+
188
+ if __name__ == "__main__":
189
+ main()
190
+ sys.exit()
@@ -0,0 +1,109 @@
1
+ from .srim import parse_srim
2
+ from .okid import parse_okid
3
+
4
+ HELP = """
5
+ ssid [-p|w <>...] <method> <event> <inputs> <outputs>
6
+ ssid [-p|w <>...] <method> -i <inputs> -o <outputs>
7
+
8
+ -i/--inputs FILE... Input data
9
+ -o/--outputs FILE... Output data
10
+
11
+ Methods:
12
+ <method> <outputs> <plots>
13
+ srim {dftmcs} {m}
14
+ okid {dftmcs} {m}
15
+ spec {ft} {a}
16
+ four {ft} {a}
17
+ test
18
+
19
+ Outputs options
20
+ -p/--plot
21
+ a/--accel-spect
22
+ -w/--write
23
+ ABCD system matrices
24
+ d damping
25
+ freq frequency
26
+ cycl cyclic frequency
27
+ t period
28
+ m modes
29
+ c condition-number
30
+ """
31
+
32
+
33
+ def parse_args(args):
34
+ outputs = []
35
+ parsers = {
36
+ "srim": parse_srim,
37
+ "test": parse_srim,
38
+ "okid": parse_okid
39
+ }
40
+ config = {}
41
+ argi = iter(args[1:])
42
+ for arg in argi:
43
+ if arg == "-p":
44
+ outputs.append(next(arg))
45
+ if arg == "--setup":
46
+ install_me()
47
+ sys.exit()
48
+ elif arg in ["--help", "-h"]:
49
+ print(HELP)
50
+ sys.exit()
51
+ else:
52
+ config["method"] = arg
53
+ return parsers[arg](argi, config), outputs
54
+
55
+ if __name__ == "__main__":
56
+ import sys
57
+ import quakeio
58
+ import numpy as np
59
+ from pathlib import Path
60
+ channels = [[17, 3, 20], [9, 7, 4]]
61
+ method = None
62
+
63
+ config, out_ops = parse_args(sys.argv)
64
+
65
+ if config["method"] == "test":
66
+ data_dir = Path("RioDell_Petrolia_Processed_Data")
67
+
68
+ first_input = quakeio.read(data_dir/f"CHAN{channels[0][0]:03d}.V2")
69
+ npoints = len(first_input.accel.data)
70
+ inputs, outputs = np.zeros((2,npoints,len(channels[0])))
71
+
72
+ # Inputs
73
+ inputs[:,0] = first_input.accel.data
74
+ for i,inp in enumerate(channels[0][1:]):
75
+ inputs[:,i+1] = quakeio.read(data_dir/f"CHAN{inp:03d}.V2").accel.data
76
+
77
+ # Outputs
78
+ for i,inp in enumerate(channels[1]):
79
+ outputs[:,i] = quakeio.read(data_dir/f"CHAN{inp:03d}.V2").accel.data
80
+
81
+ dt = first_input.accel["time_step"]
82
+ config["dt"] = dt
83
+
84
+ elif "event_file" in config:
85
+ event = quakeio.read(config["event_file"])
86
+ inputs = np.array([
87
+ event.at(file_name=f"CHAN{i:03d}.V2").accel.data for i in channels[0]
88
+ ]).T
89
+ outputs = np.array([
90
+ event.at(file_name=f"CHAN{i:03d}.V2").accel.data for i in channels[1]
91
+ ]).T
92
+ npoints = len(inputs[:,0])
93
+ dt = event.at(file_name=f"CHAN{channels[0][0]:03d}.V2").accel["time_step"]
94
+ config["dt"] = dt
95
+
96
+ #print(config)
97
+
98
+ A,B,C,D = srim(inputs, outputs, **config)
99
+
100
+ freqdmpSRIM, modeshapeSRIM, *_ = ComposeModes(dt, A, B, C, D)
101
+
102
+ if not out_ops:
103
+ print(f"period: {np.real(1/freqdmpSRIM[:,0])}")
104
+ elif "freq" in out_ops:
105
+ print(f"frequency: {freqdmpSRIM[:,0]}")
106
+ elif "cycl" in out_ops:
107
+ print(f"cyclic_frequency: {2*np.pi*freqdmpSRIM[:,0]}")
108
+
109
+