wavespectra 4.4.3__tar.gz → 4.5.1__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.
Files changed (73) hide show
  1. {wavespectra-4.4.3/wavespectra.egg-info → wavespectra-4.5.1}/PKG-INFO +1 -1
  2. {wavespectra-4.4.3 → wavespectra-4.5.1}/pyproject.toml +1 -0
  3. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/__init__.py +1 -1
  4. wavespectra-4.5.1/wavespectra/input/funwave_new.py +104 -0
  5. wavespectra-4.5.1/wavespectra/output/funwave.py +235 -0
  6. {wavespectra-4.4.3 → wavespectra-4.5.1/wavespectra.egg-info}/PKG-INFO +1 -1
  7. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra.egg-info/SOURCES.txt +1 -0
  8. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra.egg-info/entry_points.txt +1 -0
  9. wavespectra-4.4.3/wavespectra/output/funwave.py +0 -122
  10. {wavespectra-4.4.3 → wavespectra-4.5.1}/AUTHORS.rst +0 -0
  11. {wavespectra-4.4.3 → wavespectra-4.5.1}/LICENSE.txt +0 -0
  12. {wavespectra-4.4.3 → wavespectra-4.5.1}/README.rst +0 -0
  13. {wavespectra-4.4.3 → wavespectra-4.5.1}/setup.cfg +0 -0
  14. {wavespectra-4.4.3 → wavespectra-4.5.1}/setup.py +0 -0
  15. {wavespectra-4.4.3 → wavespectra-4.5.1}/tests/test_cli.py +0 -0
  16. {wavespectra-4.4.3 → wavespectra-4.5.1}/tests/test_cookiecutter_template.py +0 -0
  17. {wavespectra-4.4.3 → wavespectra-4.5.1}/tests/test_partition.py +0 -0
  18. {wavespectra-4.4.3 → wavespectra-4.5.1}/tests/test_specarray.py +0 -0
  19. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/cli.py +0 -0
  20. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/construct/__init__.py +0 -0
  21. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/construct/direction.py +0 -0
  22. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/construct/frequency.py +0 -0
  23. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/__init__.py +0 -0
  24. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/attributes.py +0 -0
  25. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/attributes.yml +0 -0
  26. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/fitting.py +0 -0
  27. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/npstats.py +0 -0
  28. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/select.py +0 -0
  29. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/swan.py +0 -0
  30. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/utils.py +0 -0
  31. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/core/xrstats.py +0 -0
  32. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/__init__.py +0 -0
  33. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/awac.py +0 -0
  34. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/dataset.py +0 -0
  35. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/datawell.py +0 -0
  36. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/era5.py +0 -0
  37. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/funwave.py +0 -0
  38. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/json.py +0 -0
  39. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/ncswan.py +0 -0
  40. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/ndbc.py +0 -0
  41. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/ndbc_ascii.py +0 -0
  42. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/netcdf.py +0 -0
  43. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/obscape.py +0 -0
  44. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/octopus.py +0 -0
  45. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/spotter.py +0 -0
  46. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/swan.py +0 -0
  47. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/triaxys.py +0 -0
  48. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/wavespectra.py +0 -0
  49. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/ww3.py +0 -0
  50. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/ww3_station.py +0 -0
  51. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/wwm.py +0 -0
  52. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/input/xwaves.py +0 -0
  53. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/output/__init__.py +0 -0
  54. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/output/json.py +0 -0
  55. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/output/netcdf.py +0 -0
  56. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/output/octopus.py +0 -0
  57. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/output/orcaflex.py +0 -0
  58. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/output/swan.py +0 -0
  59. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/output/ww3.py +0 -0
  60. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/output/ww3.yml +0 -0
  61. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/partition/__init__.py +0 -0
  62. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/partition/hanson_and_phillips_2001.py +0 -0
  63. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/partition/partition.py +0 -0
  64. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/partition/specpart/specpart.c +0 -0
  65. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/partition/specpart/specpart.h +0 -0
  66. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/partition/specpart/specpart_wrap.c +0 -0
  67. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/partition/tracking.py +0 -0
  68. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/plot.py +0 -0
  69. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/specarray.py +0 -0
  70. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra/specdataset.py +0 -0
  71. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra.egg-info/dependency_links.txt +0 -0
  72. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra.egg-info/requires.txt +0 -0
  73. {wavespectra-4.4.3 → wavespectra-4.5.1}/wavespectra.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavespectra
