Skip to content

Commit b1a69a4

Browse files
committed
Adding SDE based noise process as default.
1 parent 023f671 commit b1a69a4

1 file changed

Lines changed: 58 additions & 13 deletions

File tree

pompy/models.py

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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):
486492
class 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

Comments
 (0)