@@ -290,8 +290,9 @@ class WindModel(object):
290290 """
291291
292292 def __init__ (self , sim_region = None , n_x = 21 , n_y = 21 , u_av = 1. , v_av = 0. ,
293- k_x = 2. , k_y = 2. , noise_gain = 20. , noise_damp = 0.1 ,
294- noise_bandwidth = 0.2 , rng = np .random ):
293+ k_x = 20. , k_y = 20. , noise_gain = 2. , noise_damp = 0.1 ,
294+ noise_bandwidth = 0.2 , use_original_noise_updates = False ,
295+ rng = np .random ):
295296 """
296297 Parameters
297298 ----------
@@ -323,6 +324,10 @@ def __init__(self, sim_region=None, n_x=21, n_y=21, u_av=1., v_av=0.,
323324 noise_bandwidth : float
324325 Bandwidth for boundary condition noise generation (dimension:
325326 angle / time).
327+ use_original_noise_updates : boolean
328+ Whether to use the original non-SDE based updates for the noise
329+ process as defined in Farrell et al. (2002), see notes in
330+ `ColouredNoiseGenerator` documentation.
326331 rng : RandomState
327332 Random number generator to use in generating input noise. Defaults
328333 to `numpy.random` global generator however a seeded `RandomState`
@@ -342,7 +347,8 @@ def __init__(self, sim_region=None, n_x=21, n_y=21, u_av=1., v_av=0.,
342347 # for both components of the wind velocity field so (2,8) state
343348 # vector (2 as state includes first derivative)
344349 self .noise_gen = ColouredNoiseGenerator (
345- np .zeros ((2 , 8 )), noise_damp , noise_bandwidth , noise_gain , rng )
350+ np .zeros ((2 , 8 )), noise_damp , noise_bandwidth , noise_gain ,
351+ use_original_noise_updates , rng )
346352 # compute grid node spacing
347353 self .dx = sim_region .w / (n_x - 1 ) # x grid point spacing
348354 self .dy = sim_region .h / (n_y - 1 ) # y grid point spacing
@@ -486,12 +492,44 @@ def _centred_second_diffs(self, f):
486492class ColouredNoiseGenerator (object ):
487493 """Generator of coloured (correlated) Gaussian noise process.
488494
489- Generates a coloured noise output via integration of a state space
490- system formulation.
495+ Generates a coloured noise output via numerical integration of a stochastic
496+ differential equation formulation. The system is assumed to be defined by
497+ the system of SDEs::
498+
499+ dx_0 = x_1 * dt
500+ dx_1 = -(a * x_0 + b * x_1) * dt + c * dn
501+
502+ where `a = bandwidth**2` and `b = 2 * damping * bandwidth`,
503+ `c = gain * bandwidth**2` and `dn` is a standard Gaussian white noise
504+ process. This is numerically integrated using an Euler-Maruyama scheme::
505+
506+ for t in range(n_timestep):
507+ x[t+1,0] = x[t,0] + dt * x[t,1]
508+ x[t+1,1] = x[t,1] - dt * (a*x[t,0] + b*x[t,1]) + dt**0.5 * c * n[t]
509+
510+ where `x` is an array of shape `(n_timestep, 2)` and `n` is an array of
511+ shape `(n_timestep)` filled with random standard normal draws.
512+
513+ This differs from the code accompanying Farrell et al. (2002) which applies
514+ an Euler integration scheme to a state space formulation of a second-order
515+ linear system with Gaussian noise input at each time step, resulting in
516+ updates of the form::
517+
518+ for t in range(n_timestep):
519+ x[t+1,0] = x[t,0] + dt * x[t,1]
520+ x[t+1,1] = x[t,1] + dt * (-a * x[t,0] - b * x[t,1] + c * n[t])
521+
522+ This differs from the scheme implemented here by scaling the noise input
523+ by the timestep `dt` rather than its square root `dt**0.5`. This introduces
524+ an implicit dependence of the amplitude of the process on `dt`, in
525+ particular that the amplitude scales roughly as `dt**0.5`.
526+
527+ Updates consistent with the Farrell et al. (2002) implementation can be
528+ achieved by setting the `use_original_updates` flat to `True`.
491529 """
492530
493- def __init__ (self , init_state , damping = 0.1 , bandwidth = 0.2 , gain = 20 . ,
494- rng = np .random ):
531+ def __init__ (self , init_state , damping = 0.1 , bandwidth = 0.2 , gain = 1 . ,
532+ use_original_updates = False , rng = np .random ):
495533 """
496534 Parameters
497535 ----------
@@ -507,14 +545,16 @@ def __init__(self, init_state, damping=0.1, bandwidth=0.2, gain=20.,
507545 bandwidth : float
508546 Bandwidth or equivalently undamped natural frequency of system,
509547 affects system reponsiveness to variations in (noise) input
510- (dimension = angular measure / time).
548+ (dimension = 1 / time).
511549 gain : float
512- Input gain of system, affects scaling of (noise) input
513- (dimensionless).
550+ Input gain of system, affects scaling of (noise) input.
514551 rng : RandomState
515552 Random number generator to use in generating input noise. Defaults
516553 to `numpy.random` global generator however a seeded `RandomState`
517554 object can be passed if it is desired to have reproducible output.
555+ use_original_updates : boolean
556+ Whether to use the original non-SDE based updates for the noise
557+ process as defined in Farrell et al. (2002), see above notes.
518558 """
519559 # set up state space matrices
520560 self .a_mtx = np .array ([
@@ -523,6 +563,7 @@ def __init__(self, init_state, damping=0.1, bandwidth=0.2, gain=20.,
523563 # initialise state
524564 self .state = init_state
525565 self .rng = rng
566+ self .use_original_updates = use_original_updates
526567
527568 @property
528569 def output (self ):
@@ -539,6 +580,10 @@ def update(self, dt):
539580 """
540581 # get normal random input
541582 n = self .rng .normal (size = (1 , self .state .shape [1 ]))
542- # apply update with Euler-Maruyama integration
543- self .state += (
544- self .a_mtx .dot (self .state ) * dt + self .b_mtx * n * dt ** 0.5 )
583+ if self .use_original_updates :
584+ # apply Farrell et al. (2002) update
585+ self .state += dt * (self .a_mtx .dot (self .state ) + self .b_mtx .dot (n ))
586+ else :
587+ # apply update with Euler-Maruyama integration
588+ self .state += (
589+ dt * self .a_mtx .dot (self .state ) + self .b_mtx .dot (n ) * dt ** 0.5 )
0 commit comments