Exemple #1
0
def run_momsolve(config_file):

    # Read run config file
    params = ConfigParser(config_file)

    log = inout.setup_logging(params)

    input_data = inout.InputData(params)

    # Get model mesh
    mesh = fice_mesh.get_mesh(params)
    # Initialize model
    mdl = model.model(mesh, input_data, params, init_vel_obs=False)
    # Get alpha from file
    mdl.alpha_from_data()

    try:
        Bglen = mdl.input_data.interpolate("Bglen", mdl.M)
        mdl.init_beta(mdl.bglen_to_beta(Bglen), False)
    except (AttributeError, KeyError) as e:
        log.warning('Using default bglen (constant)')

    # Forward Solve
    slvr = solver.ssa_solver(mdl)
    slvr.def_mom_eq()
    slvr.solve_mom_eq()


    # Output model variables in ParaView+Fenics friendly format
    outdir = params.io.output_dir

    h5file = HDF5File(mesh.mpi_comm(), str(Path(outdir)/'U.h5'), 'w')
    h5file.write(slvr.U, 'U')
    h5file.write(mesh, 'mesh')
    h5file.attributes('mesh')['periodic'] = params.mesh.periodic_bc

    inout.write_variable(slvr.U, params)
Exemple #2
0
def run_inv(config_file):
    """Run the inversion part of the simulation"""
    # Read run config file
    params = ConfigParser(config_file)

    inout.setup_logging(params)
    inout.log_preamble("inverse", params)

    # Load the static model data (geometry, smb, etc)
    input_data = inout.InputData(params)

    # Get the model mesh
    mesh = fice_mesh.get_mesh(params)
    mdl = model.model(mesh, input_data, params)

    # pts_lengthscale = params.obs.pts_len

    mdl.gen_alpha()

    # Add random noise to Beta field iff we're inverting for it
    mdl.bglen_from_data()
    mdl.init_beta(mdl.bglen_to_beta(mdl.bglen), pert=False)

    # Next line will output the initial guess for alpha fed into the inversion
    # File(os.path.join(outdir,'alpha_initguess.pvd')) << mdl.alpha

    #####################
    # Run the Inversion #
    #####################

    slvr = solver.ssa_solver(mdl)
    slvr.inversion()

    ##############################################
    #  Write out variables in outdir and         #
    #  diagnostics folder                        #
    #############################################

    phase_name = params.inversion.phase_name
    phase_suffix = params.inversion.phase_suffix
    outdir = Path(params.io.output_dir) / phase_name / phase_suffix
    diag_dir = Path(params.io.diagnostics_dir)

    # Required for next phase (HDF5):

    invout_file = params.io.inversion_file

    phase_suffix = params.inversion.phase_suffix
    if len(phase_suffix) > 0:
        invout_file = params.io.run_name + phase_suffix + '_invout.h5'

    invout = HDF5File(mesh.mpi_comm(), str(outdir / invout_file), 'w')

    invout.parameters.add("gamma_alpha", slvr.gamma_alpha)
    invout.parameters.add("delta_alpha", slvr.delta_alpha)
    invout.parameters.add("gamma_beta", slvr.gamma_beta)
    invout.parameters.add("delta_beta", slvr.delta_beta)
    invout.parameters.add("delta_beta_gnd", slvr.delta_beta_gnd)
    invout.parameters.add("timestamp", str(datetime.datetime.now()))

    invout.write(mdl.alpha, 'alpha')
    invout.write(mdl.beta, 'beta')

    # For visualisation (XML & VTK):
    if params.io.write_diagnostics:

        inout.write_variable(slvr.U,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)
        inout.write_variable(mdl.beta,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

        mdl.beta_bgd.rename("beta_bgd", "")
        inout.write_variable(mdl.beta_bgd,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

        inout.write_variable(mdl.bed,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)
        H = project(mdl.H, mdl.M)
        H.rename("thick", "")
        inout.write_variable(H,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

        fl_ex = project(slvr.float_conditional(H), mdl.M)
        inout.write_variable(fl_ex,
                             params,
                             name='float',
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

        inout.write_variable(mdl.mask_vel_M,
                             params,
                             name="mask_vel",
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

        inout.write_variable(mdl.u_obs_Q,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)
        inout.write_variable(mdl.v_obs_Q,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)
        inout.write_variable(mdl.u_std_Q,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)
        inout.write_variable(mdl.v_std_Q,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

        U_obs = project((mdl.v_obs_Q**2 + mdl.u_obs_Q**2)**(1.0 / 2.0), mdl.M)
        U_obs.rename("uv_obs", "")
        inout.write_variable(U_obs,
                             params,
                             name="uv_obs",
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

        inout.write_variable(mdl.alpha,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

        Bglen = project(slvr.beta_to_bglen(slvr.beta), mdl.M)
        Bglen.rename("Bglen", "")
        inout.write_variable(Bglen,
                             params,
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)
        inout.write_variable(slvr.bmelt,
                             params,
                             name="bmelt",
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)
        inout.write_variable(slvr.smb,
                             params,
                             name="smb",
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)
        inout.write_variable(mdl.surf,
                             params,
                             name="surf",
                             outdir=diag_dir,
                             phase_name=phase_name,
                             phase_suffix=phase_suffix)

    return mdl
Exemple #3
0
def run_invsigma(config_file):
    """Compute control sigma values from eigendecomposition"""

    comm = MPI.comm_world
    rank = comm.rank

    # Read run config file
    params = ConfigParser(config_file)

    # Setup logging
    log = inout.setup_logging(params)
    inout.log_preamble("inv sigma", params)

    outdir = params.io.output_dir
    diags_dir = params.io.diagnostics_dir

    # Load the static model data (geometry, smb, etc)
    input_data = inout.InputData(params)

    # Eigen decomposition params
    phase_suffix_e = params.eigendec.phase_suffix
    eigendir = Path(outdir)/params.eigendec.phase_name/phase_suffix_e
    lamfile = params.io.eigenvalue_file
    vecfile = params.io.eigenvecs_file
    threshlam = params.eigendec.eigenvalue_thresh

    if len(phase_suffix_e) > 0:
        lamfile = params.io.run_name + phase_suffix_e + '_eigvals.p'
        vecfile = params.io.run_name + phase_suffix_e + '_vr.h5'

    # Get model mesh
    mesh = fice_mesh.get_mesh(params)

    # Define the model (only need alpha & beta though)
    mdl = model.model(mesh, input_data, params, init_fields=True)

    # Load alpha/beta fields
    mdl.alpha_from_inversion()
    mdl.beta_from_inversion()
    mdl.bglen_from_data(mask_only=True)

    # Setup our solver object
    slvr = solver.ssa_solver(mdl, mixed_space=params.inversion.dual)

    cntrl = slvr.get_control()[0]
    space = slvr.get_control_space()

    # sigma_old, sigma_prior_old = [Function(space) for i in range(3)]
    x, y, z = [Function(space) for i in range(3)]
    # Regularization operator using inversion delta/gamma values
    Prior = mdl.get_prior()
    reg_op = Prior(slvr, space)

    # Load the eigenvalues
    with open(os.path.join(eigendir, lamfile), 'rb') as ff:
        eigendata = pickle.load(ff)
        lam = eigendata[0].real.astype(np.float64)
        nlam = len(lam)

    # Check if eigendecomposition successfully produced num_eig
    # or if some are NaN
    if np.any(np.isnan(lam)):
        nlam = np.argwhere(np.isnan(lam))[0][0]
        lam = lam[:nlam]

    # Read in the eigenvectors and check they are normalised
    # w.r.t. the prior (i.e. the B matrix in our GHEP)
    eps = params.constants.float_eps
    W = []
    with HDF5File(comm,
                  os.path.join(eigendir, vecfile), 'r') as hdf5data:
        for i in range(nlam):
            w = Function(space)
            hdf5data.read(w, f'v/vector_{i}')

            print(f"Getting eigenvector {i} of {nlam}")
            # # Test norm in prior == 1.0
            # reg_op.action(w.vector(), y.vector())
            # norm_in_prior = w.vector().inner(y.vector())
            # assert (abs(norm_in_prior - 1.0) < eps)

            W.append(w)

    # Which eigenvalues are larger than our threshold?
    pind = np.flatnonzero(lam > threshlam)
    lam = lam[pind]
    W = [W[i] for i in pind]

    # this is a diagonal matrix but we only ever address it element-wise
    # bit of a waste of space.
    D = np.diag(lam / (lam + 1))

    # TODO make this a model method
    cntrl_names = []
    if params.inversion.alpha_active:
        cntrl_names.append("alpha")
    if params.inversion.beta_active:
        cntrl_names.append("beta")
    dual = params.inversion.dual

    ############################################
    # Isaac Eq. 20
    # P2 = prior
    # P1 = WDW
    # Note - don't think we're considering the cross terms
    # in the posterior covariance.

    # Generate patches of cells for computing invsigma
    clust_fun, npatches = patch_fun(mesh, params)

    # Create standard & mixed DG spaces
    dg_space = FunctionSpace(mesh, 'DG', 0)
    if(dual):
        dg_el = FiniteElement("DG", mesh.ufl_cell(), 0)
        mixedEl = dg_el * dg_el
        dg_out_space = FunctionSpace(mesh, mixedEl)
    else:
        dg_out_space = dg_space

    sigmas = [Function(dg_space) for i in range(len(cntrl_names))]
    sigma_priors = [Function(dg_space) for i in range(len(cntrl_names))]

    indic_1 = Function(dg_space)
    indic = Function(dg_out_space)

    test = TestFunction(space)

    neg_flag = 0
    for i in range(npatches):

        print(f"Working on patch {i+1} of {npatches}")

        # Create DG indicator function for patch i
        indic_1.vector()[:] = (clust_fun.vector()[:] == i).astype(int)
        indic_1.vector().apply("insert")

        # Loop alpha & beta as appropriate
        for j in range(len(cntrl_names)):

            if(dual):
                indic.vector()[:] = 0.0
                indic.vector().apply("insert")
                assign(indic.sub(j), indic_1)
            else:
                assign(indic, indic_1)

            clust_lump = assemble(inner(indic, test)*dx)
            patch_area = clust_lump.sum()  # Duplicate work here...

            clust_lump /= patch_area

            # Prior variance
            reg_op.inv_action(clust_lump, x.vector())
            cov_prior = x.vector().inner(clust_lump)

            # P_i^T W D W^T P_i
            # P_i is clust_lump
            # P_i^T has dims [1 x M], W has dims [M x N]
            # where N is num eigs & M is size of ev function space
            PiW = np.asarray([clust_lump.inner(w.vector()) for w in W])

            # PiW & PiWD are [1 x N]
            PiWD = PiW * D.diagonal()
            # PiWDWPi, [1 x N] * [N x 1]
            PiWDWPi = np.inner(PiWD, PiW)  # np.inner OK here because already parallel reduced

            cov_reduction = PiWDWPi
            cov_post = cov_prior - cov_reduction

            if cov_post < 0:
                log.warning(f'WARNING: Negative Sigma: {cov_post}')
                log.warning('Setting as Zero and Continuing.')
                neg_flag = 1
                continue

            # NB: "+=" here but each DOF will only be contributed to *once*
            # Essentially we are constructing the sigmas functions from
            # non-overlapping patches.
            sigmas[j].vector()[:] += indic_1.vector()[:] * np.sqrt(cov_post)
            sigmas[j].vector().apply("insert")

            sigma_priors[j].vector()[:] += indic_1.vector()[:] * np.sqrt(cov_prior)
            sigma_priors[j].vector().apply("insert")


    if neg_flag:
        log.warning('Negative value(s) of sigma encountered.'
                    'Examine the range of eigenvalues and check if '
                    'the threshlam paramater is set appropriately.')

    # # Previous approach for comparison
    # #####################################

    # # Isaac Eq. 20
    # # P2 = prior
    # # P1 = WDW
    # # Note - don't think we're considering the cross terms
    # # in the posterior covariance.
    # # TODO - this isn't particularly well parallelised - can it be improved?
    # neg_flag = 0
    # for j in range(space.dim()):

    #     # Who owns this DOF?
    #     own_idx = y.vector().owns_index(j)
    #     ownership = np.where(comm.allgather(own_idx))[0]
    #     assert len(ownership) == 1
    #     idx_root  = ownership[0]

    #     # Prior (P2)
    #     y.vector().zero()
    #     y.vector().vec().setValue(j, 1.0)
    #     y.vector().apply('insert')
    #     reg_op.inv_action(y.vector(), x.vector())
    #     P2 = x

    #     # WDW (P1) ~ lam * V_r**2
    #     tmp2 = np.asarray([D[i, i] * w.vector().vec().getValue(j) for i, w in enumerate(W)])
    #     tmp2 = comm.bcast(tmp2, root=idx_root)

    #     P1 = Function(space)
    #     for tmp, w in zip(tmp2, W):
    #         P1.vector().axpy(tmp, w.vector())

    #     P_vec = P2.vector() - P1.vector()

    #     # Extract jth component & save
    #     # TODO why does this need to be communicated here? surely owning proc
    #     # just inserts?
    #     dprod = comm.bcast(P_vec.vec().getValue(j), root=idx_root)
    #     dprod_prior = comm.bcast(P2.vector().vec().getValue(j), root=idx_root)

    #     if dprod < 0:
    #         log.warning(f'WARNING: Negative Sigma: {dprod}')
    #         log.warning('Setting as Zero and Continuing.')
    #         neg_flag = 1
    #         continue

    #     sigma_old.vector().vec().setValue(j, np.sqrt(dprod))
    #     sigma_prior_old.vector().vec().setValue(j, np.sqrt(dprod_prior))

    # sigma_old.vector().apply("insert")
    # sigma_prior_old.vector().apply("insert")

    # For testing - whole thing at once:
    # wdw = (np.matrix(W) * np.matrix(D) * np.matrix(W).T)
    # wdw[:,0] == P1 for j = 0

    # if neg_flag:
    #     log.warning('Negative value(s) of sigma encountered.'
    #                 'Examine the range of eigenvalues and check if '
    #                 'the threshlam paramater is set appropriately.')

    # Write sigma & sigma_prior to files
    # sigma_var_name = "_".join((cntrl.name(), "sigma"))
    # sigma_prior_var_name = "_".join((cntrl.name(), "sigma_prior"))

    # sigma_old.rename(sigma_var_name, "")
    # sigma_prior_old.rename(sigma_prior_var_name, "")

    # inout.write_variable(sigma_old, params,
    #                      name=sigma_var_name+"_old")
    # inout.write_variable(sigma_prior_old, params,
    #                      name=sigma_prior_var_name+"_old")

    for i, name in enumerate(cntrl_names):
        sigmas[i].rename("sigma_"+name, "")
        sigma_priors[i].rename("sigma_prior_"+name, "")

        phase_suffix_sigma = params.inv_sigma.phase_suffix

        inout.write_variable(sigmas[i], params,
                             outdir=outdir,
                             phase_name=params.inv_sigma.phase_name,
                             phase_suffix=phase_suffix_sigma)
        inout.write_variable(sigma_priors[i], params,
                             outdir=outdir,
                             phase_name=params.inv_sigma.phase_name,
                             phase_suffix=phase_suffix_sigma)

    mdl.cntrl_sigma = sigmas
    mdl.cntrl_sigma_prior = sigma_priors
    return mdl
Exemple #4
0
def run_forward(config_file):

    # Read run config file
    params = ConfigParser(config_file)
    log = inout.setup_logging(params)
    inout.log_preamble("forward", params)

    outdir = params.io.output_dir
    diag_dir = params.io.diagnostics_dir
    phase_name = params.time.phase_name

    # Load the static model data (geometry, smb, etc)
    input_data = inout.InputData(params)

    # Get model mesh
    mesh = fice_mesh.get_mesh(params)

    # Define the model
    mdl = model.model(mesh, input_data, params)

    mdl.alpha_from_inversion()
    mdl.beta_from_inversion()

    # Solve
    slvr = solver.ssa_solver(mdl, mixed_space=params.inversion.dual)
    slvr.save_ts_zero()

    cntrl = slvr.get_control()

    qoi_func = slvr.get_qoi_func()

    # TODO here - cntrl now returns a list - so compute_gradient returns a list of tuples

    # Run the forward model
    Q = slvr.timestep(adjoint_flag=1, qoi_func=qoi_func)
    # Run the adjoint model, computing gradient of Qoi w.r.t cntrl
    dQ_ts = compute_gradient(Q, cntrl)  # Isaac 27

    # Output model variables in ParaView+Fenics friendly format
    # Output QOI & DQOI (needed for next steps)
    inout.write_qval(slvr.Qval_ts, params)
    inout.write_dqval(dQ_ts, [var.name() for var in cntrl], params)

    # Output final velocity, surface & thickness (visualisation)
    inout.write_variable(slvr.U,
                         params,
                         name="U_fwd",
                         outdir=diag_dir,
                         phase_name=phase_name,
                         phase_suffix=params.time.phase_suffix)
    inout.write_variable(mdl.surf,
                         params,
                         name="surf_fwd",
                         outdir=diag_dir,
                         phase_name=phase_name,
                         phase_suffix=params.time.phase_suffix)

    H = project(mdl.H, mdl.Q)
    inout.write_variable(H,
                         params,
                         name="H_fwd",
                         outdir=diag_dir,
                         phase_name=phase_name,
                         phase_suffix=params.time.phase_suffix)
    return mdl
Exemple #5
0
def run_invsigma(config_file):
    """Compute control sigma values from eigendecomposition"""

    comm = MPI.comm_world
    rank = comm.rank

    # Read run config file
    params = ConfigParser(config_file)

    # Setup logging
    log = inout.setup_logging(params)
    inout.log_preamble("inv sigma", params)

    outdir = params.io.output_dir

    # Load the static model data (geometry, smb, etc)
    input_data = inout.InputData(params)

    eigendir = outdir
    lamfile = params.io.eigenvalue_file
    vecfile = params.io.eigenvecs_file
    threshlam = params.eigendec.eigenvalue_thresh

    # Get model mesh
    mesh = fice_mesh.get_mesh(params)

    # Define the model (only need alpha & beta though)
    mdl = model.model(mesh, input_data, params, init_fields=False)

    # Load alpha/beta fields
    mdl.alpha_from_inversion()
    mdl.beta_from_inversion()

    # Regularization operator using inversion delta/gamma values
    # TODO - this won't handle dual inversion case
    if params.inversion.alpha_active:
        delta = params.inversion.delta_alpha
        gamma = params.inversion.gamma_alpha
        cntrl = mdl.alpha
    elif params.inversion.beta_active:
        delta = params.inversion.delta_beta
        gamma = params.inversion.gamma_beta
        cntrl = mdl.beta

    space = cntrl.function_space()

    sigma, sigma_prior, x, y, z = [Function(space) for i in range(5)]

    reg_op = prior.laplacian(delta, gamma, space)

    # Load the eigenvalues
    with open(os.path.join(eigendir, lamfile), 'rb') as ff:
        eigendata = pickle.load(ff)
        lam = eigendata[0].real.astype(np.float64)
        nlam = len(lam)

    # Read in the eigenvectors and check they are normalised
    # w.r.t. the prior (i.e. the B matrix in our GHEP)
    eps = params.constants.float_eps
    W = []
    with HDF5File(comm, os.path.join(eigendir, vecfile), 'r') as hdf5data:
        for i in range(nlam):
            w = Function(space)
            hdf5data.read(w, f'v/vector_{i}')

            # Test norm in prior == 1.0
            reg_op.action(w.vector(), y.vector())
            norm_in_prior = w.vector().inner(y.vector())
            assert (abs(norm_in_prior - 1.0) < eps)

            W.append(w)

    # Which eigenvalues are larger than our threshold?
    pind = np.flatnonzero(lam > threshlam)
    lam = lam[pind]
    W = [W[i] for i in pind]

    D = np.diag(lam / (lam + 1))

    neg_flag = 0

    # Isaac Eq. 20
    # P2 = prior
    # P1 = WDW
    # Note - don't think we're considering the cross terms
    # in the posterior covariance.
    # TODO - this isn't particularly well parallelised - can it be improved?
    for j in range(space.dim()):

        # Who owns this index?
        own_idx = y.vector().owns_index(j)
        ownership = np.where(comm.allgather(own_idx))[0]
        assert len(ownership) == 1
        idx_root = ownership[0]

        y.vector().zero()
        y.vector().vec().setValue(j, 1.0)
        y.vector().apply('insert')

        tmp2 = np.asarray(
            [D[i, i] * w.vector().vec().getValue(j) for i, w in enumerate(W)])
        tmp2 = comm.bcast(tmp2, root=idx_root)

        P1 = Function(space)
        for tmp, w in zip(tmp2, W):
            P1.vector().axpy(tmp, w.vector())

        reg_op.inv_action(y.vector(), x.vector())
        P2 = x

        P_vec = P2.vector() - P1.vector()

        dprod = comm.bcast(P_vec.vec().getValue(j), root=idx_root)
        dprod_prior = comm.bcast(P2.vector().vec().getValue(j), root=idx_root)

        if dprod < 0:
            log.warning(f'WARNING: Negative Sigma: {dprod}')
            log.warning('Setting as Zero and Continuing.')
            neg_flag = 1
            continue

        sigma.vector().vec().setValue(j, np.sqrt(dprod))
        sigma_prior.vector().vec().setValue(j, np.sqrt(dprod_prior))

    sigma.vector().apply("insert")
    sigma_prior.vector().apply("insert")

    # For testing - whole thing at once:
    # wdw = (np.matrix(W) * np.matrix(D) * np.matrix(W).T)
    # wdw[:,0] == P1 for j = 0

    if neg_flag:
        log.warning('Negative value(s) of sigma encountered.'
                    'Examine the range of eigenvalues and check if '
                    'the threshlam paramater is set appropriately.')

    # Write sigma & sigma_prior to files
    sigma_var_name = "_".join((cntrl.name(), "sigma"))
    sigma_prior_var_name = "_".join((cntrl.name(), "sigma_prior"))

    sigma.rename(sigma_var_name, "")
    sigma_prior.rename(sigma_prior_var_name, "")

    inout.write_variable(sigma, params, name=sigma_var_name)
    inout.write_variable(sigma_prior, params, name=sigma_prior_var_name)

    mdl.cntrl_sigma = sigma
    mdl.cntrl_sigma_prior = sigma_prior
    return mdl
Exemple #6
0
def run_forward(config_file):

    #Read run config file
    params = ConfigParser(config_file)
    log = inout.setup_logging(params)
    inout.log_preamble("forward", params)

    outdir = params.io.output_dir

    # Load the static model data (geometry, smb, etc)
    input_data = inout.InputData(params)

    # Get model mesh
    mesh = fice_mesh.get_mesh(params)

    # Define the model
    mdl = model.model(mesh, input_data, params)

    mdl.alpha_from_inversion()
    mdl.beta_from_inversion()

    # Solve
    slvr = solver.ssa_solver(mdl)
    slvr.save_ts_zero()

    cntrl = slvr.get_control()

    qoi_func = slvr.get_qoi_func()

    #TODO here - cntrl now returns a list - so compute_gradient returns a list of tuples

    #Run the forward model
    Q = slvr.timestep(adjoint_flag=1, qoi_func=qoi_func)
    #Run the adjoint model, computing gradient of Qoi w.r.t cntrl
    dQ_ts = compute_gradient(Q, cntrl)  #Isaac 27

    #Uncomment for Taylor Verification, Comment above two lines
    # param['num_sens'] = 1
    # J = slvr.timestep(adjoint_flag=1, cst_func=slvr.comp_Q_vaf)
    # dJ = compute_gradient(J, slvr.alpha)
    #
    #
    # def forward_ts(alpha_val=None):
    #     slvr.reset_ts_zero()
    #     if alpha_val:
    #         slvr.alpha = alpha_val
    #     return slvr.timestep(adjoint_flag=1, cst_func=slvr.comp_Q_vaf)
    #
    #
    # min_order = taylor_test(lambda alpha : forward_ts(alpha_val = alpha), slvr.alpha,
    #   J_val = J.value(), dJ = dJ, seed = 1e-2, size = 6)
    # sys.exit(os.EX_OK)

    # Output model variables in ParaView+Fenics friendly format
    outdir = params.io.output_dir

    # Output QOI & DQOI (needed for next steps)
    inout.write_qval(slvr.Qval_ts, params)
    inout.write_dqval(dQ_ts, [var.name() for var in cntrl], params)

    # Output final velocity, surface & thickness (visualisation)
    inout.write_variable(slvr.U, params, name="U_fwd")
    inout.write_variable(mdl.surf, params, name="surf_fwd")

    H = project(mdl.H, mdl.Q)
    inout.write_variable(H, params, name="H_fwd")

    return mdl