3
- Version: 4.4.3
3
+ Version: 4.5.1
4
4
  Summary: Library for ocean wave spectra
5
5
  Author-email: Wavespectra Developers <r.guedes@oceanum.science>
6
6
  Maintainer-email: Rafael Guedes <r.guedes@oceanum.science>, Tom Durrant <t.durrant@oceanum.science>
@@ -56,6 +56,7 @@ wavespectra = "wavespectra.cli:main"
56
56
  [project.entry-points."xarray.backends"]
57
57
  era5 = "wavespectra.input.era5:ERA5BackendEntrypoint"
58
58
  funwave = "wavespectra.input.funwave:FunwaveBackendEntrypoint"
59
+ funwave_new = "wavespectra.input.funwave_new:FunwaveNewBackendEntrypoint"
59
60
  json = "wavespectra.input.json:JsonBackendEntrypoint"
60
61
  ncswan = "wavespectra.input.ncswan:NCSwanBackendEntrypoint"
61
62
  ndbc = "wavespectra.input.ndbc:NDBCBackendEntrypoint"
@@ -14,7 +14,7 @@ except ImportError:
14
14
  warnings.warn("Cannot import accessors at the main module level")
15
15
 
16
16
 
17
- __version__ = "4.4.3"
17
+ __version__ = "4.5.1"
18
18
 
19
19
 
20
20
  def _import_functions(pkgname="input", prefix="read"):
