Skip to content

Commit 1ef9329

Browse files
committed
Generalize Gaussian random field generation
Allow higher orders of smoothness and use more intuitive definitions of length and output scale parameters
1 parent 6395815 commit 1ef9329

File tree

1 file changed

+47
-38
lines changed

1 file changed

+47
-38
lines changed

NektarDriftwave/src/nektar_driftwave.jl

+47-38
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,10 @@ Base.@kwdef struct NektarDriftwaveModelParameters{S <: Real, T <: Real}
2929
nektar_bin_directory::String = ""
3030
"Path to directory containing DriftWaveSolver binary"
3131
driftwave_solver_bin_directory::String = ""
32-
"Hasegawa-Wakatani system parameter α"
32+
"Hasegawa-Wakatani system parameter α - adiabiacity operator"
3333
alpha::S = 2.
34-
"Hasegawa-Wakatani system parameter κ"
34+
"Hasegawa-Wakatani system parameter κ - background density gradient scale-length"
3535
kappa::S = 1.
36-
"Lengthscale parameter for bump function used for initial field means"
37-
s::S = 2.
38-
"Lengthscale parameter for state noise in dynamics and initialisation"
39-
lambda::S = 0.1
4036
"Number of quadrilateral elements along each axis in mesh"
4137
mesh_dims::Vector{Int} = [32, 32]
4238
"Size (extents) of rectangular spatial domain mesh is defined on"
@@ -52,13 +48,19 @@ Base.@kwdef struct NektarDriftwaveModelParameters{S <: Real, T <: Real}
5248
collect, vec(collect(Iterators.product(-10.:10.:10., -10.:10.:10.)))
5349
)
5450
"Which of field variables are observed (subset of {phi, zeta, n})"
55-
observed_variables::Vector{String} = ["phi"]
56-
"Output scale parameter for initial state field Gaussian process"
57-
initial_state_scale::Union{S, Vector{S}} = 0.05
58-
"Output scale parameter for additive state noise fields Gaussian process"
59-
state_noise_scale::Union{S, Vector{S}} = 0.05
51+
observed_variables::Vector{String} = ["zeta"]
6052
"Scale parameter (standard deviation) of independent Gaussian noise in observations"
61-
observation_noise_std::Union{T, Vector{T}} = 0.1
53+
observation_noise_std::T = 0.1
54+
"Length scale parameter for Gaussian random fields used for state noise and initialisation"
55+
state_grf_length_scale::S = 1.
56+
"Positive integer smoothness parameter for Gaussian random fields used for state noise and initialisation"
57+
state_grf_smoothness::Int = 2
58+
"Output scale parameter for initial state Gaussian random field"
59+
initial_state_grf_output_scale::S = 0.05
60+
"Output scale parameter for additive state noise Gaussian random field"
61+
state_noise_grf_output_scale::S = 0.05
62+
"Length scale parameter for bump functions used for initial state field means"
63+
initial_state_mean_length_scale::S = 2.
6264
end
6365

