Skip to content

Commit bd62b1c

Browse files
committed
Improve tides analysis step
- Run extract_HC in parallel - Add list of constituents to analyze to config file
1 parent db6fda2 commit bd62b1c

File tree

3 files changed

+111
-85
lines changed

3 files changed

+111
-85
lines changed

compass/ocean/tests/tides/analysis/__init__.py

Lines changed: 105 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import os
2+
import subprocess
23

34
import cartopy.crs as ccrs
45
import cartopy.feature as cfeature
56
import matplotlib.pyplot as plt
67
import netCDF4
78
import numpy as np
89
from matplotlib import colormaps
9-
from mpas_tools.logging import check_call
1010

1111
from compass.step import Step
1212

@@ -42,7 +42,8 @@ def __init__(self, test_case):
4242

4343
self.harmonic_analysis_file = 'harmonicAnalysis.nc'
4444
self.grid_file = 'initial_state.nc'
45-
self.constituents = ['k1', 'k2', 'm2', 'n2', 'o1', 'p1', 'q1', 's2']
45+
self.constituents_valid = ['k1', 'k2', 'm2', 'n2',
46+
'o1', 'p1', 'q1', 's2']
4647

4748
self.add_input_file(
4849
filename=self.harmonic_analysis_file,
@@ -60,6 +61,8 @@ def setup(self):
6061
config = self.config
6162
self.tpxo_version = config.get('tides', 'tpxo_version')
6263

64+
self.constituents = config.getlist('tides', 'constituents')
65+
6366
os.makedirs(f'{self.work_dir}/TPXO_data', exist_ok=True)
6467
if self.tpxo_version == 'TPXO9':
6568
for constituent in self.constituents:
@@ -90,72 +93,79 @@ def setup(self):
9093
target='TPXO8/grid_tpxo8_atlas_30_v1',
9194
database='tides')
9295

93-
def write_coordinate_file(self, idx):
96+
def write_coordinate_file(self, nchunks):
9497
"""
9598
Write mesh coordinates for TPXO extraction
9699
"""
97100

98101
# Read in mesh
99102
grid_nc = netCDF4.Dataset(self.grid_file, 'r')
100-
lon_grid = np.degrees(grid_nc.variables['lonCell'][idx])
101-
lat_grid = np.degrees(grid_nc.variables['latCell'][idx])
102-
nCells = len(lon_grid)
103+
lon_grid = np.degrees(grid_nc.variables['lonCell'][:])
104+
lat_grid = np.degrees(grid_nc.variables['latCell'][:])
105+
106+
lon_grid_split = np.array_split(lon_grid, nchunks)
107+
lat_grid_split = np.array_split(lat_grid, nchunks)
103108

104109
# Write coordinate file for OTPS2
105-
f = open('lat_lon', 'w')
106-
for i in range(nCells):
107-
f.write(f'{lat_grid[i]} {lon_grid[i]} \n')
108-
f.close()
110+
for chunk in range(nchunks):
111+
f = open(f'inputs/lat_lon_{chunk}', 'w')
112+
for i in range(lon_grid_split[chunk].size):
113+
f.write(f'{lat_grid_split[chunk][i]} '
114+
f'{lon_grid_split[chunk][i]} \n')
115+
f.close()
109116

110-
def setup_otps2(self):
117+
def setup_otps2(self, nchunks):
111118
"""
112119
Write input files for TPXO extraction
113120
"""
114121

115122
for con in self.constituents:
116123
print(f'setup {con}')
117124

