|
| 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 cartesian_to_spherical(x, y, z): |
| 28 | + radius = np.sqrt(x**2 + y**2 + z**2) |
| 29 | + theta = np.rad2deg(np.arccos(z / radius)) |
| 30 | + phi = np.rad2deg(np.arctan2(y, x)) |
| 31 | + |
| 32 | + return radius, theta, phi |
| 33 | + |
| 34 | +def initialize(comm_handle): |
| 35 | + if not comm_handle is None: |
| 36 | + # Convert the handle back to an MPI communicator |
| 37 | + global comm |
| 38 | + comm = MPI.Comm.f2py(comm_handle) |
| 39 | + |
| 40 | + rank = comm.Get_rank() |
| 41 | + size = comm.Get_size() |
| 42 | + |
| 43 | + print(f"Python: Hello from Rank {rank} of {size}") |
| 44 | + |
| 45 | + data = 1 |
| 46 | + globalsum = comm.allreduce(data, op=MPI.SUM) |
| 47 | + if comm.rank == 0: |
| 48 | + print(f"\tPython: testing communication; sum {globalsum}") |
| 49 | + else: |
| 50 | + print("Python: running sequentially!") |
| 51 | + |
| 52 | +def finalize(): |
| 53 | + pass |
| 54 | + |
| 55 | + |
| 56 | +# Run the Landlab simulation from the current time to end_time and return |
| 57 | +# the new topographic elevation (in m) at each local node. |
| 58 | +# dict_variable_name_to_value_in_nodes is a dictionary mapping variables |
| 59 | +# (x velocity, y velocity, temperature, etc.) to an array of values in each |
| 60 | +# node. |
| 61 | +def update_until(end_time, dict_variable_name_to_value_in_nodes): |
| 62 | + global current_time, elevation, linear_diffuser, flow_accumulator, stream_power_eroder, timestep |
| 63 | + |
| 64 | + dt = end_time - current_time |
| 65 | + timestep += 1 |
| 66 | + |
| 67 | + deposition_erosion = np.zeros(model_grid.number_of_nodes) |
| 68 | + |
| 69 | + x_velocity = dict_variable_name_to_value_in_nodes["x velocity"] |
| 70 | + y_velocity = dict_variable_name_to_value_in_nodes["y velocity"] |
| 71 | + z_velocity = dict_variable_name_to_value_in_nodes["z velocity"] |
| 72 | + |
| 73 | + radial_velocity = np.sqrt(x_velocity**2 + y_velocity**2 + z_velocity**2) |
| 74 | + |
| 75 | + # Substepping for surface processes |
| 76 | + if dt>0: |
| 77 | + n_substeps = 10 |
| 78 | + sub_dt = dt / n_substeps |
| 79 | + for _ in range(n_substeps): |
| 80 | + |
| 81 | + # TODO: |
| 82 | + elevation_before = elevation.copy() |
| 83 | + |
| 84 | + #flow_accumulator.run_one_step() |
| 85 | + #stream_power_eroder.run_one_step(sub_dt) |
| 86 | + linear_diffuser.run_one_step(sub_dt) |
| 87 | + |
| 88 | + elevation += radial_velocity * sub_dt |
| 89 | + |
| 90 | + deposition_erosion += elevation - elevation_before |
| 91 | + pass |
| 92 | + |
| 93 | + filename = f"./output-spherical-landlab/landlab_{str(timestep).zfill(3)}.vtk" |
| 94 | + print("Writing output VTK file...", filename) |
| 95 | + vtk_file = write_legacy_vtk(path=filename, grid=model_grid, clobber=True, z_at_node=elevation) |
| 96 | + |
| 97 | + vtks.append((current_time, filename)) |
| 98 | + |
| 99 | + if True: |
| 100 | + # write vtk.series file (ParaView supports legacy VTK in this format) |
| 101 | + with open("./output-spherical-landlab/landlab.vtk.series", "w") as f: |
| 102 | + series = { |
| 103 | + "file-series-version": "1.0", |
| 104 | + "files": [ |
| 105 | + {"name": os.path.basename(filename), "time": time / s2yr} |
| 106 | + for time, filename in vtks |
| 107 | + ] |
| 108 | + } |
| 109 | + json.dump(series, f, indent=2) |
| 110 | + |
| 111 | + current_time = end_time |
| 112 | + print("Max elevation:", np.max(elevation), |
| 113 | + "Min elevation:", np.min(elevation), |
| 114 | + "min deposition:", np.min(deposition_erosion), |
| 115 | + "max deposition:", np.max(deposition_erosion)) |
| 116 | + |
| 117 | + return deposition_erosion.copy() |
| 118 | + |
| 119 | +def set_mesh_information(dict_grid_information): |
| 120 | + global model_grid, elevation |
| 121 | + |
| 122 | + if not model_grid: |
| 123 | + print("* Creating Spherical Grid ...") |
| 124 | + #x_extent = dict_grid_information["Mesh X extent"] |
| 125 | + #y_extent = dict_grid_information["Mesh Y extent"] |
| 126 | + #spacing = dict_grid_information["Mesh Spacing"] |
| 127 | + |
| 128 | + model_grid = landlab.IcosphereGlobalGrid(radius=6371e3*0.99, mesh_densification_level=3) |
| 129 | + print(type(model_grid.x_of_node),type(model_grid.y_of_node),type(model_grid.z_of_node)) |
| 130 | + for i in range(len(model_grid.x_of_node)): |
| 131 | + x=model_grid.x_of_node[i] |
| 132 | + y=model_grid.y_of_node[i] |
| 133 | + z=model_grid.z_of_node[i] |
| 134 | + print(i,x,y,z,np.sqrt(x*x+y*y+z*z)) |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | + |
| 139 | + print("model grid nodes", len(model_grid.x_of_node)) |
| 140 | + r, theta, phi = cartesian_to_spherical(model_grid.x_of_node, \ |
| 141 | + model_grid.y_of_node, \ |
| 142 | + model_grid.z_of_node) |
| 143 | + |
| 144 | + elevation = model_grid.add_zeros("topographic__elevation", at="node") |
| 145 | + elevation += 1e3 + np.random.rand(elevation.size) * 0.1e3 |
| 146 | + #elevation[theta < 90] += 1000 |
| 147 | + print(elevation) |
| 148 | + |
| 149 | + initialize_landlab_components(None) |
| 150 | + print("* Done") |
| 151 | + |
| 152 | +# Return the x coordinates of the locally owned nodes on this |
| 153 | +# MPI rank. grid_id is always 0. |
| 154 | +def get_grid_x(grid_dictionary): |
| 155 | + global model_grid |
| 156 | + #dim = grid_dictionary["ASPECT Dimension"] |
| 157 | + #if dim == 2: |
| 158 | + # print(np.unique(model_grid.x_of_node)) |
| 159 | + # return np.unique(model_grid.x_of_node) |
| 160 | + #if dim == 3: |
| 161 | + return model_grid.x_of_node.copy() |
| 162 | + |
| 163 | +# Return the y coordinates of the locally owned nodes on this |
| 164 | +# MPI rank. grid_id is always 0. |
| 165 | +def get_grid_y(grid_dictionary): |
| 166 | + global model_grid |
| 167 | + return model_grid.y_of_node.copy() |
| 168 | +def get_grid_z(grid_dictionary): |
| 169 | + global model_grid |
| 170 | + return model_grid.z_of_node.copy() |
| 171 | + |
| 172 | +def initialize_landlab_components(landlab_component_parameters): |
| 173 | + global model_grid, elevation, linear_diffuser, flow_accumulator, stream_power_eroder |
| 174 | + |
| 175 | + D = 1e-4 #landlab_component_parameters["Hillslope coefficient"] |
| 176 | + K_sp = 1e-5 #landlab_component_parameters["Stream power erodibility coefficient"] |
| 177 | + flow_director = "Steepest" # Only flow director supported for HexGrids |
| 178 | + |
| 179 | + |
| 180 | + print("* Creating LinearDiffuser ... with D =", D) |
| 181 | + linear_diffuser = LinearDiffuser(model_grid, linear_diffusivity=D) |
| 182 | + print("* Creating FlowAccumulator ... with flow director =", flow_director) |
| 183 | + flow_accumulator = FlowAccumulator(model_grid, elevation, flow_director=flow_director) |
| 184 | + print("* Creating StreamPowerEroder ... with K_sp =", K_sp) |
| 185 | + stream_power_eroder = StreamPowerEroder(model_grid, K_sp=K_sp) |
| 186 | + |
| 187 | + pass |
| 188 | +# Return the initial topography at the start of the simulation |
| 189 | +# in each node. |
| 190 | +def get_initial_topography(grid_dictionary): |
| 191 | + global elevation |
| 192 | + #dim = grid_dictionary["ASPECT Dimension"] |
| 193 | + #if dim == 2: |
| 194 | + # return elevation[model_grid.y_of_node == 0] |
| 195 | + #if dim == 3: |
| 196 | + return elevation |
| 197 | + |
0 commit comments