def test_index(): with pytest.raises(ValueError, match=r".*not accept scalar values.*"): xs.index(()) # test constructor @attr.attrs class Foo: var = xs.index(dims="x") with pytest.raises(TypeError): # index variable not in contructor (intent='out') Foo(var=2)
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 UniformRectilinearGrid2D: """Create a uniform rectilinear (static) 2-dimensional grid.""" shape = xs.variable(dims='shape_yx', description='nb. of grid nodes in (y, x)', static=True) spacing = xs.variable(dims='shape_yx', description='grid node spacing in (y, x)', static=True) origin = xs.variable(dims='shape_yx', description='(y, x) coordinates of grid origin', static=True) length = xs.variable(dims='shape_yx', intent='out', description='total grid length in (y, x)') size = xs.variable(intent='out', description='total nb. of nodes') area = xs.variable(intent='out', description='total grid area') cell_area = xs.variable(intent='out', description='fixed grid cell area') dx = xs.variable(intent='out', description="grid spacing in x (cols)") dy = xs.variable(intent='out', description="grid spacing in y (rows)") nx = xs.variable(intent='out', description="nb. of nodes in x (cols)") ny = xs.variable(intent='out', description="nb. of nodes in y (rows)") x = xs.index(dims='x', description='grid x coordinate') y = xs.index(dims='y', description='grid y coordinate') def _set_length_or_spacing(self): self.length = (self.shape - 1) * self.spacing def initialize(self): self._set_length_or_spacing() self.size = np.prod(self.shape) self.area = np.prod(self.length) self.cell_area = np.prod(self.spacing) self.dx = self.spacing[1] self.dy = self.spacing[0] self.nx = self.shape[1] self.ny = self.shape[0] self.x = np.linspace(self.origin[1], self.origin[1] + self.length[1], self.shape[1]) self.y = np.linspace(self.origin[0], self.origin[0] + self.length[0], self.shape[0])
class Foo: var = xs.variable(global_name="global_var") idx = xs.index(dims="x", global_name="global_idx") obj = xs.any_object(global_name="global_obj") def initialize(self): self.idx = np.array([1, 1]) self.obj = 2
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 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 P: v1 = xs.variable(dims="x", intent="out", encoding={"dtype": np.int32}) v2 = xs.on_demand(dims="x", encoding={"fill_value": 0}) v3 = xs.index(dims="x") @v2.compute def _get_v2(self): return [0]
class InitProfile: n_points = xs.variable(description="nb. of profile points", converter=int, static=True) x = xs.index(dims="x") u = xs.foreign(Profile, "u", intent="out") def initialize(self): self.x = np.arange(self.n_points) self.u = np.zeros(self.n_points) self.u[0] = 1.0
class UniformGrid1D: """Create 1D model grid with uniform spacing""" #grid parameters spacing = xs.variable(description="grid_spacing", static=True) length = xs.variable(description="grid total length", static=True) #x is an index variable, used for accessing the grid. x = xs.index(dims="x") #create the grid def initialize(self): self.x = np.arange(0, self.length, self.spacing)
class P: v1 = xs.variable(dims="x", intent="out", encoding={"dtype": np.int32}) v2 = xs.on_demand(dims="x", encoding={"fill_value": 0}) v3 = xs.index(dims="x") v4 = xs.variable( dims="x", intent="out", encoding={ "dtype": object, "object_codec": zarr.codecs.Pickle() }, ) @v2.compute def _get_v2(self): return [0]
class StratigraphicHorizons: """Generate a fixed number of stratigraphic horizons. A horizon is active, i.e., it tracks the evolution of the land/submarine topographic surface until it is "frozen" at a given time. Beyond this freezing (or deactivation) time, the horizon will only be affected by tectonic deformation and/or erosion. To compute diagnostics on those horizons, you can create a subclass where you can add "on_demand" variables. """ freeze_time = xs.variable( dims='horizon', description='horizon freezing (deactivation) time', static=True ) horizon = xs.index(dims='horizon', description='horizon number') active = xs.variable( dims='horizon', intent='out', description='whether the horizon is active or not' ) surf_elevation = xs.foreign(SurfaceTopography, 'elevation') elevation_motion = xs.foreign(TotalVerticalMotion, 'surface_upward') bedrock_motion = xs.foreign(TotalVerticalMotion, 'bedrock_upward') elevation = xs.variable( dims=('horizon', 'y', 'x'), intent='out', description='elevation of horizon surfaces' ) @xs.runtime(args='sim_start') def initialize(self, start_time): if np.any(self.freeze_time < start_time): raise ValueError("'freeze_time' value must be greater than the " "time of the beginning of the simulation") self.elevation = np.repeat(self.surf_elevation[None, :, :], self.freeze_time.size, axis=0) self.horizon = np.arange(0, len(self.freeze_time)) self.active = np.full_like(self.freeze_time, True, dtype=bool) @xs.runtime(args='step_start') def run_step(self, current_time): self.active = current_time < self.freeze_time def finalize_step(self): elevation_next = self.surf_elevation + self.elevation_motion self.elevation[self.active] = elevation_next self.elevation[~self.active] = np.minimum( self.elevation[~self.active] + self.bedrock_motion, elevation_next )
class BorderBoundary: """Sets boundary conditions at grid borders. Borders are defined in the following order: left, right, top, bottom Border status can be one of: - "core" (open boundary) - "fixed_value" (closed boundary) - "looped" (periodic boundary) "fixed_value" must be set for at least one border. This is the minimal constraint in order to make the numerical model solvable. "looped" must be symmetric, i.e., defined for (left, right) or (top, bottom). Note that currently if "core" is set for two opposed borders these will have periodic conditions (this comes from a current limitation in fastscapelib-fortran which will be solved in a next release). """ status = xs.variable(dims=[(), 'border'], default="fixed_value", description='node status at borders', static=True) border = xs.index(dims='border', description='4-border boundaries coordinate') border_status = xs.variable( dims='border', intent='out', description='node status at the 4-border boundaries') ibc = xs.variable(intent='out', description='boundary code used by fastscapelib-fortran') @status.validator def _check_status(self, attribute, value): if not np.isscalar(value) and len(value) != 4: raise ValueError("Border status should be defined for all borders " f"(left, right, top, bottom), found {value}") valid = ["fixed_value", "core", "looped"] bs = list(np.broadcast_to(value, 4)) for s in bs: if s not in valid: raise ValueError( f"Invalid border status {s!r}, must be one of {valid}") if "fixed_value" not in bs: raise ValueError( f"There must be at least one border with status 'fixed_value', found {bs}" ) def invalid_looped(s): return bool(s[0] == "looped") ^ bool(s[1] == "looped") if invalid_looped(bs[:2]) or invalid_looped(bs[2:]): raise ValueError( f"Periodic boundary conditions must be symmetric, found {bs}") def initialize(self): self.border = np.array(['left', 'right', 'top', 'bottom']) bstatus = np.array(np.broadcast_to(self.status, 4)) # TODO: remove when solved in fastscapelib-fortran w_msg_common = ( "borders have both 'core' status but periodic conditions " "are used due to current behavior in fastscapelib-fortran") if (bstatus[0] == "core" and bstatus[1] == "core"): w_msg = "Left and right " + w_msg_common warnings.warn(w_msg, UserWarning) if (bstatus[2] == "core" and bstatus[3] == "core"): w_msg = "Top and bottom " + w_msg_common warnings.warn(w_msg, UserWarning) self.border_status = bstatus # 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, 1000, 10]))
class Foo: var = xs.index(dims="x")