Module curlew.synthetic
Generate synthetic datasets and models for testing purposes.
Functions
def anderson(shape=(1500, 1000), offset1=225, offset2=250, **kwargs)-
Expand source code
def anderson( shape=(1500,1000), offset1=225, offset2=250, **kwargs ): """ Return a synthetic model with a slightly curved layer-cake stratigraphy cut by two intersecting normal faults. Parameters ---------- shape : tuple The width and height of the generated data. offset1 : tuple The offset of the first (older) normal fault. offet2 : tuple The offset of the second (younger) normal fault. Keywords --------- All keywords are passed to `curlew.data.sample(...)` Returns -------- xy : np.ndarray A list of (N,d) xy points (from a grid with the specified `shape`) s : np.ndarray, An array of shape (N,2) containing the scalar values and ID of the "event" that caused them. C : list, A list of constraints for each of the (two) events in this synthetic model. M : GeoModel Geomodel of the synthetic model """ G = grid( shape, step=(1,1), center=(shape[0]/2,shape[1]/2) ) s0 = strati('s0', C=QuadraticField( 'f0', input_dim=2, curve=(-0.00005,0), origin=(1000,500) ) ) s1 = fault( 's1', C=LinearField( 'f1', input_dim=2, origin=(950,550), gradient=(np.cos( np.deg2rad(35) ), np.sin( np.deg2rad(35) )) ), offset=offset1, shortening = [0,-1] ) # extensional faults s2 = fault( 's2', C=LinearField( 'f2', input_dim=2, origin=(1050,500), gradient=(-np.cos( np.deg2rad(35) ), np.sin( np.deg2rad(35) )) ), offset=offset2, shortening = [0,-1] ) # extensional faults for y in np.linspace(0, shape[1], 5): # generate 5 contacts s0.addIsosurface( 'i'+str(int(y)), seed=np.array([shape[0]/2, y]) ) M = GeoModel( [s0, s1, s2], grid=G, name='anderson' ) s = M.predict(G) C = sample( s, pv='rgb', **kwargs ) if 'breaks' in kwargs: del kwargs['breaks'] if 'pv' in kwargs: del kwargs['pv'] kwargs['pval'] = kwargs.get('pval', 1.0) # change default to sample all value constraints Cf1 = sample( s1.predict(G), pv='rgb', breaks=[0.5],xstep=600, **kwargs ) Cf2 = sample( s2.predict(G), pv='rgb', breaks=[0.5], **kwargs ) C = [C[0], Cf1[0], Cf2[0], C[-1]] # combine stratigraphy, fault and property constraints mask = (C[0].gv[:,1] > 0.9) & (C[0].gv[:,1] < 1.1) # mask incorrect bebbing constraints near the faults C[0].gp = C[0].gp[mask] # mask the gp constraints C[0].gv = C[0].gv[mask] # mask the gv constraints C[0].gop = C[0].gop[mask] # mask the gop constraints C[0].gov = C[0].gov[mask] # mask the gov constraints C[1].vv = C[1].vv * 0 # ensure value constraints are exactly zero (fault surface = 0) C[2].vv = C[2].vv * 0 # ensure value constraints are exactly zero (fault surface = 0) return C, M # returnReturn a synthetic model with a slightly curved layer-cake stratigraphy cut by two intersecting normal faults.
Parameters
shape:tuple- The width and height of the generated data.
offset1:tuple- The offset of the first (older) normal fault.
offet2:tuple- The offset of the second (younger) normal fault.
Keywords
All keywords are passed to <code>curlew.data.sample(...)</code>Returns
xy:np.ndarray- A list of (N,d) xy points (from a grid with the specified
shape) s:np.ndarray,- An array of shape (N,2) containing the scalar values and ID of the "event" that caused them.
C:list,- A list of constraints for each of the (two) events in this synthetic model.
M:GeoModel- Geomodel of the synthetic model
def hutton(shape=(1500, 1000), **kwargs)-
Expand source code
def hutton( shape=(1500,1000), **kwargs ): """ Return a synthetic model with a folded basement cut by an unconformity. Parameters ---------- shape : tuple The width and height of the generated data. Keywords --------- All keywords are passed to `curlew.data.sample(...)` Returns -------- C : list, A list of constraints for each of the (two) events in this synthetic model. M : GeoModel Geomodel of the synthetic model """ G = grid( shape, step=(1,1), center=(shape[0]/2,shape[1]/2) ) #xy = G.coords() #s0 = strati('s0', C=PeriodicField( 'f0', input_dim=2 ) ) # Folds s0 = strati('s0', C=QuadraticField( 'f1', input_dim=2, gradient=np.array([0,1]), origin=(0,0), curve=(-0.00002,0) )) # base series d1 = fold('d1', origin=np.array([0,0]), extension=np.array([0,1]), # vertical extension compression=np.array([1,0.3]), # almost horizontal compression wavelength=2000, amplitude=250, sharpness=0.7) s1 = strati('s1', C=QuadraticField( 'f1', input_dim=2, gradient=np.array([0.1,0.9]), origin=(1000,500), curve=(-0.00002,0) ), base=0) # unconformable series # add some isosurfaces for y in np.linspace(0, shape[1], 5): # generate contacts s0.addIsosurface( 'i'+str(int(y)), seed=np.array([shape[0]/2, y]) ) for y in np.linspace(0, shape[1], 10): # generate contacts s1.addIsosurface( 'i'+str(int(y)), seed=np.array([shape[0]/2, y]) ) M = GeoModel( [s0,d1,s1], grid=G, name='hutton' ) s = M.predict(G) C = sample( s, pv='rgb', **kwargs ) C=[C[1],C[0],C[2]] # ignore fold event when generating constraints return C, MReturn a synthetic model with a folded basement cut by an unconformity.
Parameters
shape:tuple- The width and height of the generated data.
Keywords
All keywords are passed to <code>curlew.data.sample(...)</code>Returns
C:list,- A list of constraints for each of the (two) events in this synthetic model.
M:GeoModel- Geomodel of the synthetic model
def lehmann(shape=(1500, 1000), **kwargs)-
Expand source code
def lehmann( shape=(1500,1000), **kwargs ): """ Return a synthetic model with a folded basement. Parameters ---------- shape : tuple The width and height of the generated data. Keywords --------- All keywords are passed to `curlew.data.sample(...)` Returns -------- xy : np.ndarray A list of (N,d) xy points (from a grid with the specified `shape`) s : np.ndarray, An array of shape (N,2) containing the scalar values and ID of the "event" that caused them. C : list, A list of constraints for each of the (two) events in this synthetic model. M : GeoModel Geomodel of the synthetic model """ G = grid( shape, step=(1,1), center=(shape[0]/2,shape[1]/2) ) xy = G.coords() s0 = strati('s0', C=PeriodicField('f0', input_dim=2) ) # Folds M = GeoModel([s0], grid=G, name='lehmann' ) # add some isosurfaces for y in np.linspace(0, shape[1], 5): # generate 5 contacts s0.addIsosurface( 'i'+str(int(y)), seed=np.array([shape[0]/2, y]) ) s = M.predict(G) C = sample(s, pv='rgb', **kwargs) return C, MReturn a synthetic model with a folded basement.
Parameters
shape:tuple- The width and height of the generated data.
Keywords
All keywords are passed to <code>curlew.data.sample(...)</code>Returns
xy:np.ndarray- A list of (N,d) xy points (from a grid with the specified
shape) s:np.ndarray,- An array of shape (N,2) containing the scalar values and ID of the "event" that caused them.
C:list,- A list of constraints for each of the (two) events in this synthetic model.
M:GeoModel- Geomodel of the synthetic model
def michell(shape=(1500, 1000), offset=100, **kwargs)-
Expand source code
def michell( shape=(1500,1000), offset=100, **kwargs ): """ Return a synthetic model with a slightly curved layer-cake stratigraphy cut by a thrust fault. Parameters ---------- shape : tuple The width and height of the generated data. offset : tuple The offset of the fault. Keywords --------- All keywords are passed to `curlew.data.sample(...)` Returns -------- C : list, A list of constraints for each of the (two) events in this synthetic model. M : GeoModel Geomodel of the synthetic model """ G = grid( shape, step=(1,1), center=(shape[0]/2,shape[1]/2) ) xy = G.coords() s0 = strati('s0', C=QuadraticField( 'f0', input_dim=2, curve=(-0.00005,0), origin=(1000,500) ) ) s1 = fault( 's1', C=LinearField( 'f1', input_dim=2, origin=(1000,500), gradient=(0.5,0.5) ), offset=offset, shortening = [-1,0] ) for y in np.linspace(0, shape[1], 5): # generate 5 contacts s0.addIsosurface( 'i'+str(int(y)), seed=np.array([shape[0]/2, y]) ) M = GeoModel( [s0,s1], grid=G, name='michell' ) s = M.predict(G) C = sample( s, pv='rgb', **kwargs ) # sample unit and bedding constraints kwargs['pval'] = kwargs.get('pval', 1.0) # change default to sample all value constraints if 'breaks' in kwargs: del kwargs['breaks'] Cf = sample( s1.predict(G), pv='rgb', breaks=[0.5], **kwargs ) C = [C[0], Cf[0], C[-1]] # get stratigraphy, fault and property constraints mask = (C[0].gv[:,0] > - 0.68 ) # mask incorrect bebbing constraints near the fault C[0].gp = C[0].gp[mask] # mask the gp constraints C[0].gv = C[0].gv[mask] # mask the gv constraints C[0].gop = C[0].gop[mask] # mask the gop constraints C[0].gov = C[0].gov[mask] # mask the gov constraints C[1].vv = C[1].vv * 0 # ensure value constraints are exactly zero (fault surface = 0) return C, M # returnReturn a synthetic model with a slightly curved layer-cake stratigraphy cut by a thrust fault.
Parameters
shape:tuple- The width and height of the generated data.
offset:tuple- The offset of the fault.
Keywords
All keywords are passed to <code>curlew.data.sample(...)</code>Returns
C:list,- A list of constraints for each of the (two) events in this synthetic model.
M:GeoModel- Geomodel of the synthetic model
def playfair(shape=(1500, 1000), width=50, **kwargs)-
Expand source code
def playfair( shape=(1500,1000), width=50, **kwargs ): """ Return a synthetic model with a layer-cake stratigraphy cut by a dyke. Parameters ---------- shape : tuple The width and height of the generated data. width : float The half-width of the added dyke. Keywords --------- All keywords are passed to `curlew.data.sample(...)` Returns -------- C : list, A list of constraints for each of the (two) events in this synthetic model. M : GeoModel Geomodel of the synthetic model """ G = grid( shape, step=(1,1), center=(shape[0]/2,shape[1]/2) ) xy = G.coords() s0 = strati('s0', C=QuadraticField( 'f0', input_dim=2, curve=(-0.00005,0), origin=(1000,500) ) ) s1 = sheet( 's1', C=LinearField( 'f1', input_dim=2, origin = [1000,500], gradient=(0.5,0.5) ), contact=(-width,width) ) for y in np.linspace(0, shape[1], 5): # generate 5 contacts s0.addIsosurface( 'i'+str(int(y)), seed=np.array([shape[0]/2, y]) ) M = GeoModel( [s0, s1], grid=G, name='playfair' ) s = M.predict(G) kwargs['pval'] = kwargs.get('pval', 1.0) # change default to sample all value constraints C = sample( M.predict(G), pv='rgb', **kwargs ) C1 = sample( s1.predict(G), pv='rgb', breaks=[-width,width], **kwargs) return [C[1], C1[0], C[-1]], MReturn a synthetic model with a layer-cake stratigraphy cut by a dyke.
Parameters
shape:tuple- The width and height of the generated data.
width:float- The half-width of the added dyke.
Keywords
All keywords are passed to <code>curlew.data.sample(...)</code>Returns
C:list,- A list of constraints for each of the (two) events in this synthetic model.
M:GeoModel- Geomodel of the synthetic model
def sample(G, pv=None, breaks=19, init=100, xstep=300, pval=0.6, cmap='tab20', seed=42)-
Expand source code
def sample( G, pv=None, breaks=19, init=100, xstep=300, pval=0.6, cmap='tab20', seed=42 ): """ Sample value, orientation, and property constraints from a scalar field and associated gradients. Parameters ---------- G : curlew.geofield.Geode A Geode object containing the results from a GeoField or GeoModel. pv : np.ndarray or str A (N, d) array of n-dimensional property vectors (e.g., color). Can also be 'rgb' to create synthetic colors. breaks : list A set of scalar values at which contacts (changes in color) should be placed. init : int The index of the first "drillhole" in the x-direction. xstep : int The separation between "drillholes" in the x-direction. pval : float The probability that an observation is a scalar value (rather than just a gradient) constraint. cmap : str The name of the Matplotlib colormap to use for sampling colors that determine where geological contacts are. Must be a discrete colormap. seed : int Random seed to facilitate reproducible results. Returns ------- np.ndarray The sampled value, orientation, and property constraints. """ # sample constraints along drillhoes for s0 and s1 np.random.seed(seed) # reshape GeoField to image dims and initialise structure for accumulating constraints sf = G.grid.reshape( G.scalar ) sid = G.grid.reshape( G.structureID.astype(int) ) xy = G.grid.reshape( G.grid.coords() ) constraints = {int(k):{'vp':[], 'vv':[], 'gp':[], 'gv':[], 'gop':[], 'gov':[]} for k in np.unique(sid)} # compute contact points and gradients (bedding orientation) gx = np.diff( sf, axis= 0 ) gy = np.diff( sf, axis= 1 ) c = colour(sf, breaks=breaks, cmap=cmap) # discretise resulting scalar field into colours contacts = np.sum( np.abs( np.diff(c, axis=1, append=0) ), axis=-1) > 0 # get contacts domains = np.sum( [np.abs( np.diff(sid, axis=1, append=0) ), # get domain boundaries as gradients will be wrong here np.abs( np.diff(sid, axis=0, append=0) )], axis=0) > 0 if pv == 'rgb': pv = c # loop through boreholes and collect for x in np.arange(init,sf.shape[0],xstep): cc = np.argwhere( contacts[x,:] ) if (len(cc) > 1): for y in cc.squeeze()[:-1]: # ignore last as this is the boundary i = int( sid[x,y] ) if not domains[x,y]: # ignore domain boundaries as gradients will be wrong constraints[i]['gp'].append( xy[x,y] ) # add gradient constraint constraints[i]['gv'].append( (gx[x,y], gy[x,y]) ) constraints[i]['gop'].append( xy[x,y] ) # add identical orientation constraint constraints[i]['gov'].append( (gx[x,y], gy[x,y]) ) if np.random.rand() <= pval: # add value constraint constraints[i]['vp'].append( xy[x,y] ) constraints[i]['vv'].append( sf[x,y] ) if pv is not None: # add continuous property constraints for y in np.arange(sf.shape[1], step=1): if 'property' not in constraints: constraints['property'] = {'pp':[], 'pv':[], 'vp':[], 'vv':[]} constraints['property']['pp'].append( xy[x,y] ) # property position constraints['property']['pv'].append( pv[x,y] ) # property value constraints['property']['vp'].append( xy[x,y] ) # position again constraints['property']['vv'].append( sf[x,y] ) # also store scalar value -- useful as a reference # convert to numpy arrays for i in constraints.keys(): for k,v in constraints[i].items(): if len(v) > 0: constraints[i][k] = np.array(v) if (k == 'gv') or (k=='gov'): # normalise gradient constraints constraints[i][k] /= np.linalg.norm( v, axis=-1 )[:,None] else: constraints[i][k] = None return [CSet(**v) for k,v in constraints.items()]Sample value, orientation, and property constraints from a scalar field and associated gradients.
Parameters
G:curlew.geofield.Geode- A Geode object containing the results from a GeoField or GeoModel.
pv:np.ndarrayorstr- A (N, d) array of n-dimensional property vectors (e.g., color). Can also be 'rgb' to create synthetic colors.
breaks:list- A set of scalar values at which contacts (changes in color) should be placed.
init:int- The index of the first "drillhole" in the x-direction.
xstep:int- The separation between "drillholes" in the x-direction.
pval:float- The probability that an observation is a scalar value (rather than just a gradient) constraint.
cmap:str- The name of the Matplotlib colormap to use for sampling colors that determine where geological contacts are. Must be a discrete colormap.
seed:int- Random seed to facilitate reproducible results.
Returns
np.ndarray- The sampled value, orientation, and property constraints.
def seuss(shape=(1500, 700), nlayers=6, **kwargs)-
Expand source code
def seuss(shape=(1500, 700), nlayers=6, **kwargs): """ Return a synthetic model with layered stratigraphy, a dyke, an intrusion domain boundary, and two listric faults (one older, one younger), matching the "Seuss" GeoModel from the Building Analytical Models tutorial. Parameters ---------- shape : tuple The width and height of the generated data. Default (1500, 700) matches the tutorial notebook. nlayers : int Number of stratigraphic layers (isosurfaces) in each package. Default 6. Keywords --------- All keywords are passed to `curlew.synthetic.sample(...)` when generating constraints. Returns -------- C : list A list of constraints for the events in this synthetic model. M : GeoModel Geomodel with listric faults and domain boundaries (name='Seuss'). """ dims = shape G = grid(dims, step=(1, 1), center=(dims[0] / 2, dims[1] / 2)) curlew.default_dim = 2 # First stratigraphic package (layer-cake) s0 = strati( "s0", C=LinearField("f0", input_dim=2, gradient=(0.1, 0.9)), ) sy1 = np.linspace(0, dims[1], nlayers) sx1 = np.full_like(sy1, dims[0] / 10) for i, (x, y) in enumerate(zip(sx1, sy1)): s0.addIsosurface("S%d" % (i + 1), seed=np.array([x, y])) # Dyke s1 = sheet( "s1", C=LinearField( "f1", input_dim=2, origin=np.array([750.0, 300.0]), gradient=np.array([-0.5, 0.5]), ), contact=(-20, 20), aperture=2, ) # Domain boundary: intrusion (constant -2) below, s0 and s1 above intrusion = GeoField("i0", field=-2, type=int) # quick and easy way to define a constant scalar field d1 = domainBoundary( "d1", C=QuadraticField( "d1f", input_dim=2, origin=np.array([400.0, 50.0]), gradient=np.array([0.4, 1.0]), curve=(0, 0.002), ), bound=0, lt=[intrusion], gt=[s0, s1], ) # Older listric fault f1 = fault( "f1", C=ListricField( "f1f", input_dim=2, origin=np.array([650.0, 700.0]), fault_floor=0.0, fault_ceil=700.0, curvature_rate=0.006, ), width=1e-9, n_steps=3, offset=100, shortening=np.array([1, -1]), ) # Second stratigraphic package (unconformable cover) s2 = strati( "s2", C=LinearField( "f2", input_dim=2, origin=np.array([0.0, 500.0]), gradient=np.array([0.0, 1.0]), ), ) sy2 = np.linspace(350, dims[1], nlayers) sx2 = np.full_like(sy2, 9 * dims[0] / 10) for i, (x, y) in enumerate(zip(sx2, sy2)): s2.addIsosurface("S%d" % (i + 1), seed=np.array([x, y])) # Unconformity domain boundary: d1 and f1 below, s2 above d2 = domainBoundary( "d2", C=LinearField( "d2f", input_dim=2, origin=np.array([700.0, 500.0]), gradient=np.array([0.2, 0.9]), ), bound=0, lt=[d1, f1], gt=[s2], ) f2 = fault('f2', type=ListricField, C=None, contact=0.0, offset=100, # The displacement magnitude n_steps=3, # apply displacement in several steps, due to high curvature shortening=[1, -1], input_dim=2, origin=np.array([250.0, 700.0]), fault_ceil=700., # Defines the ceiling of the fault fault_floor=0., # Defines the floor of the fault width=1e-9, curvature_rate=0.006 # Defines the steepness of the decay ) M = GeoModel([d2, f2], grid=G, name="Seuss") s = M.predict(G) C = sample(s, pv="rgb", **kwargs) return C, MReturn a synthetic model with layered stratigraphy, a dyke, an intrusion domain boundary, and two listric faults (one older, one younger), matching the "Seuss" GeoModel from the Building Analytical Models tutorial.
Parameters
shape:tuple- The width and height of the generated data. Default (1500, 700) matches the tutorial notebook.
nlayers:int- Number of stratigraphic layers (isosurfaces) in each package. Default 6.
Keywords
All keywords are passed to <code><a title="curlew.synthetic.sample" href="#curlew.synthetic.sample">sample()</a>(...)</code> when generating constraints.Returns
C:list- A list of constraints for the events in this synthetic model.
M:GeoModel- Geomodel with listric faults and domain boundaries (name='Seuss').
def steno(shape=(1500, 1000), **kwargs)-
Expand source code
def steno( shape=(1500,1000), **kwargs ): """ Return a synthetic model with a slightly curved layer-cake stratigraphy. Parameters ---------- shape : tuple The width and height of the generated data. Keywords --------- All keywords are passed to `curlew.data.sample(...)` Returns -------- xy : np.ndarray A list of (N,d) xy points (from a grid with the specified `shape`) s : np.ndarray, An array of shape (N,2) containing the scalar values and ID of the "event" that caused them. C : list, A list of constraints for each of the (two) events in this synthetic model. M : GeoModel Geomodel of the synthetic model """ G = grid( shape, step=(1, 1), center=(shape[0]/2,shape[1]/2) ) xy = G.coords() s0 = strati('s0', C=QuadraticField( 'f0', input_dim=2, gradient= (0.00001,1), curve=(-0.00005,0), origin = (1000,500) ) ) # add some isosurfaces for y in np.linspace(0, shape[1], 5): # generate 5 contacts s0.addIsosurface( 'i'+str(int(y)), seed=np.array([shape[0]/2, y]) ) M = GeoModel([s0], grid=G, name="steno") s = s0.predict(G) C = sample( s, pv='rgb', **kwargs ) return C, MReturn a synthetic model with a slightly curved layer-cake stratigraphy.
Parameters
shape:tuple- The width and height of the generated data.
Keywords
All keywords are passed to <code>curlew.data.sample(...)</code>Returns
xy:np.ndarray- A list of (N,d) xy points (from a grid with the specified
shape) s:np.ndarray,- An array of shape (N,2) containing the scalar values and ID of the "event" that caused them.
C:list,- A list of constraints for each of the (two) events in this synthetic model.
M:GeoModel- Geomodel of the synthetic model
def walker(shape=(1500, 1000),
width=[60, 50, 40, 50, 40],
pos=[0, 100, 200, 400, 600],
**kwargs)-
Expand source code
def walker( shape=(1500,1000), width=[60,50,40,50,40], pos=[0,100,200,400,600], **kwargs ): """ Return a synthetic model with a layer-cake stratigraphy cut by several parallel dykes. Parameters ---------- shape : tuple The width and height of the generated data. width : float A list specifying the dyke widths. pos : float A list specifying the dyke positions in the x-axis. Must have the same length as width. Keywords --------- All keywords are passed to `curlew.data.sample(...)` Returns -------- C : list, A list of constraints for each of the (two) events in this synthetic model. M : GeoModel Geomodel of the synthetic model """ G = grid( shape, step=(1,1), center=(shape[0]/2,shape[1]/2) ) xy = G.coords() o = (0, shape[1] / 2) s0 = strati('s0', C=QuadraticField( 'f0', input_dim=2, curve=(-0.00005,0), origin=o ) ) contact = [(pos[i], pos[i]+width[i]) for i in range(len(pos))] s1 = sheet( 's1', C=LinearField( 'f1', input_dim=2, origin =o, gradient=(0.5,0.5) ), contact=contact ) for y in np.linspace(0, shape[1], 5): # generate 5 contacts s0.addIsosurface( 'i'+str(int(y)), seed=np.array([shape[0]/2, y]) ) M = GeoModel( [s0, s1], grid=G, name='playfair' ) s = M.predict(G) kwargs['pval'] = kwargs.get('pval', 1.0) # change default to sample all value constraints C = sample( M.predict(G), pv='rgb', **kwargs ) C1 = sample( s1.predict(G), pv='rgb', breaks=np.hstack(contact), **kwargs) return [C[1], C1[0], C[-1]], MReturn a synthetic model with a layer-cake stratigraphy cut by several parallel dykes.
Parameters
shape:tuple- The width and height of the generated data.
width:float- A list specifying the dyke widths.
pos:float- A list specifying the dyke positions in the x-axis. Must have the same length as width.
Keywords
All keywords are passed to <code>curlew.data.sample(...)</code>Returns
C:list,- A list of constraints for each of the (two) events in this synthetic model.
M:GeoModel- Geomodel of the synthetic model