@@ -0,0 +1,104 @@
1
+ """Read Funwave WK_NEW_DATA2D Wave Maker files"""
2
+
3
+ from xarray.backends import BackendEntrypoint
4
+ import numpy as np
5
+ import xarray as xr
6
+
7
+ from wavespectra import SpecArray
8
+ from wavespectra.input import read_ascii_or_binary
9
+ from wavespectra.core.attributes import attrs, set_spec_attributes
10
+
11
+
12
+ def read_funwave_new(filename_or_obj):
13
+ """Read Spectra in Funwave WK_NEW_DATA2D wavemaker format.
14
+
15
+ Args:
16
+ - filename_or_obj (str, filelike): Funwave WaveCompFile to read.
17
+
18
+ Returns:
19
+ - dset (SpecDataset): spectra dataset object read from funwave file.
20
+
21
+ Note:
22
+ - Format description: https://fengyanshi.github.io/build/html/wavemaker_coherence.html.
23
+ - The file describes individual wave components, the gridded spectrum is
24
+ reconstructed by summing component energies :math:`a^2/2` falling in each
25
+ frequency-direction bin defined by the unique frequencies and directions.
26
+ - Directions converted from Cartesian (0E, CCW, to) to wavespectra (0N, CW, from).
27
+ - A 1D :math:`E(f)` spectrum is returned if all directions are equal.
28
+ - Phases are ignored if present.
29
+
30
+ """
31
+ data = read_ascii_or_binary(filename_or_obj, mode="r")
32
+
33
+ # Remove any empty rows
34
+ data = [row for row in data if row != "\n"]
35
+
36
+ # Number of wave components
37
+ nc = int(data.pop(0).split()[0])
38
+
39
+ # Tp
40
+ tp = float(data.pop(0).split()[0])
41
+
42
+ # Wave component blocks
43
+ values = [float(row.split()[0]) for row in data]
44
+ freq = np.array(values[:nc])
45
+ dir = np.array(values[nc : 2 * nc])
46
+ amp = np.array(values[2 * nc : 3 * nc])
47
+
48
+ # Bin component energies onto the spectral grid
49
+ freqs = np.unique(freq)
50
+ dirs = np.unique(dir)
51
+ ifreq = np.searchsorted(freqs, freq)
52
+ idir = np.searchsorted(dirs, dir)
53
+ energy = np.zeros((freqs.size, dirs.size))
54
+ np.add.at(energy, (ifreq, idir), amp**2 / 2)
55
+
56
+ if dirs.size == 1:
57
+ coords = {attrs.FREQNAME: freqs}
58
+ dims = (attrs.FREQNAME,)
59
+ energy = energy.squeeze(axis=1)
60
+ else:
61
+ # Convert dir from cartesian to wavespectra convention
62
+ dirs = (270 - dirs) % 360
63
+
64
+ # Turn zero dir into 360 for continuity
65
+ i0 = np.where(dirs == 0)[0]
66
+ if i0.size > 0:
67
+ dirs[i0] = 360.0
68
+
69
+ coords = {attrs.FREQNAME: freqs, attrs.DIRNAME: dirs}
70
+ dims = (attrs.FREQNAME, attrs.DIRNAME)
71
+ darr = xr.DataArray(data=energy, coords=coords, dims=dims)
72
+
73
+ # Energy density spectrum
74
+ darr = darr / (darr.spec.df * darr.spec.dd)
75
+
76
+ # Define output dataset
77
+ dset = darr.to_dataset(name=attrs.SPECNAME)
78
+ if dirs.size > 1:
79
+ dset = dset.sortby(attrs.DIRNAME)
80
+ dset["tp"] = tp
81
+ set_spec_attributes(dset)
82
+ return dset
83
+
84
+
85
+ class FunwaveNewBackendEntrypoint(BackendEntrypoint):
86
+ """Funwave WK_NEW_DATA2D backend engine."""
87
+
88
+ def open_dataset(
89
+ self,
90
+ filename_or_obj,
91
+ *,
92
+ drop_variables=None,
93
+ ):
94
+ return read_funwave_new(filename_or_obj)
95
+
96
+ def guess_can_open(self, filename_or_obj):
97
+ return False
98
+
99
+ description = (
100
+ "Open Funwave WK_NEW_DATA2D ASCII wave component files as a wavespectra "
101
+ "dataset."
102
+ )
103
+
104
+ url = "https://github.com/wavespectra/wavespectra"
@@ -0,0 +1,235 @@
1
+ """FUNWAVE output plugin."""
2
+
3
+ import os
4
+ from zipfile import ZipFile, ZIP_DEFLATED
5
+ from io import StringIO
6
+ import logging
7
+ import numpy as np
8
+ from wavespectra.core.attributes import attrs
9
+
10
+
11
+ logger = logging.getLogger(__name__)
12
+ logging.basicConfig(level="INFO")
13
+
14
+
15
+ def to_funwave(
16
+ self,
17
+ filename,
18
+ clip=True,
19
+ ):
20
+ """Write spectra in Funwave format.
21
+
22
+ Args:
23
+ - filename (str): Name for output Funwave file.
24
+ - clip (bool): Clip directions outside [-90, 90] range in cartesian convention.
25
+
26
+ Note:
27
+ - Format description: https://fengyanshi.github.io/build/html/wavemaker_para.html.
28
+ - Directions converted from wavespectra (0N, CW, from) to Cartesian (0E, CCW, to).
29
+ - Funwave only seems to deal with directions in the [-90, 90] range in
30
+ cartesian convention, use clip=True to clip spectra outside that range.
31
+ - Both 2D :math:`E(f,d)` and 1d :math:`E(f)` spectra are supported.
32
+ - If the SpecArray has more than one spectrum, multiple files are created in a
33
+ zip archive defined by replacing the extension of `filename` by ".zip".
34
+
35
+ """
36
+ _write_funwave(self, filename, clip, funwave_spectrum)
37
+
38
+
39
+ def to_funwave_new(
40
+ self,
41
+ filename,
42
+ clip=True,
43
+ phases=True,
44
+ ):
45
+ """Write spectra in Funwave WK_NEW_DATA2D wavemaker format.
46
+
47
+ Args:
48
+ - filename (str): Name for output Funwave file.
49
+ - clip (bool): Clip directions outside [-90, 90] range in cartesian convention.
50
+ - phases (bool): Write random phases for each wave component, if False the
51
+ phase block is omitted and Funwave generates random phases itself.
52
+
53
+ Note:
54
+ - Format description: https://fengyanshi.github.io/build/html/wavemaker_coherence.html.
55
+ - Unlike the gridded WK_DATA2D format written by `to_funwave`, this format
56
+ describes individual wave components, one per spectral bin.
57
+ - Directions converted from wavespectra (0N, CW, from) to Cartesian (0E, CCW, to).
58
+ - Funwave only deals with directions in the [-90, 90] range in cartesian
59
+ convention (components outside it are discarded by the model), use
60
+ clip=True to clip spectra outside that range.
61
+ - Both 2D :math:`E(f,d)` and 1d :math:`E(f)` spectra are supported.
62
+ - If the SpecArray has more than one spectrum, multiple files are created in a
63
+ zip archive defined by replacing the extension of `filename` by ".zip".
64
+
65
+ """
66
+ _write_funwave(
67
+ self,
68
+ filename,
69
+ clip,
70
+ lambda darr, fname: funwave_new_spectrum(darr, fname, phases=phases),
71
+ )
72
+
73
+
74
+ def _write_funwave(self, filename, clip, spectrum_writer):
75
+ """Write one or multiple spectra using the given single-spectrum writer."""
76
+ darr = self.efth.copy(deep=True)
77
+
78
+ # Convert directions to Cartesian, going-to convention
79
+ if attrs.DIRNAME in self.efth.dims:
80
+ dir = (270 - self.dir.values) % 360
81
+ dir[dir > 180] = dir[dir > 180] - 360
82
+ darr = darr.assign_coords({attrs.DIRNAME: dir}).sortby(attrs.DIRNAME)
83
+
84
+ # Clip directions outside [-90, 90] range
85
+ if clip:
86
+ darr = darr.sel(**{attrs.DIRNAME: slice(-90, 90)})
87
+
88
+ stack_dims = list(darr.spec._non_spec_dims)
89
+ dimsizes = set([darr[d].size for d in darr.spec._non_spec_dims])
90
+
91
+ if not stack_dims or dimsizes == {1}:
92
+ # Single spectrum in object, write directly to txt file
93
+ spectrum_writer(darr, filename)
94
+
95
+ else:
96
+ # Multiple spectra in object, write each txt file in a zip archive
97
+ darrs = darr.stack({"stacked": stack_dims})
98
+ fpath, fext = os.path.splitext(filename)
99
+ zipname = fpath + ".zip"
100
+ logger.info(f"Multiple spectra, writing txt files in zip archive {zipname}")
101
+ with ZipFile(zipname, "w", compression=ZIP_DEFLATED) as zstream:
102
+ for ind in range(darrs.stacked.size):
103
+ darr = darrs.isel(stacked=[ind]).unstack()
104
+
105
+ # Prefix to compose file name
106
+ prefix = "_".join([f"{d}-{darr[d].values[0]}" for d in stack_dims])
107
+
108
+ # Squeeze out non-spectral dimensions which should have lenght 1
109
+ darr = darr.squeeze(drop=True)
110
+
111
+ # Write spectrum to zip archive
112
+ fname = os.path.basename(fpath) + "-" + prefix + fext
113
+ logger.debug(f"Write {fname} to {zipname}")
114
+ spectrum = spectrum_writer(darr, None).getvalue()
115
+ zstream.writestr(fname, spectrum)
116
+
117
+
118
+ def funwave_spectrum(darr, filename):
119
+ """Funwave spectrum memory buffer.
120
+
121
+ Args:
122
+ darr (SpecArray): Spectrum to write (only `freq`, `dir` dims are allowed).
123
+ filename (str): Name of file to save spectrum to, choose `None` if you don't
124
+ want to save it to file but only return the memory buffer.
125
+
126
+ Returns:
127
+ StringIO memory buffer with spectrum object.
128
+
129
+ """
130
+ # Ensure direction dim exists for 1D spectrum and has value 0
131
+ if attrs.DIRNAME not in darr.dims:
132
+ darr = darr.expand_dims({attrs.DIRNAME: [0]}, axis=-1)
133
+ elif darr[attrs.DIRNAME].size == 1:
134
+ darr = darr.assign_coords({attrs.DIRNAME: [0]})
135
+
136
+ # Amplitudes and phases
137
+ # Clip to >=0 before sqrt: interpolation/rotation can leave tiny negative
138
+ # energy (~1e-21) from float cancellation, which would yield NaN amplitudes.
139
+ amp = np.sqrt(np.maximum(darr * darr.spec.df * darr.spec.dd * 8, 0.0)) / 2
140
+ amp = amp.transpose()
141
+ nd, nf = amp.shape
142
+ phi = np.random.uniform(0, 1, (nd, nf)) * 360.0
143
+
144
+ # Peak wave period
145
+ tp = float(darr.spec.tp())
146
+
147
+ # Create spectrum in memory buffer
148
+ s = StringIO()
149
+ s.write(f"{nf:>5d}{nd:>5d} - NumFreq NumDir\n")
150
+ s.write(f"{tp:>10.3f} - PeakPeriod\n")
151
+ s.write(nf * "%10.5f - Freq\n" % tuple(darr[attrs.FREQNAME].values))
152
+ s.write(nd * "%10.3f - Dire\n" % tuple(darr[attrs.DIRNAME].values))
153
+ if nd == 1:
154
+ np.savetxt(s, amp.squeeze(drop=True), fmt="%12.8f", delimiter="")
155
+ else:
156
+ np.savetxt(s, amp, fmt="%12.8f", delimiter="")
157
+ np.savetxt(s, phi, fmt="%12.3f", delimiter="")
158
+
159
+ # Save to file
160
+ if filename is not None:
161
+ with open(filename, "w") as fid:
162
+ fid.write(s.getvalue())
163
+
164
+ return s
165
+
166
+
167
+ def funwave_new_spectrum(darr, filename, phases=True):
168
+ """Funwave WK_NEW_DATA2D spectrum memory buffer.
169
+
170
+ Args:
171
+ darr (SpecArray): Spectrum to write (only `freq`, `dir` dims are allowed).
172
+ filename (str): Name of file to save spectrum to, choose `None` if you don't
173
+ want to save it to file but only return the memory buffer.
174
+ phases (bool): Write random phases for each wave component, if False the
175
+ phase block is omitted and Funwave generates random phases itself.
176
+
177
+ Returns:
178
+ StringIO memory buffer with spectrum object.
179
+
180
+ Note:
181
+ The file describes one wave component per spectral bin, with blocks of
182
+ frequency (Hz), direction (degrees, cartesian) and amplitude (m) values,
183
+ optionally followed by a block of phases (degrees):
184
+
185
+ .. code-block:: none
186
+
187
+ NumFreq
188
+ PeakPeriod
189
+ Freq(1..NumFreq)
190
+ Dire(1..NumFreq)
191
+ Amp(1..NumFreq)
192
+ Phase(1..NumFreq)
193
+
194
+ """
195
+ # Ensure direction dim exists for 1D spectrum and has value 0
196
+ if attrs.DIRNAME not in darr.dims:
197
+ darr = darr.expand_dims({attrs.DIRNAME: [0]}, axis=-1)
198
+ elif darr[attrs.DIRNAME].size == 1:
199
+ darr = darr.assign_coords({attrs.DIRNAME: [0]})
200
+
201
+ # Component amplitudes from spectral bins
202
+ # Clip to >=0 before sqrt: interpolation/rotation can leave tiny negative
203
+ # energy (~1e-21) from float cancellation, which would yield NaN amplitudes.
204
+ amp = np.sqrt(np.maximum(darr * darr.spec.df * darr.spec.dd * 8, 0.0)) / 2
205
+ amp = amp.transpose(attrs.FREQNAME, attrs.DIRNAME)
206
+
207
+ # Unpack bins into individual wave components
208
+ freq, dire = np.meshgrid(
209
+ darr[attrs.FREQNAME].values, darr[attrs.DIRNAME].values, indexing="ij"
210
+ )
211
+ freq = freq.ravel()
212
+ dire = dire.ravel()
213
+ amp = amp.values.ravel()
214
+ nc = freq.size
215
+
216
+ # Peak wave period
217
+ tp = float(darr.spec.tp())
218
+
219
+ # Create spectrum in memory buffer
220
+ s = StringIO()
221
+ s.write(f"{nc:>5d} - NumFreq\n")
222
+ s.write(f"{tp:>10.3f} - PeakPeriod\n")
223
+ s.write(nc * "%10.5f - Freq\n" % tuple(freq))
224
+ s.write(nc * "%10.3f - Dire\n" % tuple(dire))
225
+ s.write(nc * "%12.8f - Amp\n" % tuple(amp))
226
+ if phases:
227
+ phi = np.random.uniform(0, 360.0, nc)
228
+ s.write(nc * "%12.3f - Phase\n" % tuple(phi))
229
+
230
+ # Save to file
231
+ if filename is not None:
232
+ with open(filename, "w") as fid:
233
+ fid.write(s.getvalue())
234
+
235
+ return s
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavespectra
3
- Version: 4.4.3
3
+ Version: 4.5.1
4
4
  Summary: Library for ocean wave spectra
