Coverage for /usr/local/lib/python3.11/dist-packages/pyrocko/pz.py: 60%
126 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-06 06:59 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-10-06 06:59 +0000
1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6'''
7Reader for `SAC
8<http://ds.iris.edu/ds/nodes/dmc/software/downloads/sac/>`_ pole-zero files.
10See also :py:mod:`~pyrocko.io.enhanced_sacpz`.
11'''
13import math
14import numpy as num
15try:
16 from StringIO import StringIO as BytesIO
17except ImportError:
18 from io import BytesIO
20from pyrocko import trace
22d2r = math.pi/180.
25class SacPoleZeroError(Exception):
26 pass
29def read_sac_zpk(filename=None, file=None, string=None, get_comments=False):
30 '''
31 Read SAC pole-zero file.
33 :returns: ``(zeros, poles, constant)`` or
34 ``(zeros, poles, constant, comments)`` if ``get_comments`` is True.
35 '''
37 if filename is not None:
38 f = open(filename, 'rb')
40 elif file is not None:
41 f = file
43 elif string is not None:
44 f = BytesIO(string)
46 sects = ('ZEROS', 'POLES', 'CONSTANT')
47 sectdata = {'ZEROS': [], 'POLES': []}
48 npoles = 0
49 nzeros = 0
50 constant = 1.0
51 atsect = None
52 comments = []
53 for iline, line in enumerate(f):
54 line = str(line.decode('ascii'))
55 toks = line.split()
56 if len(toks) == 0:
57 continue
59 if toks[0][0] in '*#':
60 comments.append(line)
61 continue
63 if len(toks) != 2:
64 f.close()
65 raise SacPoleZeroError(
66 'Expected 2 tokens in line %i of file %s'
67 % (iline+1, filename))
69 if toks[0].startswith('*'):
70 continue
72 lsect = toks[0].upper()
73 if lsect in sects:
74 atsect = lsect
75 sectdata[atsect] = []
76 if lsect.upper() == 'ZEROS':
77 nzeros = int(toks[1])
78 elif toks[0].upper() == 'POLES':
79 npoles = int(toks[1])
80 elif toks[0].upper() == 'CONSTANT':
81 constant = float(toks[1])
82 else:
83 if atsect:
84 sectdata[atsect].append(
85 complex(float(toks[0]), float(toks[1])))
87 if f != file:
88 f.close()
90 poles = sectdata['POLES']
91 zeros = sectdata['ZEROS']
92 npoles_ = len(poles)
93 nzeros_ = len(zeros)
94 if npoles_ > npoles:
95 raise SacPoleZeroError(
96 'Expected %i poles but found %i in pole-zero file "%s"'
97 % (npoles, npoles_, filename))
98 if nzeros_ > nzeros:
99 raise SacPoleZeroError(
100 'Expected %i zeros but found %i in pole-zero file "%s"'
101 % (nzeros, nzeros_, filename))
103 if npoles_ < npoles:
104 poles.extend([complex(0.)]*(npoles-npoles_))
106 if nzeros_ < npoles:
107 zeros.extend([complex(0.)]*(nzeros-nzeros_))
109 if len(poles) == 0 and len(zeros) == 0:
110 raise SacPoleZeroError(
111 'No poles and zeros found in file "%s"' % (filename))
113 if not num.all(num.isfinite(poles)):
114 raise SacPoleZeroError(
115 'Not finite pole(s) found in pole-zero file "%s"'
116 % filename)
118 if not num.all(num.isfinite(zeros)):
119 raise SacPoleZeroError(
120 'Not finite zero(s) found in pole-zero file "%s"'
121 % filename)
123 if not num.isfinite(constant):
124 raise SacPoleZeroError(
125 'Ivalid constant (%g) found in pole-zero file "%s"'
126 % (constant, filename))
128 if get_comments:
129 return zeros, poles, constant, comments
130 else:
131 return zeros, poles, constant
134def write_sac_zpk(zeros, poles, constant, filename):
135 if hasattr(filename, 'write'):
136 f = filename
137 else:
138 f = open('w', filename)
140 def write_complex(x):
141 f.write('%12.8g %12.8g\n' % (complex(x).real, complex(x).imag))
143 f.write('POLES %i\n' % len(poles))
144 for p in poles:
145 if p != 0.0:
146 write_complex(p)
148 f.write('ZEROS %i\n' % len(zeros))
149 for z in zeros:
150 if z != 0.0:
151 write_complex(z)
153 f.write('CONSTANT %12.8g\n' % constant)
154 if not hasattr(filename, 'write'):
155 f.close()
158def evaluate(zeros, poles, constant, fmin=0.001, fmax=100., nf=100):
160 logfmin = math.log(fmin)
161 logfmax = math.log(fmax)
162 logf = num.linspace(logfmin, logfmax, nf)
163 f = num.exp(logf)
164 trans = trace.PoleZeroResponse(zeros, poles, constant)
165 return f, trans.evaluate(f)
168def evaluate_at(zeros, poles, constant, f):
169 jomeg = 1.0j * 2. * math.pi * f
171 a = constant
172 for z in zeros:
173 a *= jomeg-z
174 for p in poles:
175 a /= jomeg-p
177 return a
180def read_to_pyrocko_response(filename=None, file=None, string=None):
181 '''
182 Read SAC pole-zero file into Pyrocko response object.
184 :returns: Response as a :py:class:~pyrocko.trace.PoleZeroResponse` object.
185 '''
187 from pyrocko import trace
189 zeros, poles, constant = read_sac_zpk(
190 filename=filename, file=file, string=string)
191 return trace.PoleZeroResponse(zeros, poles, constant)
194def read_to_stationxml_response(
195 input_unit, output_unit, normalization_frequency=1.0,
196 filename=None, file=None, string=None):
197 '''
198 Read SAC pole-zero file into StationXML response object.
200 :param input_unit: Input unit to be reported in the StationXML response.
201 :type input_unit: str
202 :param output_unit: Output unit to be reported in the StationXML response.
203 :type output_unit: str
204 :param normalization_frequency: Frequency where the normalization factor
205 for the StationXML response should be computed.
206 :type normalization_frequency: float
208 :returns: Response as a :py:class:~pyrocko.io.stationxml.Response` object
209 with a single pole-zero response stage.
210 '''
212 from pyrocko.io import stationxml
214 presponse = read_to_pyrocko_response(
215 filename=filename, file=file, string=string)
217 return stationxml.Response.from_pyrocko_pz_response(
218 presponse, input_unit, output_unit, normalization_frequency)
221def plot_amplitudes_zpk(
222 zpks, filename_pdf,
223 fmin=0.001,
224 fmax=100.,
225 nf=100,
226 fnorm=None):
228 from pyrocko.plot import gmtpy
230 p = gmtpy.LogLogPlot(width=30*gmtpy.cm, yexp=0)
231 for i, (zeros, poles, constant) in enumerate(zpks):
232 f, h = evaluate(zeros, poles, constant, fmin, fmax, nf)
233 if fnorm is not None:
234 h /= evaluate_at(zeros, poles, constant, fnorm)
236 amp = num.abs(h)
237 p.plot((f, amp), '-W2p,%s' % gmtpy.color(i))
239 p.save(filename_pdf)
242def plot_phases_zpk(zpks, filename_pdf, fmin=0.001, fmax=100., nf=100):
244 from pyrocko.plot import gmtpy
246 p = gmtpy.LogLinPlot(width=30*gmtpy.cm)
247 for i, (zeros, poles, constant) in enumerate(zpks):
248 f, h = evaluate(zeros, poles, constant, fmin, fmax, nf)
249 phase = num.unwrap(num.angle(h)) / d2r
250 p.plot((f, phase), '-W1p,%s' % gmtpy.color(i))
252 p.save(filename_pdf)