pygtide 0.8.2__cp314-cp314-win_amd64.whl

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.
pygtide/core.py ADDED
@@ -0,0 +1,539 @@
1
+ """
2
+ PyGTide - A python class to calculate time series of the gravitational tides on Earth
3
+ -------------------------------------------------------------------------------
4
+ Author: Gabriel C. Rau (gabriel@hydrogeo.science)
5
+ Website: https://hydrogeo.science
6
+ Based on:
7
+ ETERNA 3.4 PREDICT (by Prof. Wenzel, 1996)
8
+ Updated updated by Kudryevtsev (2004)
9
+
10
+ Publications:
11
+ Wenzel, H.-G. The nanogal software: Earth tide data processing package ETERNA 3.30,
12
+ Bull. Inf. Marées Terrestres (1996) 124, 9425–9439.
13
+ Kudryavtsev, S. Journal of Geodesy (2004) 77: 829. https://doi.org/10.1007/s00190-003-0361-2
14
+ -------------------------------------------------------------------------------
15
+ How to run:
16
+ import pygtide
17
+ pt = pygtide.pygtide()
18
+ pt.predict(latitude, longitude, height, startdate, duration, samprate, **control):
19
+ data = pt.results()
20
+
21
+ pt.results() returns:
22
+ either False, or a Pandas dataframe with the respective data.
23
+
24
+ Python status messages can be suppressed by setting: self.msg = False.
25
+ The routine relies on files in the subdirectory 'commdat'. This includes the tidal
26
+ catalogues, corrections of the pole rotation as well as leap seconds.
27
+ Wave group parameters are being read from the "self.waves.ini" file.
28
+ -------------------------------------------------------------------------------
29
+ The original code was written in Fortran 77/90 and is available for download from:
30
+ http://igets.u-strasbg.fr/soft_and_tool.php
31
+ -------------------------------------------------------------------------------
32
+ Subroutines used in the Fortran program:
33
+ --- new ---
34
+ PREDICT: New Python interface subroutine to hand over arguments and calculate
35
+ INIT: New subroutine which sets the variables in module out for access in Python.
36
+ --- existing ---
37
+ ETASTN: computes astronomical elements.
38
+ PREDIN: reads control parameters.
39
+ ETDDTA: reads tabel of DDT = TDT - UTC.
40
+ ETDDTB: interpolates DDT = TDT - UTC from table.
41
+ ETGCON: computes geodetic coefficients.
42
+ ETGREN: computes date from Julian date.
43
+ ETJULN: computes JULIAN date.
44
+ ETLEGN: computes fully normalized Legendre spherical harmonics.
45
+ ETLOVE: computes elastic parameters from Wahr-Dehant model.
46
+ ETPOLC: computes DUT1.
47
+ ETPHAS: computes the phases and frequencies of the tidal waves.
48
+ ETPOTS: computes amplitudes, frequencies and phases of tidal waves.
49
+ GEOEXT: computes JOBTIME.
50
+ ===============================================================================
51
+ Note: While Prof. Wenzel passed away, his legacy continues to live as PyGTide
52
+ -------------------------------------------------------------------------------
53
+ ####### ######## ####### ####### ## ## ####
54
+ ## ## ## ## ## ### ## ## ##
55
+ ## ## ## ## ## #### ## ## ##
56
+ ###### ## ###### ####### ## ## ## ## ##
57
+ ## ## ## ## ## ## #### ########
58
+ ## ## ## ## ## ## ### ## ##
59
+ ####### ## ####### ## ## ## ## ## ##
60
+
61
+ Prof. Dr.-Ing. Hans-Georg Wenzel
62
+ Black Forest Observatory
63
+ Universitaet Karlsruhe
64
+ Englerstr. 7
65
+ D-76128 KARLSRUHE
66
+ Germany
67
+ Phone : ++49-0721-6082307
68
+ Telefax : ++49-0721-694552
69
+ e-mail : wenzel@gik.bau-verm.uni-karlsruhe.de
70
+ ===============================================================================
71
+ This Python module was created based on the Fortran code PREDICT as part of
72
+ ETERNA 3.4, originally written by Prof. Hans George Wenzel in 1996. PREDICT is used to
73
+ calculate Earth tide gravity time series. The original PREDICT Fortran code was
74
+ updated to implement the new tidal catalogue by Kudryatvtsev (2004). The code
75
+ was then modernised (Fortran 90) for compilation as Python 3 module. This interface
76
+ provides a convenient way to utilise ETERNA PREDICT within Python.
77
+
78
+ The module relies on external files in the directory "commdat". The folowing files require
79
+ regular updating:
80
+
81
+ - etddt.dat - contains the difference between ephemeris time and UTC (include any leap seconds)
82
+ - etpolut.dat - contains the earth's pole rotation
83
+
84
+ The original Fortran code was also modified for use with f2py:
85
+ - COMMON blocks were transformed into modules
86
+ - continuous lines were updated for F90 compatibility
87
+ - the main program was changed into a subroutine (for f2py compliance)
88
+ - various other modernisations and enhancements
89
+ - BUG FIX: the original date and time data contained a rounding bug when the
90
+ sampling rate was lower than 60 seconds. This was successfully fixed.
91
+ ===============================================================================
92
+ """
93
+
94
+ import pygtide.etpred as etpred
95
+ from datetime import datetime, timedelta, date
96
+ from warnings import warn
97
+ import numpy as np
98
+ import pandas as pd
99
+ from importlib import resources
100
+ import os
101
+
102
+
103
+ class pygtide(object):
104
+ """
105
+ The PyGTide class will initialise internal variables
106
+ """
107
+
108
+ def __init__(self, msg=True):
109
+ """
110
+ pygtide.init() initialises the etpred (Fortran) module and sets global variables
111
+ """
112
+ self.msg = msg
113
+ self.fortran_version = etpred.inout.vers.astype(str)
114
+ # Resolve package data directory directly from filesystem.
115
+ # Avoid importlib.resources.as_file() which creates temp dirs that get cleaned up.
116
+ pkg_dir = os.path.dirname(__file__)
117
+ self.data_dir = os.path.join(pkg_dir, "commdat")
118
+ if not self.data_dir.endswith(os.sep):
119
+ self.data_dir += os.sep
120
+
121
+ # Fortran expects a fixed-length string (256 chars)
122
+ etpred.params.comdir = self.data_dir + " " * (256 - len(self.data_dir))
123
+
124
+ # OS-dependent null file
125
+ etpred.params.nullfile = os.devnull + " " * (10 - len(os.devnull))
126
+
127
+ self.args = []
128
+
129
+ # Initialise Fortran module
130
+ etpred.init()
131
+
132
+ # capture end date of file "etddt.dat" from module
133
+ year = int(etpred.inout.etd_start)
134
+ self.etddt_start = datetime(year, 1, 1)
135
+ year = etpred.inout.etd_end
136
+ self.etddt_end = datetime(int(year), 1, 1) + timedelta(
137
+ days=(year - int(year)) * 365
138
+ )
139
+
140
+ # capture end date of file "etpolut1.dat" from module
141
+ self.etpolut1_start = datetime.strptime(str(etpred.inout.etpol_start), "%Y%m%d")
142
+ self.etpolut1_end = datetime.strptime(str(etpred.inout.etpol_end), "%Y%m%d")
143
+
144
+ self.headers = np.char.strip(etpred.inout.header.astype("str"))
145
+ # self.units = ['(m/s)**2','nm/s**2','mas','mm','mm','nstr','nstr','nstr','nstr','nstr','mm']
146
+ self.exec = False
147
+
148
+ self.wavegroup_def = np.asarray([[0, 10, 1, 0.0]])
149
+ self.set_wavegroup(self.wavegroup_def)
150
+
151
+ # %% sync the Python object
152
+ def update(self):
153
+ """
154
+ self.update() refreshes the variables of PyGTide based on the Fortran module etpred
155
+ """
156
+ self.headers = np.char.strip(etpred.inout.header.astype("str"))
157
+ self.args = etpred.inout.argsin
158
+ self.unit = etpred.inout.etpunit.astype("str")
159
+
160
+ # %% set wave group parameters
161
+ def set_wavegroup(self, wavedata=None):
162
+ if wavedata is None:
163
+ wavedata = self.wavegroup_def
164
+ # require at least 4 columns
165
+ if wavedata.shape[1] != 4:
166
+ raise ValueError("The wave group input must have 4 columns!")
167
+ return False
168
+ # require frequency ranges to increase and not overlap
169
+ freq_diffs = np.diff(wavedata[:, 0:1].flatten())
170
+ if (freq_diffs < 0).any():
171
+ raise ValueError(
172
+ "Wave group frequency ranges must be increasing and not overlapping!"
173
+ )
174
+ return False
175
+ if (wavedata[:, 2] < 0).any():
176
+ raise ValueError("Amplitude factors must be positive!")
177
+ return False
178
+ # set the wave group parameters
179
+ etpred.waves(
180
+ wavedata[:, 0],
181
+ wavedata[:, 1],
182
+ wavedata[:, 2],
183
+ wavedata[:, 3],
184
+ int(wavedata.shape[0]),
185
+ )
186
+ return True
187
+
188
+ # %% reset the wave group
189
+ def reset_wavegroup(self):
190
+ self.set_wavegroup(self.wavegroup_def)
191
+ return True
192
+
193
+ # run module etpred and return numbers
194
+ def predict(
195
+ self, latitude, longitude, height, startdate, duration, samprate, **control
196
+ ):
197
+ """
198
+ self.predict(latitude, longitude, height, startdate, duration, samprate, **control):
199
+ -------------------------------------------------------------------------------
200
+ Explanation of parameters used as numeric array "argsin". Parameters which are set
201
+ will overwrite default control parameters (ETERNA ini file input is disabled).
202
+ -------------------------------------------------------------------------------
203
+ Required parameters:
204
+ ---
205
+ Latitude Ellipsoidal latitude of the station in degree referring to
206
+ WGS84 reference system (ETERNA: STATLATITU).
207
+ Longitude Ellipsoidal longitude of the station in degree referring
208
+ to WGS84 reference system (ETERNA: STATLONITU).
209
+ Height Ellipsoidal height of the station in meters referring to
210
+ WGS84 reference system (ETERNA: STATELEVAT).
211
+ Startdate Initial epoch, used to compute the Fourier development of
212
+ the specific earth tide component. Format is either string
213
+ 'YYYY-MM-DD' or a datime object (ETERNA: INITIALEPO).
214
+ Duration Time span for the prediction in hours. The model tide series
215
+ will start at the initial epoch INITIALEPO and the time span
216
+ will be PREDICSPAN hours (ETERNA: PREDICSPAN).
217
+ Samprate Data sample interval in seconds (ETERNA: SAMPLERATE).
218
+ -------------------------------------------------------------------------------
219
+ Optional keyword (**control) parameters:
220
+ ---
221
+ statgravit= Gravity of the station in m/s^2, necessary for tidal tilt
222
+ only. If the gravity is unknown, use a value of less than 1.0
223
+ and the program will compute and subsequently use the
224
+ normal gravity value referring to GRS80 reference system.
225
+ statazimut= azimuth of the instrument in degree decimal, reckoned
226
+ clockwise from north. This parameter is used for tidal tilt,
227
+ horizontal displacement and horizontal strain only.
228
+ tidalpoten= Parameter for the tidal potential catalog to be used:
229
+ 1 = Doodson (1921) tidal potential catalog,
230
+ 2 = Cartwright-Tayler-Edden (1973) tidal potential catalog
231
+ 3 = Buellesfeld (1985) tidal potential catalog,
232
+ 4 = Tamura (1987) tidal potential catalog,
233
+ 5 = Xi (1989) tidal potential catalog,
234
+ 6 = Roosbeek (1996) tidal potential catalog,
235
+ 7 = Hartmann and Wenzel (1995) tidal potential catalog.
236
+ 8 = (default) Kudryavtsev (2004) tidal potential catalog.
237
+ tidalcompo= Earth tide component:
238
+ = -1 for tidal potential (m**2/s**2)
239
+ = 0 (default) for tidal gravity in (nm/s**2)
240
+ = 1 for tidal tilt (mas), at azimuth STATAZIMUT.
241
+ = 2 for tidal vertical displacement (mm)
242
+ = 3 for tidal horizontal displacement (mm) azimuth STATAZIMUT.
243
+ = 4 for tidal vertical strain (10**-9 = nstr)
244
+ = 5 for tidal horizontal strain (10**-9 = nstr) azimuth STATAZIMUT.
245
+ = 6 for tidal areal strain (10**-9 = nstr)
246
+ = 7 for tidal shear strain (10**-9 = nstr)
247
+ = 8 for tidal volume strain (10**-9 = nstr)
248
+ The computed model tides will be given in the units defined above.
249
+ amtruncate= Amplitude threshold (default 0) for the tidal potential catalogue (m^2/s^2).
250
+ Only tidal waves with amplitudes exceeding the
251
+ amplitude threshold are used for the computation. This
252
+ reduces the execution time, but also the accuracy of the
253
+ computed tidal signales. For mean latitudes, the relation
254
+ between amplitude threshold and gravity tide accuracy is
255
+ for the Hartmann and Wenzel (1995) tidal potential catalog
256
+ threshold rms error [nm/s^2]
257
+ 1.D-01 88.40
258
+ 1.D-02 14.40
259
+ 1.D-03 2.250
260
+ 1.D-04 0.440
261
+ 1.D-05 0.068
262
+ 1.D-06 0.011
263
+ 1.D-07 0.002
264
+ 1.D-08 0.001
265
+ 1.D-09 0.001
266
+ 1.D-10 0.001
267
+ poltidecor= Amplitude factor for gravity pole tide. If the amplitude
268
+ factor is greater zero, gravity pole tides will be computed using
269
+ the IERS daily pole coordinates. Default value is 1.16.
270
+ lodtidecor= Amplitude factor for gravity LOD tide. If the amplitude
271
+ factor is greater zero, gravity LOD tides will be computed
272
+ using the IERS daily pole coordinates. Default value is 1.16.
273
+ fileout= Defaults value is 0 (output is suppressed). If set to 1, the routine
274
+ writes two text files called "self.inout.prd" and "self.inout.prn"
275
+ in the original format into the directory of the module.
276
+ screenout= Defaults value is 0 (output is silenced). If set to 1, the routine
277
+ writes output to the screen (but not the Python terminal).
278
+ -------------------------------------------------------------------------------
279
+ """
280
+ # prepare full input argument array
281
+ argsin = np.zeros(18)
282
+ # define default values as given by the Fortran code
283
+ # tidal catalog
284
+ argsin[10] = 8
285
+ # amplitude truncation
286
+ argsin[12] = 1.0e-10
287
+ # values from: https://dx.doi.org/10.1016/j.jog.2005.08.035
288
+ argsin[13] = 1.16
289
+ argsin[14] = 1.16
290
+
291
+ # iterate through optional arguments passed
292
+ if "statgravit" in control:
293
+ if not (0 <= control["statgravit"] <= 20):
294
+ raise ValueError("Station gravity exceeds permissible range!")
295
+ else:
296
+ argsin[8] = control["statgravit"]
297
+ if "statazimut" in control:
298
+ if not (0 <= control["statazimut"] <= 180):
299
+ raise ValueError("Statazimut exceeds permissible range!")
300
+ else:
301
+ argsin[9] = control["statazimut"]
302
+ if "tidalpoten" in control:
303
+ if control["tidalpoten"] not in range(1, 9):
304
+ raise ValueError("Tidalpoten must be an integer between 1 and 8!")
305
+ else:
306
+ argsin[10] = control["tidalpoten"]
307
+ if "tidalcompo" in control:
308
+ if control["tidalcompo"] not in range(-1, 10):
309
+ raise ValueError("Tidalcompo must be an integer between -1 and 9!")
310
+ else:
311
+ argsin[11] = control["tidalcompo"]
312
+ if "amtruncate" in control:
313
+ if not (0 <= control["amtruncate"]):
314
+ raise ValueError("Amtruncate must be greater than 0!")
315
+ else:
316
+ argsin[12] = control["amtruncate"]
317
+ if "poltidecor" in control:
318
+ if not (control["poltidecor"] >= 0):
319
+ raise ValueError("Poltidecor must be >= 0!")
320
+ else:
321
+ argsin[13] = control["poltidecor"]
322
+ if "lodtidecor" in control:
323
+ if not (control["lodtidecor"] >= 0):
324
+ raise ValueError("Lodtidecor must be >= 0!")
325
+ else:
326
+ argsin[14] = control["lodtidecor"]
327
+ # additional control parameters
328
+ if "fileprd" in control:
329
+ if control["fileprd"] not in range(0, 2):
330
+ raise ValueError("Fileprd flag must be 0 or 1!")
331
+ else:
332
+ argsin[15] = control["fileprd"]
333
+ if "fileprn" in control:
334
+ if control["fileprn"] not in range(0, 2):
335
+ raise ValueError("Fileprn flag must be 0 or 1!")
336
+ else:
337
+ argsin[16] = control["fileprn"]
338
+ if "screenout" in control:
339
+ if control["screenout"] not in range(0, 2):
340
+ raise ValueError("Screenout flag must be 0 or 1!")
341
+ else:
342
+ argsin[17] = control["screenout"]
343
+ # process required parameters here
344
+ if not (-90 <= latitude <= 90):
345
+ raise ValueError("Latitude exceeds permissible range!")
346
+ else:
347
+ argsin[0] = latitude
348
+ if not (-180 <= longitude <= 180):
349
+ raise ValueError("Longitude exceeds permissible range!")
350
+ else:
351
+ argsin[1] = longitude
352
+ if not (-500 <= height <= 5000):
353
+ raise ValueError("Height exceeds permissible range!")
354
+ else:
355
+ argsin[2] = height
356
+ if not (0 < duration <= 10 * 24 * 365):
357
+ raise ValueError("Duration exceeds permissible range!")
358
+ else:
359
+ argsin[6] = int(duration)
360
+
361
+ # test startdate format and validity
362
+ if not (isinstance(startdate, date)):
363
+ try:
364
+ startdate = datetime.strptime(startdate, "%Y-%m-%d")
365
+ except ValueError:
366
+ raise ValueError("Startdate has incorrect format (YYYY-MM-DD)!")
367
+ enddate = startdate + timedelta(hours=duration)
368
+ # check if requested prediction series exceeds permissible time
369
+ if startdate < self.etddt_start:
370
+ fname = str(etpred.params.etddtdat)
371
+ warn(
372
+ "Prediction timeframe is earlier than the available time database (%s). "
373
+ "For details refer to the file '%s'." % (self.etddt_start, fname)
374
+ )
375
+ if enddate > (self.etddt_end + timedelta(days=365)):
376
+ fname = str(etpred.params.etddtdat)
377
+ warn(
378
+ "Please consider updating the leap second database '%s' (last value is from %s)."
379
+ % (fname, self.etddt_end)
380
+ )
381
+ # if not (-50*365 < (startdate - dt.datetime.now()).days < 365):
382
+ if ((argsin[13] > 0) or (argsin[14] > 0)) and (
383
+ (startdate < self.etpolut1_start) or (enddate > self.etpolut1_end)
384
+ ):
385
+ fname = str(etpred.params.etddtdat)
386
+ warn(
387
+ "Dates exceed permissible range for pole/LOD tide correction (interval %s to %s). Consider update file '%s'."
388
+ % (self.etpolut1_start, self.etpolut1_end, fname)
389
+ )
390
+ if ((argsin[13] > 0) or (argsin[14] > 0)) and (
391
+ startdate < datetime.strptime("1600-01-01", "%Y-%m-%d")
392
+ ):
393
+ raise ValueError(
394
+ "PyGTide should not be used for dates before the year 1600."
395
+ )
396
+ # set the start date and time
397
+ argsin[3:6] = [startdate.year, startdate.month, startdate.day]
398
+ # test sammprate validity
399
+ if not (0 < samprate <= 24 * 3600):
400
+ raise ValueError("samprate exceeds permissible range!")
401
+ else:
402
+ argsin[7] = int(samprate)
403
+ # test that samprate is not larger than duration
404
+ if samprate / 3600 > duration:
405
+ raise ValueError("samprate exceeds duration!")
406
+ # print(argsin)
407
+ self.args = argsin
408
+ if self.msg:
409
+ print("%s is calculating, please wait ..." % (self.fortran_version))
410
+
411
+ # hand over variables
412
+ etpred.predict(argsin)
413
+
414
+ self.exec = True
415
+ self.update()
416
+ return True
417
+
418
+ # easy access to the raw data calculated by the Fortran module
419
+ def results(self, digits=6):
420
+ """
421
+ self.results(digits=6)
422
+ Returns:
423
+ - If predict() was executed, returns a dataframe with the results
424
+ - False
425
+ keyword 'digits' sets the number of digits returned.
426
+ """
427
+ if self.exec:
428
+ # get the headers from Fortran
429
+ cols = np.char.strip(etpred.inout.header.astype("str"))
430
+ allcols = np.insert(cols[2:], 0, "UTC")
431
+ etdata = pd.DataFrame(columns=allcols)
432
+ # format date and time into padded number strings
433
+ etpred_data = np.array(etpred.inout.etpdata)
434
+ # print(etpred.inout.etpdata[:, 0])
435
+ # catch non-complete container fills from odd duration/sampling pairs
436
+ etpred_data = etpred_data[etpred_data[:, 0] > 0, :]
437
+ # convert
438
+ date = np.char.mod("%08.0f ", etpred_data[:, 0])
439
+ time = np.char.mod("%06.0f", etpred_data[:, 1])
440
+ # merge date and time arrays
441
+ datetime = np.core.defchararray.add(date, time)
442
+ etdata["UTC"] = pd.to_datetime(datetime, format="%Y%m%d %H%M%S", utc=True)
443
+ # obtain header strings from routine and convert
444
+ etdata[cols[2:]] = np.around(etpred_data[:, 2:], digits)
445
+ return etdata
446
+ else:
447
+ return None
448
+
449
+ # easy access to the raw data calculated by Fortran
450
+ def raw(self):
451
+ """
452
+ self.raw()
453
+ Returns:
454
+ - If predict() was executed, returns the raw data from the etpred module
455
+ - False
456
+ """
457
+ if self.exec:
458
+ return etpred.inout.etpdata
459
+ else:
460
+ return None
461
+
462
+ # easy access to the formatted data calculated by Fortran
463
+ def data(self, digits=6):
464
+ """
465
+ self.data(digits=6):
466
+ Returns:
467
+ - If predict() was executed, returns a numpy array with the results
468
+ - False
469
+ keyword 'digits' sets the number of digits returned.
470
+ """
471
+ if self.exec:
472
+ return np.around(etpred.inout.etpdata[:, 2:], digits)
473
+ else:
474
+ return None
475
+
476
+ # easy access to the raw datetime calculated by Fortran
477
+ def datetime(self):
478
+ """
479
+ self.datetime():
480
+ Returns:
481
+ - If predict() was executed, returns a numpy string array with the
482
+ calculated dates and times in seperate columns
483
+ - False
484
+ """
485
+ if self.exec:
486
+ # reformat the date and time values obtained from ETERNA
487
+ date = np.char.mod("%08.0f", etpred.inout.etpdata[:, 0])
488
+ time = np.char.mod("%06.0f", etpred.inout.etpdata[:, 1])
489
+ return np.stack((date, time), axis=1)
490
+ else:
491
+ return None
492
+
493
+
494
+ def predict_table(*args, msg=False, **kwargs):
495
+ kwargs.setdefault("screenout", int(msg))
496
+ pt = pygtide(msg=msg)
497
+ pt.predict(*args, **kwargs)
498
+ return pt.results()
499
+
500
+
501
+ def predict_series(*args, msg=False, index=0, **kwargs):
502
+ kwargs.setdefault("screenout", int(msg))
503
+ pt = pygtide(msg=msg)
504
+ pt.predict(*args, **kwargs)
505
+ return pt.data()[:, index]
506
+
507
+
508
+ def predict_spectrum(*args, nfft=None, **kwargs):
509
+ from numpy.fft import rfft, rfftfreq
510
+
511
+ sr = args[-1]
512
+ data = predict_series(*args, **kwargs)
513
+ if nfft is None:
514
+ nfft = len(data)
515
+ freq = rfftfreq(nfft, sr)
516
+ spec = rfft(data, nfft) * 2 / len(data)
517
+ return freq, spec
518
+
519
+
520
+ def plot_series(*args, indices=(0, 1), show=True, **kwargs):
521
+ table = predict_table(*args, **kwargs)
522
+ table.plot(*indices)
523
+ if show:
524
+ import matplotlib.pyplot as plt
525
+
526
+ plt.show()
527
+
528
+
529
+ def plot_spectrum(*args, ax=None, show=True, **kwargs):
530
+ import matplotlib.pyplot as plt
531
+
532
+ freq, spec = predict_spectrum(*args, **kwargs)
533
+ if ax is None:
534
+ ax = plt.subplot(111)
535
+ ax.plot(freq * 24 * 3600, np.abs(spec))
536
+ ax.set_xlabel("freq (cycles per day)")
537
+ ax.set_ylabel("amplitude")
538
+ if show:
539
+ plt.show()
pygtide/etpred.pyd ADDED
Binary file
pygtide/tests.py ADDED
@@ -0,0 +1,70 @@
1
+ # PyGTide tests
2
+ import numpy as np
3
+ from pygtide import predict_series, predict_spectrum, predict_table
4
+ from pygtide import plot_series, plot_spectrum
5
+ from pygtide import pygtide
6
+
7
+ def test(msg=False):
8
+ pt = pygtide(msg)
9
+ args = (-20.82071, -70.15288, 830.0, '2017-01-01', 6, 600)
10
+ pt.predict(*args, statazimut=90, tidalcompo=8)
11
+ pt.results()
12
+
13
+ # test against a proven array
14
+ args = (49.00937, 8.40444, 120, '2018-01-01', 50, 3600)
15
+ series = predict_series(*args, tidalcompo=5)
16
+ expected = np.array([ 20.120256, 11.371802, 0.303512, -10.684455, -19.316747,
17
+ -23.986328, -24.121086, -20.285591, -13.997906, -7.31219 ,
18
+ -2.274406, -0.387909, -2.221906, -7.260202, -14.028836,
19
+ -20.472805, -24.490089, -24.490442, -19.837274, -11.056935,
20
+ 0.244288, 11.741719, 20.946235, 25.812105, 25.243209,
21
+ 19.366281, 9.499723, -2.173853, -13.145977, -21.192355,
22
+ -24.899299, -23.975888, -19.27862 , -12.549307, -5.938311,
23
+ -1.436475, -0.358888, -3.008326, -8.60056 , -15.468955,
24
+ -21.497314, -24.673643, -23.626746, -18.009927, -8.631809,
25
+ 2.704171, 13.61692 , 21.720306, 25.207537, 23.297815,
26
+ 16.429417])
27
+ np.testing.assert_almost_equal(series, expected, 5)
28
+
29
+ args = (-20.82071, -70.15288, 830.0, '2020-01-01', 29.5 * 24, 600)
30
+ predict_table(*args, statazimut=90, tidalcompo=8, msg=msg)
31
+ freq, spec = predict_spectrum(*args, statazimut=90, tidalcompo=8)
32
+ index = np.argmax(np.abs(spec))
33
+ freqM2 = freq[index] * 3600
34
+ freqM2expected = 1 / 12.421
35
+ assert abs(freqM2 - freqM2expected) / freqM2expected < 2e-3
36
+
37
+ try:
38
+ import matplotlib.pyplot as plt
39
+ except ImportError:
40
+ pass
41
+ else:
42
+ plot_spectrum(*args, statazimut=90, tidalcompo=8, show=False)
43
+ plot_series(*args, statazimut=90, tidalcompo=8, show=False)
44
+ if msg:
45
+ plt.show()
46
+
47
+ print('------------------------------------')
48
+ print('Successfully finished PyGTide tests!')
49
+
50
+
51
+ def plot_pole_and_lod_tide():
52
+ try:
53
+ import matplotlib.pyplot as plt
54
+ except ImportError:
55
+ return False
56
+ ploty = ['Signal [nm/s**2]', 'Pole tide [nm/s**2]', 'LOD tide [nm/s**2]']
57
+ dates = ['1961-12-24', '2016-12-24', '2023-06-01']
58
+ for date in dates:
59
+ args = (20, 0, 0, date, 24*30, 600)
60
+ tab = predict_table(*args)
61
+ tab.plot(x='UTC', y=ploty, sharex=True, subplots=True)
62
+ plt.savefig(f'tides_{date}.png')
63
+ args = (-20.82071, -70.15288, 830.0, '2017-01-01', 10, 600)
64
+ tab = predict_table(*args, statazimut=90)
65
+ tab.plot(x='UTC', y=ploty, sharex=True, subplots=True)
66
+ plt.savefig('tides_2017-01-01_10h.png')
67
+ plt.show()
68
+
69
+ # test()
70
+ #_plot_tides()