5
5
  Author-email: Wavespectra Developers <r.guedes@oceanum.science>
6
6
  Maintainer-email: Rafael Guedes <r.guedes@oceanum.science>, Tom Durrant <t.durrant@oceanum.science>
@@ -37,6 +37,7 @@ wavespectra/input/dataset.py
37
37
  wavespectra/input/datawell.py
38
38
  wavespectra/input/era5.py
39
39
  wavespectra/input/funwave.py
40
+ wavespectra/input/funwave_new.py
40
41
  wavespectra/input/json.py
41
42
  wavespectra/input/ncswan.py
42
43
  wavespectra/input/ndbc.py
@@ -4,6 +4,7 @@ wavespectra = wavespectra.cli:main
4
4
  [xarray.backends]
5
5
  era5 = wavespectra.input.era5:ERA5BackendEntrypoint
6
6
  funwave = wavespectra.input.funwave:FunwaveBackendEntrypoint
7
+ funwave_new = wavespectra.input.funwave_new:FunwaveNewBackendEntrypoint
7
8
  json = wavespectra.input.json:JsonBackendEntrypoint
8
9
  ncswan = wavespectra.input.ncswan:NCSwanBackendEntrypoint
9
10
  ndbc = wavespectra.input.ndbc:NDBCBackendEntrypoint