118-
# Lines for the setup_con files
119-
lines = [{'inp': f'inputs/Model_atlas_{con}',
120-
'comment': '! 1. tidal model control file'},
121-
{'inp': 'lat_lon',
122-
'comment': '! 2. latitude/longitude/<time> file'},
123-
{'inp': 'z',
124-
'comment': '! 3. z/U/V/u/v'},
125-
{'inp': con,
126-
'comment': '! 4. tidal constituents to include'},
127-
{'inp': 'AP',
128-
'comment': '! 5. AP/RI'},
129-
{'inp': 'oce',
130-
'comment': '! 6. oce/geo'},
131-
{'inp': '1',
132-
'comment': '! 7. 1/0 correct for minor constituents'},
133-
{'inp': f'outputs/{con}.out',
134-
'comment': '! 8. output file (ASCII)'}]
135-
136-
# Create directory for setup_con and Model_atlas_con files
137-
if not os.path.exists('inputs'):
138-
os.mkdir('inputs')
139-
140-
# Write the setup_con file
141-
f = open(f'inputs/{con}_setup', 'w')
142-
for line in lines:
143-
spaces = 28 - len(line['inp'])
144-
f.write(line['inp'] + spaces * ' ' + line['comment'] + '\n')
145-
f.close()
146-
147-
# Write the Model_atlas_con file
148-
f = open(f'inputs/Model_atlas_{con}', 'w')
149-
f.write(f'TPXO_data/h_{con}_tpxo\n')
150-
f.write(f'TPXO_data/u_{con}_tpxo\n')
151-
f.write('TPXO_data/grid_tpxo')
152-
f.close()
153-
154-
# Create directory for the con.out files
155-
if not os.path.exists('outputs'):
156-
os.mkdir('outputs')
157-
158-
def run_otps2(self):
125+
for chunk in range(nchunks):
126+
127+
# Lines for the setup_con files
128+
lines = [{'inp': f'inputs/Model_atlas_{con}',
129+
'comment': '! 1. tidal model control file'},
130+
{'inp': f'inputs/lat_lon_{chunk}',
131+
'comment': '! 2. latitude/longitude/<time> file'},
132+
{'inp': 'z',
133+
'comment': '! 3. z/U/V/u/v'},
134+
{'inp': con,
135+
'comment': '! 4. tidal constituents to include'},
136+
{'inp': 'AP',
137+
'comment': '! 5. AP/RI'},
138+
{'inp': 'oce',
139+
'comment': '! 6. oce/geo'},
140+
{'inp': '1',
141+
'comment':
142+
'! 7. 1/0 correct for minor constituents'},
143+
{'inp': f'outputs/{con}_{chunk}.out',
144+
'comment': '! 8. output file (ASCII)'}]
145+
146+
# Create directory for setup_con and Model_atlas_con files
147+
if not os.path.exists('inputs'):
148+
os.mkdir('inputs')
149+
150+
# Write the setup_con file
151+
f = open(f'inputs/{con}_setup_{chunk}', 'w')
152+
for line in lines:
153+
spaces = (28 - len(line['inp'])) * ' '
154+
f.write(f"{line['inp']}{spaces}{line['comment']}\n")
155+
f.close()
156+
157+
# Write the Model_atlas_con file
158+
f = open(f'inputs/Model_atlas_{con}', 'w')
159+
f.write(f'TPXO_data/h_{con}_tpxo\n')
160+
f.write(f'TPXO_data/u_{con}_tpxo\n')
161+
f.write('TPXO_data/grid_tpxo')
162+
f.close()
163+
164+
# Create directory for the con.out files
165+
if not os.path.exists('outputs'):
166+
os.mkdir('outputs')
167+
168+
def run_otps2(self, nchunks):
159169
"""
160170
Perform TPXO extraction
161171
"""
@@ -164,35 +174,46 @@ def run_otps2(self):
164174
for con in self.constituents:
165175
print('')
166176
print(f'run {con}')
167-
check_call(f'extract_HC < inputs/{con}_setup',
168-
logger=self.logger, shell=True)
169177

170-
def read_otps2_output(self, idx):
178+
commands = []
179+
for chunk in range(nchunks):
180+
commands.append(f'extract_HC < inputs/{con}_setup_{chunk}')
181+
182+
processes = [subprocess.Popen(cmd, shell=True) for cmd in commands]
183+
184+
for p in processes:
185+
p.wait()
186+
187+
def read_otps2_output(self, nchunks):
171188
"""
172189
Read TPXO extraction output
173190
"""
174191

175-
start = idx[0]
176192
for con in self.constituents:
177193