6466
function get_params(
@@ -305,7 +307,6 @@ function make_driftwave_conditions_file(output_path, previous_state_path, parame
305307
parameters=(;
306308
NumSteps = parameters.num_steps_per_observation_time,
307309
TimeStep = parameters.time_step,
308-
s = parameters.s,
309310
kappa = parameters.kappa,
310311
alpha = parameters.alpha,
311312
IO_InfoSteps = 0,
@@ -321,26 +322,21 @@ function make_driftwave_conditions_file(output_path, previous_state_path, parame
321322
)
322323
end
323324

324-
function make_grf_conditions_file(output_path, parameters)
325+
function make_helmholtz_conditions_file(output_path, forcing_field_path, parameters)
325326
variables = ["u"]
327+
forcing_field_definition = (
328+
isnothing(forcing_field_path)
329+
? ExpressionFieldDefinition(only(variables), "awgn(1)")
330+
: FileFieldDefinition(only(variables), forcing_field_path)
331+
)
326332
make_nektar_conditions_file(
327333
output_path,
328334
variables=variables,
329335
num_modes=parameters.num_modes,
330-
solver_properties=(;
331-
EQTYPE = "Helmholtz",
332-
Projection = "DisContinuous",
333-
),
334-
parameters=(;
335-
lambda = parameters.lambda,
336-
),
336+
solver_properties=(; EQTYPE = "Helmholtz", Projection = "Continuous"),
337+
parameters=(; lambda = 1 / parameters.state_grf_length_scale^2),
337338
boundary_conditions=periodic_boundary_conditions(variables),
338-
functions=[
339-
FieldFunction(
340-
"Forcing",
341-
[ExpressionFieldDefinition(join(variables, ","), "awgn(1)")]
342-
)
343-
]
339+
functions=[FieldFunction("Forcing", [forcing_field_definition])]
344340
)
345341
end
346342

@@ -351,16 +347,12 @@ function make_poisson_conditions_file(output_path, forcing_field_path, parameter
351347
output_path,
352348
variables=variables,
353349
num_modes=parameters.num_modes,
354-
solver_properties=(;
355-
EQTYPE = "Poisson",
356-
Projection = "Continuous",
357-
),
350+
solver_properties=(; EQTYPE = "Poisson", Projection = "Continuous"),
358351
parameters=(;),
359352
boundary_conditions=periodic_boundary_conditions(variables),
360353
functions=[
361354
FieldFunction(
362-
"Forcing",
363-
[FileFieldDefinition(join(variables, ","), forcing_field_path)]
355+
"Forcing", [FileFieldDefinition(only(variables), forcing_field_path)]
364356
)
365357
]
366358
)
@@ -385,13 +377,15 @@ end
385377
struct NektarConditionsFilePaths
386378
driftwave::String
387379
grf::String
380+
grf_recursion::String
388381
poisson::String
389382
end
390383

391384
function NektarConditionsFilePaths(parent_directory::String)
392385
return NektarConditionsFilePaths(
393386
joinpath(parent_directory, "driftwave.xml"),
394387
joinpath(parent_directory, "grf.xml"),
388+
joinpath(parent_directory, "grf_recursion.xml"),
395389
joinpath(parent_directory, "poisson.xml")
396390
)
397391
end
@@ -422,14 +416,26 @@ end
422416
function generate_gaussian_random_field_file(model, task_index, variable, noise_scale, mean_expression=nothing)
423417
conditions_file_paths = model.task_conditions_file_paths[task_index]
424418
grf_field_file_path = get_field_file_path(conditions_file_paths.grf)
419+
grf_recursion_field_file_path = get_field_file_path(conditions_file_paths.grf_recursion)
425420
variable_field_path = joinpath(model.task_working_directories[task_index], "$(variable).fld")
421+
# Whittle-Matérn Gaussian random field variance for spatial dimension 2 is
422+
# Γ(ν) / (Γ(ν + 1) * κ^2ν * 4π) = 1 / (ν * κ^2ν * 4π)
423+
# Therefore multiply noise_scale by sqrt(ν * κ^2ν * 4π) so that noise_scale = 1
424+
# corresponds to unit variance
425+
ν = model.parameters.state_grf_smoothness * 2 - 1
426+
κ = 1 / model.parameters.state_grf_length_scale
427+
noise_scale *= sqrt* κ^2ν * 4π)
426428
if isnothing(mean_expression)
427429
field_expression_string = "$(noise_scale) * u"
428430
else
429431
field_expression_string = "$(mean_expression) + $(noise_scale) * u"
430432
end
431433
cd(model.task_working_directories[task_index]) do
432434
run(`$(model.executable_paths.adr_solver) -f -i Hdf5 $(conditions_file_paths.grf) $(model.mesh_file_paths.no_expansions)`)
435+
for i in 1:(model.parameters.state_grf_smoothness - 1)
436+
run(`$(model.executable_paths.adr_solver) -f -i Hdf5 $(conditions_file_paths.grf_recursion) $(model.mesh_file_paths.no_expansions)`)
437+
mv(grf_recursion_field_file_path, grf_field_file_path; force=true)
438+
end
433439
run(`$(model.executable_paths.field_convert) -f -m fieldfromstring:fieldstr="$(field_expression_string)":fieldname="$(variable)" $(model.mesh_file_paths.with_expansions) $(grf_field_file_path) $(variable_field_path):fld:format=Hdf5`)
434440
run(`$(model.executable_paths.field_convert) -f -m removefield:fieldname="u" $(model.mesh_file_paths.with_expansions) $(variable_field_path) $(variable_field_path):fld:format=Hdf5`)
435441
end
@@ -514,8 +520,10 @@ function init(
514520
for (task_working_directory, conditions_file_paths) in zip(task_working_directories, task_conditions_file_paths)
515521
mkdir(task_working_directory)
516522
driftwave_field_file_path = get_field_file_path(conditions_file_paths.driftwave)
523+
grf_field_file_path = get_field_file_path(conditions_file_paths.grf)
517524
make_driftwave_conditions_file(conditions_file_paths.driftwave, driftwave_field_file_path, parameters)
518-
make_grf_conditions_file(conditions_file_paths.grf, parameters)
525+
make_helmholtz_conditions_file(conditions_file_paths.grf, nothing, parameters)
526+
make_helmholtz_conditions_file(conditions_file_paths.grf_recursion, grf_field_file_path, parameters)
519527
make_poisson_conditions_file(conditions_file_paths.poisson, "$(driftwave_field_file_path):zeta", parameters)
520528
end
521529
observation_dimension = length(parameters.observed_points)
@@ -552,13 +560,14 @@ function ParticleDA.sample_initial_state!(
552560
) where {S, T}
553561
conditions_file_paths = model.task_conditions_file_paths[task_index]
554562
driftwave_field_file_path = get_field_file_path(conditions_file_paths.driftwave)
563+
s = model.parameters.initial_state_mean_length_scale
555564
variable_mean_expressions = [
556-
"zeta" => "4*exp((-x*x-y*y)/($(model.parameters.s^2)))*(-$(model.parameters.s^2)+x*x+y*y)/$(model.parameters.s^4)",
557-
"n" => "exp((-x*x-y*y)/$(model.parameters.s^2))",
565+
"zeta" => "4*exp((-x*x-y*y)/($(s^2)))*(-$(s^2)+x*x+y*y)/$(s^4)",
566+
"n" => "exp((-x*x-y*y)/$(s^2))",
558567
]
559568
variable_field_file_paths = [
560569
generate_gaussian_random_field_file(
561-
model, task_index, variable, model.parameters.initial_state_scale, mean_expression
570+
model, task_index, variable, model.parameters.initial_state_grf_output_scale, mean_expression
562571
)
563572
for (variable, mean_expression) in variable_mean_expressions
564573
]
@@ -594,7 +603,7 @@ function ParticleDA.update_state_stochastic!(
594603
write_state_to_field_file(driftwave_field_file_path, state)
595604
variable_field_file_paths = [
596605
generate_gaussian_random_field_file(
597-
model, task_index, variable, model.parameters.state_noise_scale
606+
model, task_index, variable, model.parameters.state_noise_grf_output_scale
598607
)
599608
for variable in ["zeta", "n"]
600609
]

0 commit comments

Comments
 (0)