@@ -1,122 +0,0 @@
1
- """FUNWAVE output plugin."""
2
-
3
- import os
4
- from zipfile import ZipFile, ZIP_DEFLATED
5
- from io import StringIO
6
- import logging
7
- import numpy as np
8
- from wavespectra.core.attributes import attrs
9
-
10
-
11
- logger = logging.getLogger(__name__)
12
- logging.basicConfig(level="INFO")
13
-
14
-
15
- def to_funwave(
16
- self,
17
- filename,
18
- clip=True,
19
- ):
20
- """Write spectra in Funwave format.
21
-
22
- Args:
23
- - filename (str): Name for output Funwave file.
24
- - clip (bool): Clip directions outside [-90, 90] range in cartesian convention.
25
-
26
- Note:
27
- - Format description: https://fengyanshi.github.io/build/html/wavemaker_para.html.
28
- - Directions converted from wavespectra (0N, CW, from) to Cartesian (0E, CCW, to).
29
- - Funwave only seems to deal with directions in the [-90, 90] range in
30
- cartesian convention, use clip=True to clip spectra outside that range.
31
- - Both 2D :math:`E(f,d)` and 1d :math:`E(f)` spectra are supported.
32
- - If the SpecArray has more than one spectrum, multiple files are created in a
33
- zip archive defined by replacing the extension of `filename` by ".zip".
34
-
35
- """
36
- darr = self.efth.copy(deep=True)
37
-
38
- # Convert directions to Cartesian, going-to convention
39
- if attrs.DIRNAME in self.efth.dims:
40
- dir = (270 - self.dir.values) % 360
41
- dir[dir > 180] = dir[dir > 180] - 360
42
- darr = darr.assign_coords({attrs.DIRNAME: dir}).sortby(attrs.DIRNAME)
43
-
44
- # Clip directions outside [-90, 90] range
45
- if clip:
46
- darr = darr.sel(**{attrs.DIRNAME: slice(-90, 90)})
47
-
48
- stack_dims = list(darr.spec._non_spec_dims)
49
- dimsizes = set([darr[d].size for d in darr.spec._non_spec_dims])
50
-
51
- if not stack_dims or dimsizes == {1}:
52
- # Single spectrum in object, write directly to txt file
53
- funwave_spectrum(darr, filename)
54
-
55
- else:
56
- # Multiple spectra in object, write each txt file in a zip archive
57
- darrs = darr.stack({"stacked": stack_dims})
58
- fpath, fext = os.path.splitext(filename)
59
- zipname = fpath + ".zip"
60
- logger.info(f"Multiple spectra, writing txt files in zip archive {zipname}")
61
- with ZipFile(zipname, "w", compression=ZIP_DEFLATED) as zstream:
62
- for ind in range(darrs.stacked.size):
63
- darr = darrs.isel(stacked=[ind]).unstack()
64
-
65
- # Prefix to compose file name
66
- prefix = "_".join([f"{d}-{darr[d].values[0]}" for d in stack_dims])
67
-
68
- # Squeeze out non-spectral dimensions which should have lenght 1
69
- darr = darr.squeeze(drop=True)
70
-
71
- # Write spectrum to zip archive
72
- fname = os.path.basename(fpath) + "-" + prefix + fext
73
- logger.debug(f"Write {fname} to {zipname}")
74
- spectrum = funwave_spectrum(darr, None).getvalue()
75
- zstream.writestr(fname, spectrum)
76
-
77
-
78
- def funwave_spectrum(darr, filename):
79
- """Funwave spectrum memory buffer.
80
-
81
- Args:
82
- darr (SpecArray): Spectrum to write (only `freq`, `dir` dims are allowed).
83
- filename (str): Name of file to save spectrum to, choose `None` if you don't
84
- want to save it to file but only return the memory buffer.
85
-
86
- Returns:
87
- StringIO memory buffer with spectrum object.
88
-
89
- """
90
- # Ensure direction dim exists for 1D spectrum and has value 0
91
- if attrs.DIRNAME not in darr.dims:
92
- darr = darr.expand_dims({attrs.DIRNAME: [0]}, axis=-1)
93
- elif darr[attrs.DIRNAME].size == 1:
94
- darr = darr.assign_coords({attrs.DIRNAME: [0]})
95
-
96
- # Amplitudes and phases
97
- amp = np.sqrt(darr * darr.spec.df * darr.spec.dd * 8) / 2
98
- amp = amp.transpose()
99
- nd, nf = amp.shape
100
- phi = np.random.uniform(0, 1, (nd, nf)) * 360.0
101
-
102
- # Peak wave period
103
- tp = float(darr.spec.tp())
104
-
105
- # Create spectrum in memory buffer
106
- s = StringIO()
107
- s.write(f"{nf:>5d}{nd:>5d} - NumFreq NumDir\n")
108
- s.write(f"{tp:>10.3f} - PeakPeriod\n")
109
- s.write(nf * "%10.5f - Freq\n" % tuple(darr[attrs.FREQNAME].values))
110
- s.write(nd * "%10.3f - Dire\n" % tuple(darr[attrs.DIRNAME].values))
111
- if nd == 1:
112
- np.savetxt(s, amp.squeeze(drop=True), fmt="%12.8f", delimiter="")
113
- else:
114
- np.savetxt(s, amp, fmt="%12.8f", delimiter="")
115
- np.savetxt(s, phi, fmt="%12.3f", delimiter="")
116
-
117
- # Save to file
118
- if filename is not None:
119
- with open(filename, "w") as fid:
120
- fid.write(s.getvalue())
121
-
122
- return s
File without changes
File without changes
File without changes
File without changes
File without changes