|
| 1 | +from argparse import ArgumentParser |
| 2 | +from pathlib import Path |
| 3 | +import xarray as xr |
| 4 | +import tracemalloc |
| 5 | +import time |
| 6 | + |
| 7 | +from glob import glob |
| 8 | + |
| 9 | +import numpy as np |
| 10 | + |
| 11 | +import parcels |
| 12 | + |
| 13 | +runtime = np.timedelta64(2, "D") |
| 14 | +dt = np.timedelta64(15, "m") |
| 15 | + |
| 16 | +parcelsv4 = True |
| 17 | +try: |
| 18 | + import xgcm |
| 19 | + from parcels.interpolators import XLinear |
| 20 | +except ImportError: |
| 21 | + parcelsv4 = False |
| 22 | + |
| 23 | +DATA_ROOT = "/storage/shared/oceanparcels/input_data/MOi" |
| 24 | + |
| 25 | +def run_benchmark(interpolator: str, trace_memory: bool = False, surface_simulation: bool =False, preload: bool = False, cycle_chunks: bool = False): |
| 26 | + |
| 27 | + lon0_expected, lat0_expected = -10.128929, -29.721205 # values from v3 using from_netcf (so assuming A-grid!) |
| 28 | + |
| 29 | + if cycle_chunks: |
| 30 | + xy_chunks = [64, 128, 256, 512, 1024, 2084, 32, 16, 8, 4] |
| 31 | + nparts = [10_000] |
| 32 | + else: |
| 33 | + xy_chunks = [256] |
| 34 | + nparts = [1, 10, 100, 1_000, 5_000, 10_000, 50_000, 100_000, 500_000, 1_000_000] |
| 35 | + fileU = f"{DATA_ROOT}/GLO12/psy4v3r1-daily_U_2010-01-0[1-3].nc" |
| 36 | + filenames = {"U": glob(fileU), "V": glob(fileU.replace("_U_", "_V_")), "W": glob(fileU.replace("_U_", "_W_"))} |
| 37 | + mesh_mask = f"{DATA_ROOT}/domain_ORCA0083-N006/PSY4V3R1_mesh_hgr.nc" |
| 38 | + |
| 39 | + for chunk in xy_chunks: |
| 40 | + if parcelsv4: |
| 41 | + if interpolator == "XLinear": |
| 42 | + interp_method = XLinear |
| 43 | + else: |
| 44 | + raise ValueError(f"Unknown interpolator: {interpolator}") |
| 45 | + |
| 46 | + fileargs = {"concat_dim": "time_counter", |
| 47 | + "combine": "nested", |
| 48 | + "data_vars": 'minimal', |
| 49 | + "coords": 'minimal', |
| 50 | + "compat": 'override', |
| 51 | + } |
| 52 | + if chunk: |
| 53 | + fileargs["chunks"] = {"time_counter": 1, "depth":2, "y": chunk, "x": chunk} |
| 54 | + |
| 55 | + ds_u = xr.open_mfdataset(filenames["U"], **fileargs)[["vozocrtx"]].drop_vars(["nav_lon", "nav_lat"]) |
| 56 | + ds_v = xr.open_mfdataset(filenames["V"], **fileargs)[["vomecrty"]].drop_vars(["nav_lon", "nav_lat"]) |
| 57 | + ds_depth = xr.open_mfdataset(filenames["W"], **fileargs)[["depthw"]] |
| 58 | + ds_mesh = xr.open_dataset(mesh_mask)[["glamf", "gphif"]].isel(t=0) |
| 59 | + |
| 60 | + ds = xr.merge([ds_u, ds_v, ds_depth, ds_mesh], compat="identical").rename({"vozocrtx": "U", "vomecrty": "V"}).rename({"glamf": "lon", "gphif": "lat", "time_counter": "time", "depthw": "depth"}) |
| 61 | + ds = xr.merge([ds_u, ds_v, ds_depth, ds_mesh], compat="identical") |
| 62 | + ds = ds.rename({"vozocrtx": "U", "vomecrty": "V", "glamf": "lon", "gphif": "lat", "time_counter": "time", "depthw": "depth"}) |
| 63 | + ds.deptht.attrs["c_grid_axis_shift"] = -0.5 |
| 64 | + |
| 65 | + coords={"X": {"left": "x"}, "Y": {"left": "y"}, "T": {"center": "time"}} |
| 66 | + |
| 67 | + coords={ |
| 68 | + "X": {"left": "x"}, |
| 69 | + "Y": {"left": "y"}, |
| 70 | + "T": {"center": "time"}, |
| 71 | + } |
| 72 | + if surface_simulation: |
| 73 | + ds = ds.isel(depth=0, deptht=0) |
| 74 | + else: |
| 75 | + coords["Z"] = {"center": "deptht", "left": "depth"} |
| 76 | + print(ds) |
| 77 | + |
| 78 | + grid = parcels._core.xgrid.XGrid(xgcm.Grid(ds, coords=coords, autoparse_metadata=False, periodic=False), mesh="spherical") |
| 79 | + |
| 80 | + U = parcels.Field("U", ds["U"], grid, interp_method=interp_method) |
| 81 | + V = parcels.Field("V", ds["V"], grid, interp_method=interp_method) |
| 82 | + U.units = parcels.GeographicPolar() |
| 83 | + V.units = parcels.Geographic() |
| 84 | + UV = parcels.VectorField("UV", U, V) |
| 85 | + |
| 86 | + fieldset = parcels.FieldSet([U, V, UV]) |
| 87 | + else: |
| 88 | + filenames = { |
| 89 | + "U": {"lon": mesh_mask, "lat": mesh_mask, "depth": filenames["W"][0], "data": filenames["U"]}, |
| 90 | + "V": {"lon": mesh_mask, "lat": mesh_mask, "depth": filenames["W"][0], "data": filenames["V"]}, |
| 91 | + } |
| 92 | + interpolator = "v3_default" |
| 93 | + if surface_simulation: |
| 94 | + indices={"depth": range(2)} |
| 95 | + else: |
| 96 | + indices=None |
| 97 | + |
| 98 | + fieldset = parcels.FieldSet.from_netcdf( |
| 99 | + filenames, |
| 100 | + variables={"U": "vozocrtx", "V": "vomecrty"}, |
| 101 | + dimensions={"time": "time_counter", "lat": "gphif", "lon": "glamf", "depth": "depthw"}, |
| 102 | + indices=indices, |
| 103 | + ) |
| 104 | + |
| 105 | + pclass = parcels.Particle if parcelsv4 else parcels.JITParticle |
| 106 | + |
| 107 | + if parcelsv4 and preload: |
| 108 | + fieldset.U.data.load() |
| 109 | + fieldset.V.data.load() |
| 110 | + |
| 111 | + for npart in nparts: |
| 112 | + if cycle_chunks: |
| 113 | + X, Y = np.meshgrid(np.linspace(-10, 10, int(np.sqrt(npart))), np.linspace(-30, -20, int(np.sqrt(npart)))) |
| 114 | + lon = X.flatten() |
| 115 | + lat = Y.flatten() |
| 116 | + else: |
| 117 | + lon = np.linspace(-10, 10, npart) |
| 118 | + lat = np.linspace(-30, -20, npart) |
| 119 | + |
| 120 | + pset = parcels.ParticleSet(fieldset=fieldset, pclass=pclass, lon=lon, lat=lat) |
| 121 | + |
| 122 | + print(f"Running {len(lon):_} particles on {"surface" if surface_simulation else "3D"} with parcels v{4 if parcelsv4 else 3}, chunksize {chunk} ({'preloaded' if preload else 'not preloaded'}) and {interpolator} interpolator") |
| 123 | + |
| 124 | + if trace_memory: |
| 125 | + tracemalloc.start() |
| 126 | + else: |
| 127 | + start = time.time() |
| 128 | + |
| 129 | + pset.execute(parcels.kernels.AdvectionEE, runtime=runtime, dt=dt, verbose_progress=False) |
| 130 | + |
| 131 | + if trace_memory: |
| 132 | + current, peak = tracemalloc.get_traced_memory() |
| 133 | + tracemalloc.stop() |
| 134 | + print(f"Memory usage: current={current / 1e6:.0f} MB, peak={peak / 1e6:.0f} MB") |
| 135 | + else: |
| 136 | + elapsed_time = time.time() - start |
| 137 | + print(f"Execution time: {elapsed_time:.0f} seconds") |
| 138 | + |
| 139 | + print("") |
| 140 | + |
| 141 | + if not cycle_chunks: |
| 142 | + assert np.allclose(pset[0].lon, lon0_expected, atol=1e-5), f"Expected lon {lon0_expected}, got {pset[0].lon}" |
| 143 | + assert np.allclose(pset[0].lat, lat0_expected, atol=1e-5), f"Expected lat {lat0_expected}, got {pset[0].lat}" |
| 144 | + |
| 145 | + |
| 146 | +def main(args=None): |
| 147 | + p = ArgumentParser() |
| 148 | + |
| 149 | + p.add_argument( |
| 150 | + "-i", |
| 151 | + "--Interpolator", |
| 152 | + choices=("XLinear", "BiRectiLinear", "PureXarrayInterp", "NoFieldAccess"), |
| 153 | + default="XLinear", |
| 154 | + ) |
| 155 | + |
| 156 | + p.add_argument( |
| 157 | + "-m", |
| 158 | + "--memory", |
| 159 | + action="store_true", |
| 160 | + help="Enable memory tracing (default: False)", |
| 161 | + ) |
| 162 | + |
| 163 | + p.add_argument( |
| 164 | + "-s", |
| 165 | + "--surface", |
| 166 | + action="store_true", |
| 167 | + help="Run surface simulation with only 1 or 2 depth levels (default: False)", |
| 168 | + ) |
| 169 | + |
| 170 | + p.add_argument( |
| 171 | + "-l", |
| 172 | + "--preload", |
| 173 | + action="store_true", |
| 174 | + help="Preload data into memory (default: False)", |
| 175 | + ) |
| 176 | + |
| 177 | + p.add_argument( |
| 178 | + "-c", |
| 179 | + "--chunks", |
| 180 | + action="store_true", |
| 181 | + help="Cycle through different chunk sizes (default: False)", |
| 182 | + ) |
| 183 | + |
| 184 | + args = p.parse_args(args) |
| 185 | + run_benchmark(args.Interpolator, args.memory, args.surface, args.preload, args.chunks) |
| 186 | + |
| 187 | + |
| 188 | +if __name__ == "__main__": |
| 189 | + main() |
0 commit comments