#!/usr/bin/env/ python3
"""
This code implements the Asymptotic Parallel in Time (APinT) algorithm
for the 1-D rotating shallow water equations, following Haut and Wingate (2014).
Control and parameterisation of the code is through the cyclops_control library.
The structure of the program is based on the Fourier Spectral code Morgawr, by AGP. A significant
amount of the numerical code is courtesy of Dr. Terry Haut of LANL.
Algorithm
---------
1) Set up relevant (Python) objects for computation to progress (spectral_toolbox, control, exponential_integrator)
2) Call solver method for timestepping
3) Output writing
| Author: Adam G. Peddle, Terry Haut
| Contact: ap553@exeter.ac.uk
| Version: 1.0
"""
import time
import sys
import os
import pickle
from spectral_toolbox_1D import SpectralToolbox
from RSWE_exponential_integrator import ExponentialIntegrator
import RSWE_direct
import cyclops_base
import numpy as np
import cyclops_control
[docs]def APinT_solver(control, st, expInt, u_init):
"""
Implements the Asymptotic Parallel in Time algorithm.
This methods proceeds by performing a coarse approximation using the coarse propagator
which is then refined (in theory, in parallel) by the fine propagator. It differs from
standard parareal methods by the averaging over the fast waves which is performed in
the coarse timestep. The fine timestep solves the full equations to the desired level
of accuracy.
Implementation follows:
.. math:: U_{n+1}^{k+1} = G(U_{n}^{k+1}) + F(U_{n}^{k}) - G(U_{n}^{k})
where G refers to the coarse propagator and F to the fine propagator. n is the timestep
k is the current iteration number. Converges to the accuracy of the fine propgator.
**Parameters**
- `control` : a control object
- `st` : the spectral toolbox object
- `expInt` : an exponential integrator object
- `u_init` : the initial conditions in Fourier space, np.array(3,Nx), (u,v,h)
**Returns**
- `errs` : A tuple containing lists of the L_infty and L_2 errors vs iteration, respectively
- `U_hat_new` : The solution after convergence (np.array(Nt, 3, Nx))
**See Also**
coarse_propagator, fine_propagator
"""
# U_hat_mac contains the solution at the completion of the
# coarse parareal timesteps.
# U_hat_mic contains the solution at the completion of the fine
# parareal timesteps, but discards the information in between
# (i.e. matches the timesteps of the coarse solution)
U_hat_mac = np.zeros((control['Nt'], 3, control['Nx']), dtype = 'complex')
U_hat_mic = np.zeros((control['Nt'], 3, control['Nx']), dtype = 'complex')
U_hat_new = np.zeros((control['Nt'], 3, control['Nx']), dtype = 'complex')
U_hat_old = np.zeros((control['Nt'], 3, control['Nx']), dtype = 'complex')
# U_hat_old contains the solution at the previous iteration for
# use in convergence testing
errors = [[] for _ in range(control['Nt'])]
# Create initial condition
U_hat_new[0,:,:] = u_init
for k in range(3):
U_hat_new[0,k,:] = st.forward_fft(U_hat_new[0,k,:])
U_hat_old[0,:,:] = U_hat_new[0,:,:]
# Compute first parareal level here
start = time.time()
for j in range(control['Nt']-1):
# First parareal level by coarse timestep in serial only
U_hat_new[j+1, :, :] = RSWE_direct.solve(control, expInt, st,
U_hat_new[j, :, :],
solver = 'coarse_propagator',
realspace = False)
U_hat_old[j+1,:,:] = U_hat_new[j+1, :, :]
end = time.time()
print("First APinT level completed in {:.8f} seconds".format(end-start))
# Further parareal levels computed here
iterative_error = 100000000000000.
L_inf_buffer = []
L_2_buffer = []
while iterative_error > control['conv_tol']:
start = time.time()
L_infty_err = 0.
L_2_err = 0.
U_hat_new = np.zeros((control['Nt'], 3, control['Nx']), dtype = 'complex')
U_hat_new[0,:,:] = U_hat_old[0,:,:]
for j in range(control['Nt']-1): # Loop over timesteps
# Compute coarse and fine timesteps (parallel-isable)
U_hat_mac[j+1, :, :] = RSWE_direct.solve(control, expInt, st,
U_hat_old[j, :, :],
solver = 'coarse_propagator',
realspace = False)
U_hat_mic[j+1, :, :] = RSWE_direct.solve(control, expInt, st,
U_hat_old[j, :, :],
solver = 'fine_propagator',
realspace = False)
# Compute and apply correction (serial)
U_hat_new[j+1, :, :] = RSWE_direct.solve(control, expInt, st,
U_hat_new[j, :, :],
solver = 'coarse_propagator',
realspace = False)
U_hat_new[j+1, :, :] = U_hat_new[j+1, :,:] + (U_hat_mic[j+1, :,:] - U_hat_mac[j+1, :,:])
# L_inf, L_2
error_iteration = cyclops_base.compute_errors(U_hat_old[j+1,:,:], U_hat_new[j+1,:,:])
L_infty_err = max(L_infty_err, error_iteration[0])
L_2_err = max(L_2_err, error_iteration[1])
L_inf_buffer.append(L_infty_err)
L_2_buffer.append(L_2_err)
# Perform convergence checks (iterative error)
U_hat_old[:, :, :] = U_hat_new[:, :, :].copy() #Overwrite previous solution for convergence tests
iterative_error_old = iterative_error
iterative_error = L_infty_err
end = time.time()
print("APinT level {:>2} completed in {:.8f} seconds".format(k,end-start))
print("L_infty norm = {:.6e}".format(iterative_error))
if iterative_error > iterative_error_old:
print('Possible Numerical Instability Detected.')
print("APinT Computation Complete in {:>2} iterations.".format(k))
return (L_inf_buffer, L_2_buffer), U_hat_new
if __name__ == "__main__":
control = cyclops_control.setup_control(sys.argv[1:])
if 'working_dir' in control: os.chdir(control['working_dir'])
# Local parameterisations:
if control['outFileStem'] is None: control['outFileStem'] = ''
conv_tol = control['conv_tol']
control['final_time'] = control['coarse_timestep'] # For generalisabilty of rswe_direct
control['solver'] = None # Idiot-proofing
# Initialise spectral toolbox object
st = SpectralToolbox(control['Nx'], control['Lx'])
if control['HMM_T0'] is None:
control['HMM_T0'] = control['coarse_timestep']/(control['epsilon']**0.2)
control['HMM_M_bar'] = max(25, int(80*control['HMM_T0']))
#Create exponential integrator object
expInt = ExponentialIntegrator(control)
# Set up initial (truth) field
ICs = cyclops_base.h_init(control)
# Set up spectral toolbox
st = SpectralToolbox(control['Nx'], control['Lx'])
# ICs should be in Fourier space for APinT algo as implemented
for k in range(3):
IC_hat = np.zeros((3, control['Nx']), dtype=complex)
IC_hat[k,:] = st.forward_fft(ICs[k,:])
# Kernel. Call to solver.
errs, U_hat_new = APinT_solver(control, st, expInt, IC_hat)
# Post-convergence, handle output
for i in range(control['Nt']):
for k in range(3):
U_hat_new[i,k,:] = st.inverse_fft(U_hat_new[i,k,:])
with open("{}_APinT.dat".format(control['outFileStem']), 'wb') as f:
data = dict()
data['u'] = np.real(U_hat_new[i, 0, :])
data['v'] = np.real(U_hat_new[i, 1, :])
data['h'] = np.real(U_hat_new[i, 2, :])
data['L_infty_errs'] = errs[0]
data['L_2_errs'] = errs[1]
data['control'] = control
pickle.dump(data, f)