178-
f = open(f'outputs/{con}.out', 'r')
179-
lines = f.read().splitlines()
180-
for i, line in enumerate(lines[3:]):
181-
line_sp = line.split()
182-
if line_sp[2] != '*************':
183-
val = float(line_sp[2])
184-
self.mesh_AP[con]['amp'][start + i] = val
185-
else:
186-
self.mesh_AP[con]['amp'][start + i] = -9999
187-
188-
if line_sp[3] != 'Site':
189-
val = float(line_sp[3])
190-
if val < 0:
191-
val = val + 360.0
192-
self.mesh_AP[con]['phase'][start + i] = val
193-
194-
else:
195-
self.mesh_AP[con]['phase'][start + i] = -9999
194+
i = 0
195+
for chunk in range(nchunks):
196+
197+
f = open(f'outputs/{con}_{chunk}.out', 'r')
198+
lines = f.read().splitlines()
199+
for line in lines[3:]:
200+
line_sp = line.split()
201+
if line_sp[2] != '*************':
202+
val = float(line_sp[2])
203+
self.mesh_AP[con]['amp'][i] = val
204+
else:
205+
self.mesh_AP[con]['amp'][i] = -9999
206+
207+
if line_sp[3] != 'Site':
208+
val = float(line_sp[3])
209+
if val < 0:
210+
val = val + 360.0
211+
self.mesh_AP[con]['phase'][i] = val
212+
213+
else:
214+
self.mesh_AP[con]['phase'][i] = -9999
215+
216+
i = i + 1
196217

197218
def append_tpxo_data(self):
198219
"""
@@ -283,7 +304,7 @@ def plot(self):
283304
depth[:] = data_nc.variables['bottomDepth'][:]
284305
area[:] = data_nc.variables['areaCell'][:]
285306

286-
constituent_list = ['K1', 'M2', 'N2', 'O1', 'S2']
307+
constituent_list = [x.upper() for x in self.constituents]
287308

288309
# Use these to fix up the plots
289310
subplot_ticks = [[np.linspace(0, 0.65, 10), np.linspace(0, 0.65, 10),
@@ -429,16 +450,16 @@ def run(self):
429450
Run this step of the test case
430451
"""
431452

453+
self.constituents = self.config.getlist('tides', 'constituents')
454+
432455
# Check if TPXO values aleady exist in harmonic_analysis.nc
433456
self.check_tpxo_data()
434457

435-
# Setup input files for TPXO extraction
436-
self.setup_otps2()
437-
438458
# Setup chunking for TPXO extraction with large meshes
439-
indices = np.arange(self.nCells)
440-
nchunks = np.ceil(self.nCells / 200000)
441-
index_chunks = np.array_split(indices, nchunks)
459+
nchunks = 128
460+
461+
# Setup input files for TPXO extraction
462+
self.setup_otps2(nchunks)
442463

443464
# Initialize data structure for TPXO values
444465
self.mesh_AP = {}
@@ -447,10 +468,9 @@ def run(self):
447468
'phase': np.zeros((self.nCells))}
448469

449470
# Extract TPXO values
450-
for idx in index_chunks:
451-
self.write_coordinate_file(idx)
452-
self.run_otps2()
453-
self.read_otps2_output(idx)
471+
self.write_coordinate_file(nchunks)
472+
self.run_otps2(nchunks)
473+
self.read_otps2_output(nchunks)
454474

455475
# Inject TPXO values
456476
self.append_tpxo_data()

compass/ocean/tests/tides/mesh/icos7/icos7.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ forward_threads = 1
5555
# TPXO version for validation
5656
tpxo_version = TPXO9
5757

58+
# constituents to validate
59+
constituents = k1, k2, m2, n2, o1, p1, q1, s2
60+
5861

5962
# config options related to remapping topography to an MPAS-Ocean mesh
6063
[remap_topography]

compass/ocean/tests/tides/mesh/vr45to5/vr45to5.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ forward_threads = 1
6161
# TPXO version for validation
6262
tpxo_version = TPXO9
6363

64+
# constituents to validate
65+
constituents = k1, k2, m2, n2, o1, p1, q1, s2
66+
6467
# config options related to remapping topography to an MPAS-Ocean mesh
6568
[remap_topography]
6669

0 commit comments

Comments
 (0)