|
| 1 | + |
| 2 | +from mpi4py import MPI |
| 3 | +import json |
| 4 | +import os |
| 5 | +import numpy as np |
| 6 | +import landlab |
| 7 | +from landlab.components import LinearDiffuser |
| 8 | +from landlab.components import FlowAccumulator |
| 9 | +from landlab.components import StreamPowerEroder |
| 10 | + |
| 11 | +from landlab.io.legacy_vtk import write_legacy_vtk |
| 12 | + |
| 13 | +current_time = 0 |
| 14 | + |
| 15 | +comm = None |
| 16 | + |
| 17 | +model_grid = None |
| 18 | +elevation = None |
| 19 | +linear_diffuser = None |
| 20 | +flow_accumulator = None |
| 21 | +stream_power_eroder = None |
| 22 | + |
| 23 | +s2yr = 365.25 * 24 * 3600 |
| 24 | +timestep = 0 |
| 25 | +vtks = [] |
| 26 | + |
| 27 | +def initialize(comm_handle): |
| 28 | + if not comm_handle is None: |
| 29 | + # Convert the handle back to an MPI communicator |
| 30 | + global comm |
| 31 | + comm = MPI.Comm.f2py(comm_handle) |
| 32 | + |
| 33 | + rank = comm.Get_rank() |
| 34 | + size = comm.Get_size() |
| 35 | + |
| 36 | + print(f"Python: Hello from Rank {rank} of {size}") |
| 37 | + |
| 38 | + data = 1 |
| 39 | + globalsum = comm.allreduce(data, op=MPI.SUM) |
| 40 | + if comm.rank == 0: |
| 41 | + print(f"\tPython: testing communication; sum {globalsum}") |
| 42 | + else: |
| 43 | + print("Python: running sequentially!") |
| 44 | + |
| 45 | +def finalize(): |
| 46 | + pass |
| 47 | + |
| 48 | + |
| 49 | +# Run the Landlab simulation from the current time to end_time and return |
| 50 | +# the new topographic elevation (in m) at each local node. |
| 51 | +# dict_variable_name_to_value_in_nodes is a dictionary mapping variables |
| 52 | +# (x velocity, y velocity, temperature, etc.) to an array of values in each |
| 53 | +# node. |
| 54 | +def update_until(end_time, dict_variable_name_to_value_in_nodes): |
| 55 | + global current_time, elevation, linear_diffuser, flow_accumulator, stream_power_eroder, timestep |
| 56 | + |
| 57 | + dt = end_time - current_time |
| 58 | + timestep += 1 |
| 59 | + |
| 60 | + deposition_erosion = np.zeros(model_grid.number_of_nodes) |
| 61 | + |
| 62 | + slice_x_velocity = dict_variable_name_to_value_in_nodes["x velocity"] |
| 63 | + slice_y_velocity = dict_variable_name_to_value_in_nodes["y velocity"] |
| 64 | + |
| 65 | + # In the current setup, the y_velocity vector is ONLY nonzero along y=0. We need |
| 66 | + # to project the velocity outwards along all fixed values of y, assigning the same |
| 67 | + # velocity to all nodes that share the same x value. |
| 68 | + |
| 69 | + # This code below first extract the velocities along y=0 (where they are correctly coming |
| 70 | + # from ASPECT), then it finds all of the unique y values in the LandLab mesh, and then assigned |
| 71 | + # the y=0 velocities to these y-values. This will only work for structured RasterGrids. |
| 72 | + |
| 73 | + vertical_velocity = np.zeros(model_grid.number_of_nodes) |
| 74 | + unique_x_values = np.unique(model_grid.x_of_node) |
| 75 | + for x in unique_x_values: |
| 76 | + vertical_velocity[model_grid.x_of_node == x] = slice_y_velocity[unique_x_values == x] |
| 77 | + |
| 78 | + # Substepping for surface processes |
| 79 | + if dt>0: |
| 80 | + n_substeps = 10 |
| 81 | + sub_dt = dt / n_substeps |
| 82 | + for _ in range(n_substeps): |
| 83 | + |
| 84 | + # TODO: |
| 85 | + elevation_before = elevation.copy() |
| 86 | + |
| 87 | + flow_accumulator.run_one_step() |
| 88 | + stream_power_eroder.run_one_step(sub_dt) |
| 89 | + linear_diffuser.run_one_step(sub_dt) |
| 90 | + |
| 91 | + elevation += vertical_velocity * sub_dt |
| 92 | + |
| 93 | + deposition_erosion += elevation - elevation_before |
| 94 | + pass |
| 95 | + |
| 96 | + filename = f"./output-2D-T-MODELS/landlab_{str(timestep).zfill(3)}.vtk" |
| 97 | + print("Writing output VTK file...", filename) |
| 98 | + vtk_file = write_legacy_vtk(path=filename, grid=model_grid, clobber=True, z_at_node=elevation) |
| 99 | + |
| 100 | + vtks.append((current_time, filename)) |
| 101 | + |
| 102 | + if True: |
| 103 | + # write vtk.series file (ParaView supports legacy VTK in this format) |
| 104 | + with open("./output-2D-T-MODELS/landlab.vtk.series", "w") as f: |
| 105 | + series = { |
| 106 | + "file-series-version": "1.0", |
| 107 | + "files": [ |
| 108 | + {"name": os.path.basename(filename), "time": time / s2yr} |
| 109 | + for time, filename in vtks |
| 110 | + ] |
| 111 | + } |
| 112 | + json.dump(series, f, indent=2) |
| 113 | + |
| 114 | + current_time = end_time |
| 115 | + print("Max elevation:", np.max(elevation), "Min elevation:", np.min(elevation)) |
| 116 | + |
| 117 | + # This setup does not do any averaging of the topography across the LandLab mesh before returning |
| 118 | + # the change in elevation used to deform the ASPECT mesh. Averaging is straight forward to |
| 119 | + # do going forward, and the commented line in the for loop below shows how this would be done. |
| 120 | + # However, for the purposes of this test I want to make sure the ASPECT mesh is |
| 121 | + # deforming to the exact same topography as the LandLab mesh where they intersect. |
| 122 | + deposition_erosion_2d = np.zeros(len(np.unique(model_grid.x_of_node))) |
| 123 | + for x in unique_x_values: |
| 124 | + # deposition_erosion_2d[unique_x_values == x] = np.average(deposition_erosion[model_grid.x_of_node == x]) |
| 125 | + deposition_erosion_2d = deposition_erosion[model_grid.y_of_node == 0] |
| 126 | + |
| 127 | + return deposition_erosion_2d |
| 128 | + |
| 129 | +def set_mesh_information(dict_grid_information): |
| 130 | + global model_grid, elevation |
| 131 | + |
| 132 | + if not model_grid: |
| 133 | + print("* Creating RasterModelGrid ...") |
| 134 | + x_extent = 100e3 |
| 135 | + y_extent = 100e3 |
| 136 | + spacing = 2500 |
| 137 | + |
| 138 | + nrows = int(y_extent / spacing) + 1 # number of node rows |
| 139 | + ncols = int(x_extent / spacing) + 1 # number of node columns |
| 140 | + |
| 141 | + # For 2D models, shift the LandLab mesh so that it is centered on the y-axis. |
| 142 | + model_grid = landlab.RasterModelGrid((nrows, ncols), xy_spacing=(spacing, spacing), xy_of_lower_left=(0, -y_extent / 2)) |
| 143 | + |
| 144 | + model_grid.set_closed_boundaries_at_grid_edges(right_is_closed=True, |
| 145 | + left_is_closed=False, |
| 146 | + top_is_closed=True, |
| 147 | + bottom_is_closed=True) |
| 148 | + |
| 149 | + print("* Creating topographic elevation ...") |
| 150 | + # Initialize topography array with zeros |
| 151 | + elevation = model_grid.add_zeros("topographic__elevation", at="node") |
| 152 | + # Assign 1000 m high topography with random noise |
| 153 | + np.random.seed(0) # For reproducibility |
| 154 | + elevation += 1000 + np.random.rand(model_grid.number_of_nodes) |
| 155 | + |
| 156 | + initialize_landlab_components(None) |
| 157 | + print("* Done") |
| 158 | + |
| 159 | +# Return the x coordinates of the locally owned nodes on this |
| 160 | +# MPI rank. grid_id is always 0. |
| 161 | +def get_grid_x(grid_dictionary): |
| 162 | + global model_grid |
| 163 | + return np.unique(model_grid.x_of_node) |
| 164 | + |
| 165 | +# Return the y coordinates of the locally owned nodes on this |
| 166 | +# MPI rank. grid_id is always 0. |
| 167 | +def get_grid_y(grid_dictionary): |
| 168 | + global model_grid |
| 169 | + return np.zeros(np.unique(model_grid.x_of_node).shape) |
| 170 | + |
| 171 | +def initialize_landlab_components(grid_dictionary): |
| 172 | + global model_grid, elevation, linear_diffuser, flow_accumulator, stream_power_eroder |
| 173 | + |
| 174 | + D = 1e-4 / s2yr |
| 175 | + K_sp = 1e-5 / s2yr |
| 176 | + flow_director = "D8" # Only flow director supported for HexGrids |
| 177 | + |
| 178 | + |
| 179 | + print("* Creating LinearDiffuser ... with D =", D) |
| 180 | + linear_diffuser = LinearDiffuser(model_grid, linear_diffusivity=D) |
| 181 | + print("* Creating FlowAccumulator ... with flow director =", flow_director) |
| 182 | + flow_accumulator = FlowAccumulator(model_grid, elevation, flow_director=flow_director) |
| 183 | + print("* Creating StreamPowerEroder ... with K_sp =", K_sp) |
| 184 | + stream_power_eroder = StreamPowerEroder(model_grid, K_sp=K_sp) |
| 185 | + pass |
| 186 | + |
| 187 | +# Return the initial topography at the start of the simulation |
| 188 | +# in each node. |
| 189 | +def get_initial_topography(grid_dictionary): |
| 190 | + global elevation |
| 191 | + return elevation[model_grid.y_of_node == 0] |
0 commit comments