class Add(object): offset = xs.variable(description=('offset * dt added every time step ' 'to profile u')) u_diff = xs.variable(group='diff', intent='out') def run_step(self, dt): self.u_diff = self.offset * dt
def gen_properties(externs, modules, modulestoconsider = None, globaldependencies = {}, propertymapping = {}): """ Generate the properties of the xs Process class that will run the lpyfile """ import numpy as np externs = externs.difference(predefined_variables) if not modulestoconsider is None: mmodules = dict() for name, pnames in modules.items(): if name in modulestoconsider: mmodules[name] = pnames modules = mmodules properties = {} properties['modules'] = modules for m, v in modules.items(): properties[m] = xs.index(dims=m) for p in v: pname = m+'_'+p properties[pname] = propertymapping.get(pname, xs.global_ref(pname, intent='in') if pname in globaldependencies else xs.variable( dims=m, intent='out', encoding={'dtype': np.float})) properties['globaldependencies'] = globaldependencies properties['externs'] = externs for e in externs: properties[e] = xs.variable() return properties
class TotalVerticalMotion: """Sum up all vertical motions of bedrock and topographic surface, respectively. Vertical motions may result from external forcing, erosion and/or feedback of erosion on tectonics (isostasy). """ bedrock_upward_vars = xs.group('bedrock_upward') surface_upward_vars = xs.group('surface_upward') surface_downward_vars = xs.group('surface_downward') bedrock_upward = xs.variable( dims=('y', 'x'), intent='out', description='bedrock motion in upward direction' ) surface_upward = xs.variable( dims=('y', 'x'), intent='out', description='topographic surface motion in upward direction' ) def run_step(self): self.bedrock_upward = sum(self.bedrock_upward_vars) self.surface_upward = (sum(self.surface_upward_vars) - sum(self.surface_downward_vars))
class DifferentialLinearDiffusion(LinearDiffusion): """Hillslope differential erosion by diffusion. Diffusivity may vary depending on whether the topographic surface is bare rock or covered by a soil (sediment) layer. """ diffusivity_bedrock = xs.variable( dims=[(), ('y', 'x')], description='bedrock diffusivity' ) diffusivity_soil = xs.variable( dims=[(), ('y', 'x')], description='soil (sediment) diffusivity' ) diffusivity = xs.variable( dims=('y', 'x'), intent='out', description='differential diffusivity' ) soil_thickness = xs.foreign(UniformSedimentLayer, 'thickness') def run_step(self): self.diffusivity = np.where(self.soil_thickness <= 0., self.diffusivity_bedrock, self.diffusivity_soil) super(DifferentialLinearDiffusion, self).run_step()
class SourcePoint(object): """Source point for quantity `u`. The location of the source point is adjusted to coincide with the nearest node the grid. """ loc = xs.variable(description='source location') flux = xs.variable(description='source flux') x = xs.foreign(UniformGrid1D, 'x') u_source = xs.variable(dims='x', intent='out', group='u_vars') @property def nearest_node(self): idx = np.abs(self.x - self.loc).argmin() return idx @property def source_rate(self): src_array = np.zeros_like(self.x) src_array[self.nearest_node] = self.flux return src_array def run_step(self, dt): self.u_source = self.source_rate * dt
class SourcePoint: """Source point for quantity `u`. The location of the source point is adjusted to coincide with the nearest node the grid. """ loc = xs.variable(description="source location") flux = xs.variable(description="source flux") x = xs.foreign(UniformGrid1D, "x") u_source = xs.variable(dims="x", intent="out", groups="u_vars") @property def nearest_node(self): idx = np.abs(self.x - self.loc).argmin() return idx @property def source_rate(self): src_array = np.zeros_like(self.x) src_array[self.nearest_node] = self.flux return src_array @xs.runtime(args="step_delta") def run_step(self, dt): self.u_source = self.source_rate * dt
class LinearDiffusion: """Hillslope erosion by diffusion.""" diffusivity = xs.variable( dims=[(), ('y', 'x')], description='diffusivity (transport coefficient)' ) erosion = xs.variable( dims=('y', 'x'), intent='out', groups='erosion' ) shape = xs.foreign(UniformRectilinearGrid2D, 'shape') elevation = xs.foreign(SurfaceToErode, 'elevation') fs_context = xs.foreign(FastscapelibContext, 'context') def run_step(self): kd = np.broadcast_to(self.diffusivity, self.shape).flatten() self.fs_context["kd"] = kd # we don't use the kdsed fastscapelib-fortran feature directly # see class DifferentialLinearDiffusion self.fs_context["kdsed"] = -1. # bypass fastscapelib-fortran global state self.fs_context["h"] = self.elevation.flatten() fs.diffusion() erosion_flat = self.elevation.ravel() - self.fs_context["h"] self.erosion = erosion_flat.reshape(self.shape)
class TwoBlocksUplift: """Set two blocks separated by a clip plane, with different uplift rates. """ x_position = xs.variable( description='position of the clip plane along the x-axis') rate_left = xs.variable(description='uplift rate of the left block') rate_right = xs.variable(description='uplift rate of the right block') shape = xs.foreign(UniformRectilinearGrid2D, 'shape') x = xs.foreign(UniformRectilinearGrid2D, 'x') # TODO: group=['bedrock_forcing_upward', 'surface_forcing_upward'] # see https://github.com/benbovy/xarray-simlab/issues/64 uplift = xs.variable(dims=[(), ('y', 'x')], intent='out', group='any_forcing_upward', description='imposed vertical uplift') def initialize(self): # align clip plane position self._x_idx = np.argmax(self.x > self.x_position) @xs.runtime(args='step_delta') def run_step(self, dt): rate = np.full(self.shape, self.rate_left) rate[:, self._x_idx:] = self.rate_right self.uplift = rate * dt
class TotalVerticalMotion: """Sum up all vertical motions of bedrock and topographic surface, respectively. Vertical motions may result from external forcing, erosion and/or feedback of erosion on tectonics (isostasy). """ #TODO: remove any_upward_vars # see https://github.com/benbovy/xarray-simlab/issues/64 any_upward_vars = xs.group('any_upward') bedrock_upward_vars = xs.group('bedrock_upward') surface_upward_vars = xs.group('surface_upward') surface_downward_vars = xs.group('surface_downward') bedrock_upward = xs.variable( dims=('y', 'x'), intent='out', description='bedrock motion in upward direction') surface_upward = xs.variable( dims=('y', 'x'), intent='out', description='topographic surface motion in upward direction') def run_step(self): sum_any = sum(self.any_upward_vars) self.bedrock_upward = sum_any + sum(self.bedrock_upward_vars) self.surface_upward = (sum_any + sum(self.surface_upward_vars) - sum(self.surface_downward_vars))
class FlowAccumulator: """Accumulate the flow from upstream to downstream.""" runoff = xs.variable( dims=[(), ('y', 'x')], description='surface runoff (source term) per area unit') shape = xs.foreign(UniformRectilinearGrid2D, 'shape') cell_area = xs.foreign(UniformRectilinearGrid2D, 'cell_area') stack = xs.foreign(FlowRouter, 'stack') nb_receivers = xs.foreign(FlowRouter, 'nb_receivers') receivers = xs.foreign(FlowRouter, 'receivers') weights = xs.foreign(FlowRouter, 'weights') flowacc = xs.variable( dims=('y', 'x'), intent='out', description='flow accumulation from up to downstream') def run_step(self): field = np.broadcast_to(self.runoff * self.cell_area, self.shape).flatten() if self.receivers.ndim == 1: _flow_accumulate_sd(field, self.stack, self.receivers) else: _flow_accumulate_mfd(field, self.stack, self.nb_receivers, self.receivers, self.weights) self.flowacc = field.reshape(self.shape)
class BorderBoundary: status = xs.variable(dims=[(), 'border'], description='node status at borders') border = xs.variable(dims='border', intent='out', description='4-border boundaries coordinate') border_status = xs.variable( dims='border', intent='out', description='node status at the 4-border boundaries') fs_context = xs.foreign(FastscapelibContext, 'context') ibc = xs.variable(intent='out', description='boundary code used by fastscapelib-fortran') def initialize(self): self.border = np.array(['left', 'right', 'top', 'bottom']) self.border_status = np.broadcast_to(self.status, 4) # convert to fastscapelib-fortran ibc code arr_bc = np.array( [1 if st == 'fixed_value' else 0 for st in self.border_status]) # different border order self.ibc = sum(arr_bc * np.array([1, 100, 10, 1000])) self.fs_context.ibc = self.ibc
class ExampleProcess: """A process with complete interface for testing.""" in_var = xs.variable(dims=["x", ("x", "y")], description="input variable") out_var = xs.variable(groups="example_group", intent="out") inout_var = xs.variable(intent="inout", converter=int) od_var = xs.on_demand() obj_var = xs.any_object(description="arbitrary object") in_foreign_var = xs.foreign(SomeProcess, "some_var") in_foreign_var2 = xs.foreign(AnotherProcess, "some_var") out_foreign_var = xs.foreign(AnotherProcess, "another_var", intent="out") in_foreign_od_var = xs.foreign(SomeProcess, "some_od_var") in_global_var = xs.global_ref("some_global_var") out_global_var = xs.global_ref("another_global_var", intent="out") group_var = xs.group("some_group") group_dict_var = xs.group_dict("some_group") other_attrib = attr.attrib(init=False, repr=False) other_attr = "this is not a xsimlab variable attribute" @od_var.compute def compute_od_var(self): return 0
class ProfileZ: """Compute the evolution of the elevation (z) profile""" h_vars = xs.group( "h_vars" ) #allows for multiple processes influencing; say diffusion and subsidence z = xs.variable(dims="x", intent="inout", description="surface elevation z", attrs={"units": "m"}) br = xs.variable(dims=[(), "x"], intent="in", description="bedrock_elevation", attrs={"units": "m"}) h = xs.variable(dims="x", intent="inout", description="sed_thickness", attrs={"units": "m"}) def run_step(self): #self._delta_br = sum((br for br in self.br_vars)) self._delta_h = sum((h for h in self.h_vars)) def finalize_step(self): #self.br += self._delta_br #update bedrock surface self.h += self._delta_h #update sediment thickness self.z = self.br + self.h #add sediment to bedrock to get topo elev.
class DifferentialStreamPowerChannel(StreamPowerChannel): """Stream-Power channel (differential) erosion. Channel incision coefficient may vary depending on whether the topographic surface is bare rock or covered by a soil (sediment) layer. """ k_coef_bedrock = xs.variable( dims=[(), ('y', 'x')], description='bedrock channel incision coefficient') k_coef_soil = xs.variable( dims=[(), ('y', 'x')], description='soil (sediment) channel incision coefficient') k_coef = xs.variable( dims=('y', 'x'), intent='out', description='differential channel incision coefficient') active_layer_thickness = xs.foreign(UniformSedimentLayer, 'thickness') def run_step(self): self.k_coef = np.where(self.active_layer_thickness <= 0., self.k_coef_bedrock, self.k_coef_soil) super(DifferentialStreamPowerChannel, self).run_step()
class VariableTwoBlockUplift: """compute diferent linear gradient uplift rate along the x or y axis for two blocks separated by a clip plane""" x_position = xsimlab.variable( description='position of the clip plane along the x-axis') uplift_rate_left = xsimlab.variable( description='uplift rate of the left box') uplift_rate_right = xsimlab.variable( description='uplift rate of the right box') gradient_left = xsimlab.variable( description='gradient of the left box uplift') gradient_right = xsimlab.variable( description='gradient of the right box uplift') grid_shape = xsimlab.foreign(RasterGrid2D, 'shape') rate = xsimlab.foreign(BlockUplift, 'rate', intent='out') def run_step(self): mask = np.ones((self.grid_shape[0], self.grid_shape[1])) mask[:, 0:self.x_position] = self.uplift_rate_left mask[:, self.x_position:self.grid_shape[1]] = self.uplift_rate_right gradient = np.ones((self.grid_shape[1])) gradient[0:self.x_position] = np.linspace(self.gradient_left, 1, self.x_position) gradient[self.x_position:self.grid_shape[1]] = np.linspace( self.gradient_right, 1, np.abs(self.grid_shape[1] - self.x_position)) self.rate = mask * gradient
class Foo: v_bool_nan = xs.variable(dims="x", intent="out") # suppress nan values by setting an explicit fill value: v_bool = xs.variable(dims="x", intent="out", encoding={"fill_value": None}) def initialize(self): self.v_bool_nan = [True, False] self.v_bool = [True, False]
class Roll(object): shift = xs.variable(description=('shift profile by a nb. of points'), attrs={'units': 'unitless'}) u = xs.foreign(Profile, 'u') u_diff = xs.variable(dims='x', group='diff', intent='out') def run_step(self, *args): self.u_diff = np.roll(self.u, self.shift) - self.u
class Add: offset = xs.variable(description=("offset * dt added every time step " "to profile u")) u_diff = xs.variable(groups="diff", intent="out") @xs.runtime(args="step_delta") def run_step(self, dt): self.u_diff = self.offset * dt
class FixedGrid(UniformGrid1D): spacing = xs.variable(description="uniform spacing", intent="out") length = xs.variable(description="total length", intent="out") def initialize(self): self.spacing = 0.01 self.length = 1.0 super(FixedGrid, self).initialize()
class WithPlaceholder: """My process {{attributes}} """ var1 = xs.variable(dims="x", description="a variable") var2 = xs.variable()
class Foo: v1 = xs.variable(intent="out") v2 = xs.variable() def initialize(self): self.v1 = 0 def run_step(self): self.v1 = 1
class UniformGrid1D: """Create a 1-dimensional, equally spaced grid.""" spacing = xs.variable(description="uniform spacing", static=True) length = xs.variable(description="total length", static=True) x = xs.index(dims="x") def initialize(self): self.x = np.arange(0, self.length, self.spacing)
class P2: var = xs.foreign(P1, "var") cached_var = xs.foreign(P1, "cached_var") view = xs.variable(dims="x", intent="out") cached_view = xs.variable(dims="x", intent="out") def run_step(self): self.view = self.var self.cached_view = self.cached_var
class UniformGrid1D(object): """Create a 1-dimensional, equally spaced grid.""" spacing = xs.variable(description='uniform spacing') length = xs.variable(description='total length') x = xs.variable(dims='x', intent='out') def initialize(self): self.x = np.arange(0, self.length, self.spacing)
class P: in_var = xs.variable(dims=[(), "x"]) out_var = xs.variable(dims=[(), "x"], intent="out") idx_var = xs.index(dims="x") def initialize(self): self.idx_var = [0, 1] def run_step(self): self.out_var = self.in_var * 2
class InitUGauss: """Initialize `u` profile using a Gaussian pulse.""" loc = xs.variable(description="location of initial pulse", static=True) scale = xs.variable(description="scale of initial pulse", static=True) x = xs.foreign(UniformGrid1D, "x") u = xs.foreign(ProfileU, "u", intent="out") def initialize(self): self.u = np.exp(-1 / self.scale ** 2 * (self.x - self.loc) ** 2)
class InitUGauss(object): """Initialize `u` profile using a Gaussian pulse.""" loc = xs.variable(description='location of initial pulse') scale = xs.variable(description='scale of initial pulse') x = xs.foreign(UniformGrid1D, 'x') u = xs.foreign(ProfileU, 'u', intent='out') def initialize(self): self.u = np.exp(-1 / self.scale**2 * (self.x - self.loc)**2)
class StreamPowerChannel(ChannelErosion): """Stream-Power channel erosion.""" k_coef = xs.variable(dims=[(), ('y', 'x')], description='bedrock channel incision coefficient') area_exp = xs.variable(description='drainage area exponent') slope_exp = xs.variable(description='slope exponent') shape = xs.foreign(UniformRectilinearGrid2D, 'shape') elevation = xs.foreign(FlowRouter, 'elevation') receivers = xs.foreign(FlowRouter, 'receivers') flowacc = xs.foreign(FlowAccumulator, 'flowacc') fs_context = xs.foreign(FastscapelibContext, 'context') chi = xs.on_demand(dims=('y', 'x'), description='integrated drainage area (chi)') def _set_g_in_context(self): # transport/deposition feature is exposed in subclasses self.fs_context.g1 = 0. self.fs_context.g2 = 0. def run_step(self): kf = np.broadcast_to(self.k_coef, self.shape).flatten() self.fs_context.kf = kf # we don't use kfsed fastscapelib-fortran feature directly self.fs_context.kfsed = -1. self._set_g_in_context() self.fs_context.m = self.area_exp self.fs_context.n = self.slope_exp # bypass fastscapelib_fortran global state self.fs_context.h = self.elevation.flatten() # TODO: https://github.com/fastscape-lem/fastscapelib-fortran/pull/25 # this has no effect yet. self.fs_context.a = self.flowacc.flatten() if self.receivers.ndim == 1: fs.streampowerlawsingleflowdirection() else: fs.streampowerlaw() erosion_flat = self.elevation.ravel() - self.fs_context.h self.erosion = erosion_flat.reshape(self.shape) @chi.compute def _chi(self): chi_arr = np.empty_like(self.elevation, dtype='d') self.fs_context.copychi(chi_arr.ravel()) return chi_arr
class P: in_var = xs.variable() out_var = xs.variable(intent="out") od_var = xs.on_demand() def run_step(self): self.out_var = self.in_var * 2 @od_var.compute def _dummy(self): return None
class P: var = xs.variable( dims=[(), "x"], description="a variable", default=0, groups=["g1", "g2"], static=True, attrs={"units": "m"}, encoding={"fill_value": -1}, ) var2 = xs.variable()