// The main program int main(int argc, char** argv) { // Initialize libMesh LibMeshInit init(argc, argv); // Parameters GetPot infile("fem_system_params.in"); const Real global_tolerance = infile("global_tolerance", 0.); const unsigned int nelem_target = infile("n_elements", 400); const bool transient = infile("transient", true); const Real deltat = infile("deltat", 0.005); unsigned int n_timesteps = infile("n_timesteps", 1); //const unsigned int coarsegridsize = infile("coarsegridsize", 1); const unsigned int coarserefinements = infile("coarserefinements", 0); const unsigned int max_adaptivesteps = infile("max_adaptivesteps", 10); //const unsigned int dim = 2; #ifdef LIBMESH_HAVE_EXODUS_API const unsigned int write_interval = infile("write_interval", 5); #endif // Create a mesh, with dimension to be overridden later, distributed // across the default MPI communicator. Mesh mesh(init.comm()); GetPot infileForMesh("convdiff_mprime.in"); std::string find_mesh_here = infileForMesh("mesh","psiLF_mesh.xda"); mesh.read(find_mesh_here); std::cout << "Read in mesh from: " << find_mesh_here << "\n\n"; // And an object to refine it MeshRefinement mesh_refinement(mesh); mesh_refinement.coarsen_by_parents() = true; mesh_refinement.absolute_global_tolerance() = global_tolerance; mesh_refinement.nelem_target() = nelem_target; mesh_refinement.refine_fraction() = 0.3; mesh_refinement.coarsen_fraction() = 0.3; mesh_refinement.coarsen_threshold() = 0.1; //mesh_refinement.uniformly_refine(coarserefinements); // Print information about the mesh to the screen. mesh.print_info(); // Create an equation systems object. EquationSystems equation_systems (mesh); // Name system ConvDiff_MprimeSys & system = equation_systems.add_system<ConvDiff_MprimeSys>("Diff_ConvDiff_MprimeSys"); // Steady-state problem system.time_solver = AutoPtr<TimeSolver>(new SteadySolver(system)); // Sanity check that we are indeed solving a steady problem libmesh_assert_equal_to (n_timesteps, 1); // Read in all the equation systems data from the LF solve (system, solutions, rhs, etc) std::string find_psiLF_here = infileForMesh("psiLF_file","psiLF.xda"); std::cout << "Looking for psiLF at: " << find_psiLF_here << "\n\n"; equation_systems.read(find_psiLF_here, READ, EquationSystems::READ_HEADER | EquationSystems::READ_DATA | EquationSystems::READ_ADDITIONAL_DATA); // Check that the norm of the solution read in is what we expect it to be Real readin_L2 = system.calculate_norm(*system.solution, 0, L2); std::cout << "Read in solution norm: "<< readin_L2 << std::endl << std::endl; //DEBUG //equation_systems.write("right_back_out.xda", WRITE, EquationSystems::WRITE_DATA | // EquationSystems::WRITE_ADDITIONAL_DATA); #ifdef LIBMESH_HAVE_GMV //GMVIO(equation_systems.get_mesh()).write_equation_systems(std::string("right_back_out.gmv"), equation_systems); #endif // Initialize the system //equation_systems.init (); //already initialized by read-in // And the nonlinear solver options NewtonSolver *solver = new NewtonSolver(system); system.time_solver->diff_solver() = AutoPtr<DiffSolver>(solver); solver->quiet = infile("solver_quiet", true); solver->verbose = !solver->quiet; solver->max_nonlinear_iterations = infile("max_nonlinear_iterations", 15); solver->relative_step_tolerance = infile("relative_step_tolerance", 1.e-3); solver->relative_residual_tolerance = infile("relative_residual_tolerance", 0.0); solver->absolute_residual_tolerance = infile("absolute_residual_tolerance", 0.0); // And the linear solver options solver->max_linear_iterations = infile("max_linear_iterations", 50000); solver->initial_linear_tolerance = infile("initial_linear_tolerance", 1.e-3); // Print information about the system to the screen. equation_systems.print_info(); // Now we begin the timestep loop to compute the time-accurate // solution of the equations...not that this is transient, but eh, why not... for (unsigned int t_step=0; t_step != n_timesteps; ++t_step) { // A pretty update message std::cout << "\n\nSolving time step " << t_step << ", time = " << system.time << std::endl; // Adaptively solve the timestep unsigned int a_step = 0; for (; a_step != max_adaptivesteps; ++a_step) { // VESTIGIAL for now ('vestigial' eh ? ;) ) std::cout << "\n\n I should be skipped what are you doing here lalalalalalala *!**!*!*!*!*!* \n\n"; system.solve(); system.postprocess(); ErrorVector error; AutoPtr<ErrorEstimator> error_estimator; // To solve to a tolerance in this problem we // need a better estimator than Kelly if (global_tolerance != 0.) { // We can't adapt to both a tolerance and a mesh // size at once libmesh_assert_equal_to (nelem_target, 0); UniformRefinementEstimator *u = new UniformRefinementEstimator; // The lid-driven cavity problem isn't in H1, so // lets estimate L2 error u->error_norm = L2; error_estimator.reset(u); } else { // If we aren't adapting to a tolerance we need a // target mesh size libmesh_assert_greater (nelem_target, 0); // Kelly is a lousy estimator to use for a problem // not in H1 - if we were doing more than a few // timesteps we'd need to turn off or limit the // maximum level of our adaptivity eventually error_estimator.reset(new KellyErrorEstimator); } // Calculate error std::vector<Real> weights(9,1.0); // based on u, v, p, c, their adjoints, and source parameter // Keep the same default norm type. std::vector<FEMNormType> norms(1, error_estimator->error_norm.type(0)); error_estimator->error_norm = SystemNorm(norms, weights); error_estimator->estimate_error(system, error); // Print out status at each adaptive step. Real global_error = error.l2_norm(); std::cout << "Adaptive step " << a_step << ": " << std::endl; if (global_tolerance != 0.) std::cout << "Global_error = " << global_error << std::endl; if (global_tolerance != 0.) std::cout << "Worst element error = " << error.maximum() << ", mean = " << error.mean() << std::endl; if (global_tolerance != 0.) { // If we've reached our desired tolerance, we // don't need any more adaptive steps if (global_error < global_tolerance) break; mesh_refinement.flag_elements_by_error_tolerance(error); } else { // If flag_elements_by_nelem_target returns true, this // should be our last adaptive step. if (mesh_refinement.flag_elements_by_nelem_target(error)) { mesh_refinement.refine_and_coarsen_elements(); equation_systems.reinit(); a_step = max_adaptivesteps; break; } } // Carry out the adaptive mesh refinement/coarsening mesh_refinement.refine_and_coarsen_elements(); equation_systems.reinit(); std::cout << "Refined mesh to " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl; } // End loop over adaptive steps // Do one last solve if necessary if (a_step == max_adaptivesteps) { QoISet qois; std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qois.add_indices(qoi_indices); qois.set_weight(0, 1.0); system.assemble_qoi_sides = true; //QoI doesn't involve sides std::cout << "\n~*~*~*~*~*~*~*~*~ adjoint solve start ~*~*~*~*~*~*~*~*~\n" << std::endl; std::pair<unsigned int, Real> adjsolve = system.adjoint_solve(); std::cout << "number of iterations to solve adjoint: " << adjsolve.first << std::endl; std::cout << "final residual of adjoint solve: " << adjsolve.second << std::endl; std::cout << "\n~*~*~*~*~*~*~*~*~ adjoint solve end ~*~*~*~*~*~*~*~*~" << std::endl; NumericVector<Number> &dual_solution = system.get_adjoint_solution(0); NumericVector<Number> &primal_solution = *system.solution; primal_solution.swap(dual_solution); ExodusII_IO(mesh).write_timestep("super_adjoint.exo", equation_systems, 1, /* This number indicates how many time steps are being written to the file */ system.time); primal_solution.swap(dual_solution); system.assemble(); //overwrite residual read in from psiLF solve // The total error estimate system.postprocess(); //to compute M_HF(psiLF) and M_LF(psiLF) terms Real QoI_error_estimate = (-0.5*(system.rhs)->dot(dual_solution)) + system.get_MHF_psiLF() - system.get_MLF_psiLF(); std::cout << "\n\n 0.5*M'_HF(psiLF)(superadj): " << std::setprecision(17) << 0.5*(system.rhs)->dot(dual_solution) << "\n"; std::cout << " M_HF(psiLF): " << std::setprecision(17) << system.get_MHF_psiLF() << "\n"; std::cout << " M_LF(psiLF): " << std::setprecision(17) << system.get_MLF_psiLF() << "\n"; std::cout << "\n\n Residual L2 norm: " << system.calculate_norm(*system.rhs, L2) << "\n"; std::cout << " Residual discrete L2 norm: " << system.calculate_norm(*system.rhs, DISCRETE_L2) << "\n"; std::cout << " Super-adjoint L2 norm: " << system.calculate_norm(dual_solution, L2) << "\n"; std::cout << " Super-adjoint discrete L2 norm: " << system.calculate_norm(dual_solution, DISCRETE_L2) << "\n"; std::cout << "\n\n QoI error estimate: " << std::setprecision(17) << QoI_error_estimate << "\n\n"; //DEBUG std::cout << "\n------------ herp derp ------------" << std::endl; //libMesh::out.precision(16); //dual_solution.print(); //system.get_adjoint_rhs().print(); AutoPtr<NumericVector<Number> > adjresid = system.solution->clone(); (system.matrix)->vector_mult(*adjresid,system.get_adjoint_solution(0)); SparseMatrix<Number>& adjmat = *system.matrix; (system.matrix)->get_transpose(adjmat); adjmat.vector_mult(*adjresid,system.get_adjoint_solution(0)); //std::cout << "******************** matrix-superadj product (libmesh) ************************" << std::endl; //adjresid->print(); adjresid->add(-1.0, system.get_adjoint_rhs(0)); //std::cout << "******************** superadjoint system residual (libmesh) ***********************" << std::endl; //adjresid->print(); std::cout << "\n\nadjoint system residual (discrete L2): " << system.calculate_norm(*adjresid,DISCRETE_L2) << std::endl; std::cout << "adjoint system residual (L2, all): " << system.calculate_norm(*adjresid,L2) << std::endl; std::cout << "adjoint system residual (L2, 0): " << system.calculate_norm(*adjresid,0,L2) << std::endl; std::cout << "adjoint system residual (L2, 1): " << system.calculate_norm(*adjresid,1,L2) << std::endl; std::cout << "adjoint system residual (L2, 2): " << system.calculate_norm(*adjresid,2,L2) << std::endl; std::cout << "adjoint system residual (L2, 3): " << system.calculate_norm(*adjresid,3,L2) << std::endl; std::cout << "adjoint system residual (L2, 4): " << system.calculate_norm(*adjresid,4,L2) << std::endl; std::cout << "adjoint system residual (L2, 5): " << system.calculate_norm(*adjresid,5,L2) << std::endl; /* AutoPtr<NumericVector<Number> > sadj_matlab = system.solution->clone(); AutoPtr<NumericVector<Number> > adjresid_matlab = system.solution->clone(); if(FILE *fp=fopen("superadj_matlab.txt","r")){ Real value; int counter = 0; int flag = 1; while(flag != -1){ flag = fscanf(fp,"%lf",&value); if(flag != -1){ sadj_matlab->set(counter, value); counter += 1; } } fclose(fp); } (system.matrix)->vector_mult(*adjresid_matlab,*sadj_matlab); //std::cout << "******************** matrix-superadj product (matlab) ***********************" << std::endl; //adjresid_matlab->print(); adjresid_matlab->add(-1.0, system.get_adjoint_rhs(0)); //std::cout << "******************** superadjoint system residual (matlab) ***********************" << std::endl; //adjresid_matlab->print(); std::cout << "\n\nmatlab import adjoint system residual (discrete L2): " << system.calculate_norm(*adjresid_matlab,DISCRETE_L2) << "\n" << std::endl; */ /* AutoPtr<NumericVector<Number> > sadj_fwd_hack = system.solution->clone(); AutoPtr<NumericVector<Number> > adjresid_fwd_hack = system.solution->clone(); if(FILE *fp=fopen("superadj_forward_hack.txt","r")){ Real value; int counter = 0; int flag = 1; while(flag != -1){ flag = fscanf(fp,"%lf",&value); if(flag != -1){ sadj_fwd_hack->set(counter, value); counter += 1; } } fclose(fp); } (system.matrix)->vector_mult(*adjresid_fwd_hack,*sadj_fwd_hack); //std::cout << "******************** matrix-superadj product (fwd_hack) ***********************" << std::endl; //adjresid_fwd_hack->print(); adjresid_fwd_hack->add(-1.0, system.get_adjoint_rhs(0)); //std::cout << "******************** superadjoint system residual (fwd_hack) ***********************" << std::endl; //adjresid_fwd_hack->print(); std::cout << "\n\nfwd_hack import adjoint system residual (discrete L2): " << system.calculate_norm(*adjresid_fwd_hack,DISCRETE_L2) << "\n" << std::endl; std::cout << "fwd_hack adjoint system residual (L2, 0): " << system.calculate_norm(*adjresid_fwd_hack,0,L2) << std::endl; std::cout << "fwd_hack adjoint system residual (L2, 1): " << system.calculate_norm(*adjresid_fwd_hack,1,L2) << std::endl; std::cout << "fwd_hack adjoint system residual (L2, 2): " << system.calculate_norm(*adjresid_fwd_hack,2,L2) << std::endl; std::cout << "fwd_hack adjoint system residual (L2, 3): " << system.calculate_norm(*adjresid_fwd_hack,3,L2) << std::endl; std::cout << "fwd_hack adjoint system residual (L2, 4): " << system.calculate_norm(*adjresid_fwd_hack,4,L2) << std::endl; std::cout << "fwd_hack adjoint system residual (L2, 5): " << system.calculate_norm(*adjresid_fwd_hack,5,L2) << std::endl; */ //std::cout << "************************ system.matrix ***********************" << std::endl; //system.matrix->print(); std::cout << "\n------------ herp derp ------------" << std::endl; // The cell wise breakdown ErrorVector cell_wise_error; cell_wise_error.resize((system.rhs)->size()); for(unsigned int i = 0; i < (system.rhs)->size() ; i++) { if(i < system.get_mesh().n_elem()) cell_wise_error[i] = fabs(-0.5*((system.rhs)->el(i) * dual_solution(i)) + system.get_MHF_psiLF(i) - system.get_MLF_psiLF(i)); else cell_wise_error[i] = fabs(-0.5*((system.rhs)->el(i) * dual_solution(i))); /*csv from 'save data' from gmv output gives a few values at each node point (value for every element that shares that node), yet paraview display only seems to show one of them -> the value in an element is given at each of the nodes that it has, hence the repetition; what is displayed in paraview is each element's value; even though MHF_psiLF and MLF_psiLF are stored by element this seems to give elemental contributions that agree with if we had taken the superadj-residual dot product by integrating over elements*/ /*at higher mesh resolutions and lower k, weird-looking artifacts start to appear and it no longer agrees with output from manual integration of superadj-residual...*/ } // Plot it std::ostringstream error_gmv; error_gmv << "error.gmv"; cell_wise_error.plot_error(error_gmv.str(), equation_systems.get_mesh()); //alternate element-wise breakdown, outputed as values matched to element centroids; for matlab plotz primal_solution.swap(dual_solution); system.postprocess(1); primal_solution.swap(dual_solution); system.postprocess(2); std::cout << "\n\n -0.5*M'_HF(psiLF)(superadj): " << std::setprecision(17) << system.get_half_adj_weighted_resid() << "\n"; primal_solution.swap(dual_solution); std::string write_error_here = infileForMesh("error_est_output_file", "error_est_breakdown.dat"); std::ofstream output(write_error_here); for(unsigned int i = 0 ; i < system.get_mesh().n_elem(); i++) { Point elem_cent = system.get_mesh().elem(i)->centroid(); if(output.is_open()) { output << elem_cent(0) << " " << elem_cent(1) << " " << fabs(system.get_half_adj_weighted_resid(i) + system.get_MHF_psiLF(i) - system.get_MLF_psiLF(i)) << "\n"; } } output.close(); } // End if at max adaptive steps #ifdef LIBMESH_HAVE_EXODUS_API // Write out this timestep if we're requested to if ((t_step+1)%write_interval == 0) { std::ostringstream file_name; /* // We write the file in the ExodusII format. file_name << "out_" << std::setw(3) << std::setfill('0') << std::right << t_step+1 << ".e"; //this should write out the primal which should be the same as what's read in... ExodusII_IO(mesh).write_timestep(file_name.str(), equation_systems, 1, //number of time steps written to file system.time); */ } #endif // #ifdef LIBMESH_HAVE_EXODUS_API } // All done. return 0; } //end main
// The main program. int main (int argc, char** argv) { // Initialize libMesh. LibMeshInit init (argc, argv); // Skip adaptive examples on a non-adaptive libMesh build #ifndef LIBMESH_ENABLE_AMR libmesh_example_assert(false, "--enable-amr"); #else // Only our PETSc interface currently supports adjoint solves libmesh_example_assert(libMesh::default_solver_package() == PETSC_SOLVERS, "--enable-petsc"); std::cout << "Started " << argv[0] << std::endl; // Make sure the general input file exists, and parse it { std::ifstream i("general.in"); if (!i) { std::cerr << '[' << libMesh::processor_id() << "] Can't find general.in; exiting early." << std::endl; libmesh_error(); } } GetPot infile("general.in"); GetPot infile_l_shaped("l-shaped.in"); // Read in parameters from the input file FEMParameters param; param.read(infile); // Skip this default-2D example if libMesh was compiled as 1D-only. libmesh_example_assert(2 <= LIBMESH_DIM, "2D support"); // Create a mesh. Mesh mesh; // And an object to refine it AutoPtr<MeshRefinement> mesh_refinement = build_mesh_refinement(mesh, param); // And an EquationSystems to run on it EquationSystems equation_systems (mesh); std::cout << "Reading in and building the mesh" << std::endl; // Read in the mesh mesh.read(param.domainfile.c_str()); // Make all the elements of the mesh second order so we can compute // with a higher order basis mesh.all_second_order(); // Create a mesh refinement object to do the initial uniform refinements // on the coarse grid read in from lshaped.xda MeshRefinement initial_uniform_refinements(mesh); initial_uniform_refinements.uniformly_refine(param.coarserefinements); std::cout << "Building system" << std::endl; // Build the FEMSystem LaplaceSystem &system = equation_systems.add_system<LaplaceSystem> ("LaplaceSystem"); // Set its parameters set_system_parameters(system, param); std::cout << "Initializing systems" << std::endl; equation_systems.init (); // Print information about the mesh and system to the screen. mesh.print_info(); equation_systems.print_info(); { // Adaptively solve the timestep unsigned int a_step = 0; for (; a_step != param.max_adaptivesteps; ++a_step) { // We can't adapt to both a tolerance and a // target mesh size if (param.global_tolerance != 0.) libmesh_assert (param.nelem_target == 0); // If we aren't adapting to a tolerance we need a // target mesh size else libmesh_assert (param.nelem_target > 0); // Solve the forward problem system.solve(); // Write out the computed primal solution write_output(equation_systems, a_step, "primal"); // Get a pointer to the primal solution vector NumericVector<Number> &primal_solution = *system.solution; // Declare a QoISet object, we need this object to set weights for our QoI error contributions QoISet qois; // Declare a qoi_indices vector, each index will correspond to a QoI std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qoi_indices.push_back(1); qois.add_indices(qoi_indices); // Set weights for each index, these will weight the contribution of each QoI in the final error // estimate to be used for flagging elements for refinement qois.set_weight(0, 0.5); qois.set_weight(1, 0.5); // A SensitivityData object to hold the qois and parameters SensitivityData sensitivities(qois, system, system.get_parameter_vector()); // Make sure we get the contributions to the adjoint RHS from the sides system.assemble_qoi_sides = true; // Compute the sensitivities system.adjoint_qoi_parameter_sensitivity(qois, system.get_parameter_vector(), sensitivities); GetPot infile("l-shaped.in"); Number sensitivity_QoI_0_0_computed = sensitivities[0][0]; Number sensitivity_QoI_0_0_exact = infile("sensitivity_0_0", 0.0); Number sensitivity_QoI_0_1_computed = sensitivities[0][1]; Number sensitivity_QoI_0_1_exact = infile("sensitivity_0_1", 0.0); std::cout << "Adaptive step " << a_step << ", we have " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl ; std::cout<<"Sensitivity of QoI one to Parameter one is "<<sensitivity_QoI_0_0_computed<<std::endl; std::cout<<"Sensitivity of QoI one to Parameter two is "<<sensitivity_QoI_0_1_computed<<std::endl; std::cout<< "The relative error in sensitivity QoI_0_0 is " << std::setprecision(17) << std::abs(sensitivity_QoI_0_0_computed - sensitivity_QoI_0_0_exact) / std::abs(sensitivity_QoI_0_0_exact) << std::endl; std::cout<< "The relative error in sensitivity QoI_0_1 is " << std::setprecision(17) << std::abs(sensitivity_QoI_0_1_computed - sensitivity_QoI_0_1_exact) / std::abs(sensitivity_QoI_0_1_exact) << std::endl << std::endl; // Get a pointer to the solution vector of the adjoint problem for QoI 0 NumericVector<Number> &dual_solution_0 = system.get_adjoint_solution(0); // Swap the primal and dual solutions so we can write out the adjoint solution primal_solution.swap(dual_solution_0); write_output(equation_systems, a_step, "adjoint_0"); // Swap back primal_solution.swap(dual_solution_0); // We have to refine either based on reaching an error tolerance or // a number of elements target, which should be verified above // Otherwise we flag elements by error tolerance or nelem target // Uniform refinement if(param.refine_uniformly) { std::cout<<"Refining Uniformly"<<std::endl<<std::endl; mesh_refinement->uniformly_refine(1); } // Adaptively refine based on reaching an error tolerance else if(param.global_tolerance >= 0. && param.nelem_target == 0.) { // Now we construct the data structures for the mesh refinement process ErrorVector error; // Build an error estimator object AutoPtr<ErrorEstimator> error_estimator = build_error_estimator(param); // Estimate the error in each element using the Adjoint Residual or Kelly error estimator error_estimator->estimate_error(system, error); mesh_refinement->flag_elements_by_error_tolerance (error); mesh_refinement->refine_and_coarsen_elements(); } // Adaptively refine based on reaching a target number of elements else { // Now we construct the data structures for the mesh refinement process ErrorVector error; // Build an error estimator object AutoPtr<ErrorEstimator> error_estimator = build_error_estimator(param); // Estimate the error in each element using the Adjoint Residual or Kelly error estimator error_estimator->estimate_error(system, error); if (mesh.n_active_elem() >= param.nelem_target) { std::cout<<"We reached the target number of elements."<<std::endl <<std::endl; break; } mesh_refinement->flag_elements_by_nelem_target (error); mesh_refinement->refine_and_coarsen_elements(); } // Dont forget to reinit the system after each adaptive refinement ! equation_systems.reinit(); std::cout << "Refined mesh to " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl; } // Do one last solve if necessary if (a_step == param.max_adaptivesteps) { system.solve(); write_output(equation_systems, a_step, "primal"); NumericVector<Number> &primal_solution = *system.solution; QoISet qois; std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qoi_indices.push_back(1); qois.add_indices(qoi_indices); qois.set_weight(0, 0.5); qois.set_weight(1, 0.5); SensitivityData sensitivities(qois, system, system.get_parameter_vector()); system.assemble_qoi_sides = true; system.adjoint_qoi_parameter_sensitivity(qois, system.get_parameter_vector(), sensitivities); GetPot infile("l-shaped.in"); Number sensitivity_QoI_0_0_computed = sensitivities[0][0]; Number sensitivity_QoI_0_0_exact = infile("sensitivity_0_0", 0.0); Number sensitivity_QoI_0_1_computed = sensitivities[0][1]; Number sensitivity_QoI_0_1_exact = infile("sensitivity_0_1", 0.0); std::cout << "Adaptive step " << a_step << ", we have " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl ; std::cout<<"Sensitivity of QoI one to Parameter one is "<<sensitivity_QoI_0_0_computed<<std::endl; std::cout<<"Sensitivity of QoI one to Parameter two is "<<sensitivity_QoI_0_1_computed<<std::endl; std::cout<< "The error in sensitivity QoI_0_0 is " << std::setprecision(17) << std::abs(sensitivity_QoI_0_0_computed - sensitivity_QoI_0_0_exact)/sensitivity_QoI_0_0_exact << std::endl; std::cout<< "The error in sensitivity QoI_0_1 is " << std::setprecision(17) << std::abs(sensitivity_QoI_0_1_computed - sensitivity_QoI_0_1_exact)/sensitivity_QoI_0_1_exact << std::endl << std::endl; NumericVector<Number> &dual_solution_0 = system.get_adjoint_solution(0); primal_solution.swap(dual_solution_0); write_output(equation_systems, a_step, "adjoint_0"); primal_solution.swap(dual_solution_0); } } std::cerr << '[' << libMesh::processor_id() << "] Completing output." << std::endl; #endif // #ifndef LIBMESH_ENABLE_AMR // All done. return 0; }
// The main program. int main (int argc, char** argv) { // Initialize libMesh. LibMeshInit init (argc, argv); // This example requires a linear solver package. libmesh_example_requires(libMesh::default_solver_package() != INVALID_SOLVER_PACKAGE, "--enable-petsc, --enable-trilinos, or --enable-eigen"); // Skip adaptive examples on a non-adaptive libMesh build #ifndef LIBMESH_ENABLE_AMR libmesh_example_requires(false, "--enable-amr"); #else // This doesn't converge with Eigen BICGSTAB for some reason... libmesh_example_requires(libMesh::default_solver_package() != EIGEN_SOLVERS, "--enable-petsc"); libMesh::out << "Started " << argv[0] << std::endl; // Make sure the general input file exists, and parse it { std::ifstream i("general.in"); if (!i) libmesh_error_msg('[' << init.comm().rank() << "] Can't find general.in; exiting early."); } GetPot infile("general.in"); // Read in parameters from the input file FEMParameters param(init.comm()); param.read(infile); // Skip this default-2D example if libMesh was compiled as 1D-only. libmesh_example_requires(2 <= LIBMESH_DIM, "2D support"); // Create a mesh, with dimension to be overridden later, distributed // across the default MPI communicator. Mesh mesh(init.comm()); // And an object to refine it std::unique_ptr<MeshRefinement> mesh_refinement = build_mesh_refinement(mesh, param); // And an EquationSystems to run on it EquationSystems equation_systems (mesh); libMesh::out << "Reading in and building the mesh" << std::endl; // Read in the mesh mesh.read(param.domainfile.c_str()); // Make all the elements of the mesh second order so we can compute // with a higher order basis mesh.all_second_order(); // Create a mesh refinement object to do the initial uniform refinements // on the coarse grid read in from lshaped.xda MeshRefinement initial_uniform_refinements(mesh); initial_uniform_refinements.uniformly_refine(param.coarserefinements); libMesh::out << "Building system" << std::endl; // Build the FEMSystem LaplaceSystem & system = equation_systems.add_system<LaplaceSystem> ("LaplaceSystem"); // Set its parameters set_system_parameters(system, param); libMesh::out << "Initializing systems" << std::endl; equation_systems.init (); // Print information about the mesh and system to the screen. mesh.print_info(); equation_systems.print_info(); LinearSolver<Number> *linear_solver = system.get_linear_solver(); { // Adaptively solve the timestep unsigned int a_step = 0; for (; a_step != param.max_adaptivesteps; ++a_step) { // We can't adapt to both a tolerance and a // target mesh size if (param.global_tolerance != 0.) libmesh_assert_equal_to (param.nelem_target, 0); // If we aren't adapting to a tolerance we need a // target mesh size else libmesh_assert_greater (param.nelem_target, 0); linear_solver->reuse_preconditioner(false); // Solve the forward problem system.solve(); // Write out the computed primal solution write_output(equation_systems, a_step, "primal", param); // Get a pointer to the primal solution vector NumericVector<Number> & primal_solution = *system.solution; // Declare a QoISet object, we need this object to set weights for our QoI error contributions QoISet qois; // Declare a qoi_indices vector, each index will correspond to a QoI std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qoi_indices.push_back(1); qois.add_indices(qoi_indices); // Set weights for each index, these will weight the contribution of each QoI in the final error // estimate to be used for flagging elements for refinement qois.set_weight(0, 0.5); qois.set_weight(1, 0.5); // Make sure we get the contributions to the adjoint RHS from the sides system.assemble_qoi_sides = true; // We are about to solve the adjoint system, but before we do this we see the same preconditioner // flag to reuse the preconditioner from the forward solver linear_solver->reuse_preconditioner(param.reuse_preconditioner); // Solve the adjoint system. This takes the transpose of the stiffness matrix and then // solves the resulting system system.adjoint_solve(); // Now that we have solved the adjoint, set the adjoint_already_solved boolean to true, so we dont solve unnecessarily in the error estimator system.set_adjoint_already_solved(true); // Get a pointer to the solution vector of the adjoint problem for QoI 0 NumericVector<Number> & dual_solution_0 = system.get_adjoint_solution(0); // Swap the primal and dual solutions so we can write out the adjoint solution primal_solution.swap(dual_solution_0); write_output(equation_systems, a_step, "adjoint_0", param); // Swap back primal_solution.swap(dual_solution_0); // Get a pointer to the solution vector of the adjoint problem for QoI 0 NumericVector<Number> & dual_solution_1 = system.get_adjoint_solution(1); // Swap again primal_solution.swap(dual_solution_1); write_output(equation_systems, a_step, "adjoint_1", param); // Swap back again primal_solution.swap(dual_solution_1); libMesh::out << "Adaptive step " << a_step << ", we have " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl; // Postprocess, compute the approximate QoIs and write them out to the console libMesh::out << "Postprocessing: " << std::endl; system.postprocess_sides = true; system.postprocess(); Number QoI_0_computed = system.get_QoI_value("computed", 0); Number QoI_0_exact = system.get_QoI_value("exact", 0); Number QoI_1_computed = system.get_QoI_value("computed", 1); Number QoI_1_exact = system.get_QoI_value("exact", 1); libMesh::out << "The relative error in QoI 0 is " << std::setprecision(17) << std::abs(QoI_0_computed - QoI_0_exact) / std::abs(QoI_0_exact) << std::endl; libMesh::out << "The relative error in QoI 1 is " << std::setprecision(17) << std::abs(QoI_1_computed - QoI_1_exact) / std::abs(QoI_1_exact) << std::endl << std::endl; // We will declare an error vector for passing to the adjoint refinement error estimator ErrorVector QoI_elementwise_error; // Build an adjoint refinement error estimator object std::unique_ptr<AdjointRefinementEstimator> adjoint_refinement_error_estimator = build_adjoint_refinement_error_estimator(qois); // Estimate the error in each element using the Adjoint Refinement estimator adjoint_refinement_error_estimator->estimate_error(system, QoI_elementwise_error); // Print out the computed error estimate, note that we access the global error estimates // using an accessor function, right now sum(QoI_elementwise_error) != global_QoI_error_estimate libMesh::out << "The computed relative error in QoI 0 is " << std::setprecision(17) << std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(0)) / std::abs(QoI_0_exact) << std::endl; libMesh::out << "The computed relative error in QoI 1 is " << std::setprecision(17) << std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(1)) / std::abs(QoI_1_exact) << std::endl << std::endl; // Also print out effectivity indices (estimated error/true error) libMesh::out << "The effectivity index for the computed error in QoI 0 is " << std::setprecision(17) << std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(0)) / std::abs(QoI_0_computed - QoI_0_exact) << std::endl; libMesh::out << "The effectivity index for the computed error in QoI 1 is " << std::setprecision(17) << std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(1)) / std::abs(QoI_1_computed - QoI_1_exact) << std::endl << std::endl; // For refinement purposes we need to sort by error // *magnitudes*, but AdjointRefinement gives us signed errors. if (!param.refine_uniformly) for (std::size_t i=0; i<QoI_elementwise_error.size(); i++) if (QoI_elementwise_error[i] != 0.) QoI_elementwise_error[i] = std::abs(QoI_elementwise_error[i]); // We have to refine either based on reaching an error tolerance or // a number of elements target, which should be verified above // Otherwise we flag elements by error tolerance or nelem target // Uniform refinement if (param.refine_uniformly) { mesh_refinement->uniformly_refine(1); } // Adaptively refine based on reaching an error tolerance else if (param.global_tolerance >= 0. && param.nelem_target == 0.) { mesh_refinement->flag_elements_by_error_tolerance (QoI_elementwise_error); mesh_refinement->refine_and_coarsen_elements(); } // Adaptively refine based on reaching a target number of elements else { if (mesh.n_active_elem() >= param.nelem_target) { libMesh::out << "We reached the target number of elements." << std::endl << std::endl; break; } mesh_refinement->flag_elements_by_nelem_target (QoI_elementwise_error); mesh_refinement->refine_and_coarsen_elements(); } // Dont forget to reinit the system after each adaptive refinement ! equation_systems.reinit(); libMesh::out << "Refined mesh to " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl; } // Do one last solve if necessary if (a_step == param.max_adaptivesteps) { linear_solver->reuse_preconditioner(false); system.solve(); write_output(equation_systems, a_step, "primal", param); NumericVector<Number> & primal_solution = *system.solution; QoISet qois; std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qoi_indices.push_back(1); qois.add_indices(qoi_indices); qois.set_weight(0, 0.5); qois.set_weight(1, 0.5); system.assemble_qoi_sides = true; linear_solver->reuse_preconditioner(param.reuse_preconditioner); system.adjoint_solve(); // Now that we have solved the adjoint, set the adjoint_already_solved boolean to true, so we dont solve unnecessarily in the error estimator system.set_adjoint_already_solved(true); NumericVector<Number> & dual_solution_0 = system.get_adjoint_solution(0); primal_solution.swap(dual_solution_0); write_output(equation_systems, a_step, "adjoint_0", param); primal_solution.swap(dual_solution_0); NumericVector<Number> & dual_solution_1 = system.get_adjoint_solution(1); primal_solution.swap(dual_solution_1); write_output(equation_systems, a_step, "adjoint_1", param); primal_solution.swap(dual_solution_1); libMesh::out << "Adaptive step " << a_step << ", we have " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl; libMesh::out << "Postprocessing: " << std::endl; system.postprocess_sides = true; system.postprocess(); Number QoI_0_computed = system.get_QoI_value("computed", 0); Number QoI_0_exact = system.get_QoI_value("exact", 0); Number QoI_1_computed = system.get_QoI_value("computed", 1); Number QoI_1_exact = system.get_QoI_value("exact", 1); libMesh::out << "The relative error in QoI 0 is " << std::setprecision(17) << std::abs(QoI_0_computed - QoI_0_exact) / std::abs(QoI_0_exact) << std::endl; libMesh::out << "The relative error in QoI 1 is " << std::setprecision(17) << std::abs(QoI_1_computed - QoI_1_exact) / std::abs(QoI_1_exact) << std::endl << std::endl; // We will declare an error vector for passing to the adjoint refinement error estimator // Right now, only the first entry of this vector will be filled (with the global QoI error estimate) // Later, each entry of the vector will contain elementwise error that the user can sum to get the total error ErrorVector QoI_elementwise_error; // Build an adjoint refinement error estimator object std::unique_ptr<AdjointRefinementEstimator> adjoint_refinement_error_estimator = build_adjoint_refinement_error_estimator(qois); // Estimate the error in each element using the Adjoint Refinement estimator adjoint_refinement_error_estimator->estimate_error(system, QoI_elementwise_error); // Print out the computed error estimate, note that we access the global error estimates // using an accessor function, right now sum(QoI_elementwise_error) != global_QoI_error_estimate libMesh::out << "The computed relative error in QoI 0 is " << std::setprecision(17) << std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(0)) / std::abs(QoI_0_exact) << std::endl; libMesh::out << "The computed relative error in QoI 1 is " << std::setprecision(17) << std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(1)) / std::abs(QoI_1_exact) << std::endl << std::endl; // Also print out effectivity indices (estimated error/true error) libMesh::out << "The effectivity index for the computed error in QoI 0 is " << std::setprecision(17) << std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(0)) / std::abs(QoI_0_computed - QoI_0_exact) << std::endl; libMesh::out << "The effectivity index for the computed error in QoI 1 is " << std::setprecision(17) << std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(1)) / std::abs(QoI_1_computed - QoI_1_exact) << std::endl << std::endl; // Hard coded assert to ensure that the actual numbers we are getting are what they should be // The effectivity index isn't exactly reproducible at single precision // libmesh_assert_less(std::abs(std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(0)) / std::abs(QoI_0_computed - QoI_0_exact) - 0.84010976704434637), 1.e-5); // libmesh_assert_less(std::abs(std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(1)) / std::abs(QoI_1_computed - QoI_1_exact) - 0.48294428289950514), 1.e-5); // But the effectivity indices should always be sane libmesh_assert_less(std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(0)) / std::abs(QoI_0_computed - QoI_0_exact), 2.5); libmesh_assert_greater(std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(0)) / std::abs(QoI_0_computed - QoI_0_exact), .4); libmesh_assert_less(std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(1)) / std::abs(QoI_1_computed - QoI_1_exact), 2.5); libmesh_assert_greater(std::abs(adjoint_refinement_error_estimator->get_global_QoI_error_estimate(1)) / std::abs(QoI_1_computed - QoI_1_exact), .4); // And the computed errors should still be low libmesh_assert_less(std::abs(QoI_0_computed - QoI_0_exact), 2e-4); libmesh_assert_less(std::abs(QoI_1_computed - QoI_1_exact), 2e-4); } } libMesh::err << '[' << mesh.processor_id() << "] Completing output." << std::endl; #endif // #ifndef LIBMESH_ENABLE_AMR // All done. return 0; }
// The main program. int main (int argc, char** argv) { // Initialize libMesh. LibMeshInit init (argc, argv); // Skip adaptive examples on a non-adaptive libMesh build #ifndef LIBMESH_ENABLE_AMR libmesh_example_assert(false, "--enable-amr"); #else std::cout << "Started " << argv[0] << std::endl; // Make sure the general input file exists, and parse it { std::ifstream i("general.in"); if (!i) { std::cerr << '[' << init.comm().rank() << "] Can't find general.in; exiting early." << std::endl; libmesh_error(); } } GetPot infile("general.in"); // Read in parameters from the input file FEMParameters param; param.read(infile); // Skip this default-2D example if libMesh was compiled as 1D-only. libmesh_example_assert(2 <= LIBMESH_DIM, "2D support"); // Create a mesh, with dimension to be overridden later, distributed // across the default MPI communicator. Mesh mesh(init.comm()); // And an object to refine it AutoPtr<MeshRefinement> mesh_refinement = build_mesh_refinement(mesh, param); // And an EquationSystems to run on it EquationSystems equation_systems (mesh); std::cout << "Reading in and building the mesh" << std::endl; // Read in the mesh mesh.read(param.domainfile.c_str()); // Make all the elements of the mesh second order so we can compute // with a higher order basis mesh.all_second_order(); // Create a mesh refinement object to do the initial uniform refinements // on the coarse grid read in from lshaped.xda MeshRefinement initial_uniform_refinements(mesh); initial_uniform_refinements.uniformly_refine(param.coarserefinements); std::cout << "Building system" << std::endl; // Build the FEMSystem LaplaceSystem &system = equation_systems.add_system<LaplaceSystem> ("LaplaceSystem"); // Set its parameters set_system_parameters(system, param); std::cout << "Initializing systems" << std::endl; equation_systems.init (); // Print information about the mesh and system to the screen. mesh.print_info(); equation_systems.print_info(); LinearSolver<Number> *linear_solver = system.get_linear_solver(); { // Adaptively solve the timestep unsigned int a_step = 0; for (; a_step != param.max_adaptivesteps; ++a_step) { // We can't adapt to both a tolerance and a // target mesh size if (param.global_tolerance != 0.) libmesh_assert_equal_to (param.nelem_target, 0); // If we aren't adapting to a tolerance we need a // target mesh size else libmesh_assert_greater (param.nelem_target, 0); linear_solver->reuse_preconditioner(false); // Solve the forward problem system.solve(); // Write out the computed primal solution write_output(equation_systems, a_step, "primal"); // Get a pointer to the primal solution vector NumericVector<Number> &primal_solution = *system.solution; // Declare a QoISet object, we need this object to set weights for our QoI error contributions QoISet qois; // Declare a qoi_indices vector, each index will correspond to a QoI std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qoi_indices.push_back(1); qois.add_indices(qoi_indices); // Set weights for each index, these will weight the contribution of each QoI in the final error // estimate to be used for flagging elements for refinement qois.set_weight(0, 0.5); qois.set_weight(1, 0.5); // Make sure we get the contributions to the adjoint RHS from the sides system.assemble_qoi_sides = true; // We are about to solve the adjoint system, but before we do this we see the same preconditioner // flag to reuse the preconditioner from the forward solver linear_solver->reuse_preconditioner(param.reuse_preconditioner); // Solve the adjoint system. This takes the transpose of the stiffness matrix and then // solves the resulting system system.adjoint_solve(); // Now that we have solved the adjoint, set the adjoint_already_solved boolean to true, so we dont solve unneccesarily in the error estimator system.set_adjoint_already_solved(true); // Get a pointer to the solution vector of the adjoint problem for QoI 0 NumericVector<Number> &dual_solution_0 = system.get_adjoint_solution(0); // Swap the primal and dual solutions so we can write out the adjoint solution primal_solution.swap(dual_solution_0); write_output(equation_systems, a_step, "adjoint_0"); // Swap back primal_solution.swap(dual_solution_0); // Get a pointer to the solution vector of the adjoint problem for QoI 0 NumericVector<Number> &dual_solution_1 = system.get_adjoint_solution(1); // Swap again primal_solution.swap(dual_solution_1); write_output(equation_systems, a_step, "adjoint_1"); // Swap back again primal_solution.swap(dual_solution_1); std::cout << "Adaptive step " << a_step << ", we have " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl ; // Postprocess, compute the approximate QoIs and write them out to the console std::cout << "Postprocessing: " << std::endl; system.postprocess_sides = true; system.postprocess(); Number QoI_0_computed = system.get_QoI_value("computed", 0); Number QoI_0_exact = system.get_QoI_value("exact", 0); Number QoI_1_computed = system.get_QoI_value("computed", 1); Number QoI_1_exact = system.get_QoI_value("exact", 1); std::cout<< "The relative error in QoI 0 is " << std::setprecision(17) << std::abs(QoI_0_computed - QoI_0_exact) / std::abs(QoI_0_exact) << std::endl; std::cout<< "The relative error in QoI 1 is " << std::setprecision(17) << std::abs(QoI_1_computed - QoI_1_exact) / std::abs(QoI_1_exact) << std::endl << std::endl; // Now we construct the data structures for the mesh refinement process ErrorVector error; // Build an error estimator object AutoPtr<ErrorEstimator> error_estimator = build_error_estimator(param, qois); // Estimate the error in each element using the Adjoint Residual or Kelly error estimator error_estimator->estimate_error(system, error); // We have to refine either based on reaching an error tolerance or // a number of elements target, which should be verified above // Otherwise we flag elements by error tolerance or nelem target // Uniform refinement if(param.refine_uniformly) { mesh_refinement->uniformly_refine(1); } // Adaptively refine based on reaching an error tolerance else if(param.global_tolerance >= 0. && param.nelem_target == 0.) { mesh_refinement->flag_elements_by_error_tolerance (error); mesh_refinement->refine_and_coarsen_elements(); } // Adaptively refine based on reaching a target number of elements else { if (mesh.n_active_elem() >= param.nelem_target) { std::cout<<"We reached the target number of elements."<<std::endl <<std::endl; break; } mesh_refinement->flag_elements_by_nelem_target (error); mesh_refinement->refine_and_coarsen_elements(); } // Dont forget to reinit the system after each adaptive refinement ! equation_systems.reinit(); std::cout << "Refined mesh to " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl; } // Do one last solve if necessary if (a_step == param.max_adaptivesteps) { linear_solver->reuse_preconditioner(false); system.solve(); write_output(equation_systems, a_step, "primal"); NumericVector<Number> &primal_solution = *system.solution; QoISet qois; std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qoi_indices.push_back(1); qois.add_indices(qoi_indices); qois.set_weight(0, 0.5); qois.set_weight(1, 0.5); system.assemble_qoi_sides = true; linear_solver->reuse_preconditioner(param.reuse_preconditioner); system.adjoint_solve(); // Now that we have solved the adjoint, set the adjoint_already_solved boolean to true, so we dont solve unneccesarily in the error estimator system.set_adjoint_already_solved(true); NumericVector<Number> &dual_solution_0 = system.get_adjoint_solution(0); primal_solution.swap(dual_solution_0); write_output(equation_systems, a_step, "adjoint_0"); primal_solution.swap(dual_solution_0); NumericVector<Number> &dual_solution_1 = system.get_adjoint_solution(1); primal_solution.swap(dual_solution_1); write_output(equation_systems, a_step, "adjoint_1"); primal_solution.swap(dual_solution_1); std::cout << "Adaptive step " << a_step << ", we have " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl ; std::cout << "Postprocessing: " << std::endl; system.postprocess_sides = true; system.postprocess(); Number QoI_0_computed = system.get_QoI_value("computed", 0); Number QoI_0_exact = system.get_QoI_value("exact", 0); Number QoI_1_computed = system.get_QoI_value("computed", 1); Number QoI_1_exact = system.get_QoI_value("exact", 1); std::cout<< "The relative error in QoI 0 is " << std::setprecision(17) << std::abs(QoI_0_computed - QoI_0_exact) / std::abs(QoI_0_exact) << std::endl; std::cout<< "The relative error in QoI 1 is " << std::setprecision(17) << std::abs(QoI_1_computed - QoI_1_exact) / std::abs(QoI_1_exact) << std::endl << std::endl; } } std::cerr << '[' << mesh.processor_id() << "] Completing output." << std::endl; #endif // #ifndef LIBMESH_ENABLE_AMR // All done. return 0; }
// ********************************************************************** // The main program. int main (int argc, char** argv) { // Initialize libMesh. LibMeshInit init (argc, argv); // Skip adaptive examples on a non-adaptive libMesh build #ifndef LIBMESH_ENABLE_AMR libmesh_example_requires(false, "--enable-amr"); #else std::cout << "Started " << argv[0] << std::endl; // Make sure the general input file exists, and parse it { std::ifstream i("general.in"); if (!i) libmesh_error_msg('[' << init.comm().rank() << "] Can't find general.in; exiting early."); } GetPot infile("general.in"); // Read in parameters from the input file FEMParameters param; param.read(infile); // Create a mesh, with dimension to be overridden later, distributed // across the default MPI communicator. Mesh mesh(init.comm()); // And an object to refine it AutoPtr<MeshRefinement> mesh_refinement = build_mesh_refinement(mesh, param); // And an EquationSystems to run on it EquationSystems equation_systems (mesh); std::cout << "Building the mesh" << std::endl; MeshTools::Generation::build_square (mesh, 20, 20, 0., 1., 0., 1., QUAD9); //if changed, check pressure pinning // Create a mesh refinement object to do the initial uniform refinements //MeshRefinement initial_uniform_refinements(mesh); //initial_uniform_refinements.uniformly_refine(param.coarserefinements); std::cout << "Building system" << std::endl; // Build the FEMSystem ConvDiffSys &system = equation_systems.add_system<ConvDiffSys> ("ConvDiffSys"); // Set its parameters set_system_parameters(system, param); std::cout << "Initializing systems" << std::endl; equation_systems.init (); // Print information about the mesh and system to the screen. mesh.print_info(); equation_systems.print_info(); LinearSolver<Number> *linear_solver = system.get_linear_solver(); { // Adaptively solve the timestep unsigned int a_step = 0; for (; a_step != param.max_adaptivesteps; ++a_step) { // We can't adapt to both a tolerance and a // target mesh size if (param.global_tolerance != 0.) libmesh_assert_equal_to (param.nelem_target, 0); // If we aren't adapting to a tolerance we need a // target mesh size else libmesh_assert_greater (param.nelem_target, 0); linear_solver->reuse_preconditioner(false); //can reuse for adjoint, but not for new forwards solve // Solve the forward problem system.solve(); // Write out the computed primal solution write_output(equation_systems, a_step, "primal"); // Get a pointer to the primal solution vector NumericVector<Number> &primal_solution = *system.solution; // Declare a QoISet object, we need this object to set weights for our QoI error contributions QoISet qois; // Declare a qoi_indices vector, each index will correspond to a QoI std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qois.add_indices(qoi_indices); // Set weights for each index, these will weight the contribution of each QoI in the final error // estimate to be used for flagging elements for refinement qois.set_weight(0, 1.0); // A SensitivityData object to hold the qois and parameters SensitivityData sensitivities_adj(qois, system, system.get_parameter_vector()); //use adjoint solve SensitivityData sensitivities_for(qois, system, system.get_parameter_vector()); //use forward solve // Make sure we get the contributions to the adjoint RHS from the sides system.assemble_qoi_sides = true; //does nothing anyways... // We are about to solve the adjoint system, but before we do this we see the same preconditioner // flag to reuse the preconditioner from the forward solver linear_solver->reuse_preconditioner(param.reuse_preconditioner); // Here we solve the adjoint problem inside the adjoint_qoi_parameter_sensitivity // function, so we have to set the adjoint_already_solved boolean to false system.set_adjoint_already_solved(false); // Compute the sensitivities system.adjoint_qoi_parameter_sensitivity(qois, system.get_parameter_vector(), sensitivities_adj); system.adjoint_qoi_parameter_sensitivity(qois, system.get_parameter_vector(), sensitivities_for); // Now that we have solved the adjoint, set the adjoint_already_solved boolean to true, //so we dont solve unneccesarily in the error estimator system.set_adjoint_already_solved(true); std::cout << "(Adjoint) Sensitivity of QoI to rho is " << sensitivities_adj[0][0] << std::endl; std::cout << "(Forward) Sensitivity of QoI to rho is " << sensitivities_for[0][0] << std::endl; // Get a pointer to the solution vector of the adjoint problem for QoI 0 NumericVector<Number> &dual_solution = system.get_adjoint_solution(0); // Swap the (pointers to) primal and dual solutions so we can write out the adjoint solution primal_solution.swap(dual_solution); write_output(equation_systems, a_step, "adjoint"); // Swap back primal_solution.swap(dual_solution); std::cout << "Adaptive step " << a_step << ", we have " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl ; // Postprocess, compute the approximate QoIs and write them out to the console std::cout << "Postprocessing: " << std::endl; system.postprocess_sides = false; //QoI doesn't involve edges system.postprocess(); Number QoI_computed = system.get_QoI_value("computed", 0); std::cout<< "Computed QoI is " << std::setprecision(17) << QoI_computed << std::endl; // Now we construct the data structures for the mesh refinement process ErrorVector error; // Build an error estimator object AutoPtr<ErrorEstimator> error_estimator = build_error_estimator(param, qois); // Estimate the error in each element using the Adjoint Residual or Kelly error estimator error_estimator->estimate_error(system, error); // We have to refine either based on reaching an error tolerance or // a number of elements target, which should be verified above // Otherwise we flag elements by error tolerance or nelem target // Uniform refinement if(param.refine_uniformly) { mesh_refinement->uniformly_refine(1); } // Adaptively refine based on reaching an error tolerance else if(param.global_tolerance >= 0. && param.nelem_target == 0.) { mesh_refinement->flag_elements_by_error_tolerance (error); mesh_refinement->refine_and_coarsen_elements(); } // Adaptively refine based on reaching a target number of elements else { if (mesh.n_active_elem() >= param.nelem_target) { std::cout<<"We reached the target number of elements."<<std::endl <<std::endl; break; } mesh_refinement->flag_elements_by_nelem_target (error); mesh_refinement->refine_and_coarsen_elements(); } // Dont forget to reinit the system after each adaptive refinement ! equation_systems.reinit(); std::cout << "Refined mesh to " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl; } // Do one last solve if necessary if (a_step == param.max_adaptivesteps) { linear_solver->reuse_preconditioner(false); system.solve(); write_output(equation_systems, a_step, "primal"); NumericVector<Number> &primal_solution = *system.solution; QoISet qois; std::vector<unsigned int> qoi_indices; qoi_indices.push_back(0); qois.add_indices(qoi_indices); qois.set_weight(0, 1.0); SensitivityData sensitivities_adj(qois, system, system.get_parameter_vector()); SensitivityData sensitivities_for(qois, system, system.get_parameter_vector()); system.assemble_qoi_sides = false; //QoI doesn't involve sides linear_solver->reuse_preconditioner(param.reuse_preconditioner); system.set_adjoint_already_solved(false); system.adjoint_qoi_parameter_sensitivity(qois, system.get_parameter_vector(), sensitivities_adj); system.forward_qoi_parameter_sensitivity(qois, system.get_parameter_vector(), sensitivities_for); // Now that we have solved the adjoint, set the adjoint_already_solved boolean to true, // so we dont solve unneccesarily in the error estimator system.set_adjoint_already_solved(true); std::cout << "(Adjoint) Sensitivity of QoI to rho is " << sensitivities_adj[0][0] << std::endl; std::cout << "(Forward) Sensitivity of QoI to rho is " << sensitivities_for[0][0] << std::endl; NumericVector<Number> &dual_solution = system.get_adjoint_solution(0); primal_solution.swap(dual_solution); write_output(equation_systems, a_step, "adjoint"); primal_solution.swap(dual_solution); std::cout << "Adaptive step " << a_step << ", we have " << mesh.n_active_elem() << " active elements and " << equation_systems.n_active_dofs() << " active dofs." << std::endl ; std::cout << "Postprocessing: " << std::endl; system.postprocess_sides = false; //nothing special on sides system.postprocess(); Number QoI_computed = system.get_QoI_value("computed", 0); std::cout<< "Computed QoI is " << std::setprecision(17) << QoI_computed << std::endl; } } std::cerr << '[' << mesh.processor_id() << "] Completing output." << std::endl; #endif // #ifndef LIBMESH_ENABLE_AMR // All done. return 0; }
void ImplicitSystem::qoi_parameter_hessian_vector_product (const QoISet & qoi_indices, const ParameterVector & parameters_in, const ParameterVector & vector, SensitivityData & sensitivities) { // We currently get partial derivatives via finite differencing const Real delta_p = TOLERANCE; ParameterVector & parameters = const_cast<ParameterVector &>(parameters_in); // We'll use a single temporary vector for matrix-vector-vector products std::unique_ptr<NumericVector<Number>> tempvec = this->solution->zero_clone(); const unsigned int Np = cast_int<unsigned int> (parameters.size()); const unsigned int Nq = this->n_qois(); // For each quantity of interest q, the parameter sensitivity // Hessian is defined as q''_{kl} = {d^2 q}/{d p_k d p_l}. // Given a vector of parameter perturbation weights w_l, this // function evaluates the hessian-vector product sum_l(q''_{kl}*w_l) // // We calculate it from values and partial derivatives of the // quantity of interest function Q, solution u, adjoint solution z, // parameter sensitivity adjoint solutions z^l, and residual R, as: // // sum_l(q''_{kl}*w_l) = // sum_l(w_l * Q''_{kl}) + Q''_{uk}(u)*(sum_l(w_l u'_l)) - // R'_k(u, sum_l(w_l*z^l)) - R'_{uk}(u,z)*(sum_l(w_l u'_l) - // sum_l(w_l*R''_{kl}(u,z)) // // See the adjoints model document for more details. // We first do an adjoint solve to get z for each quantity of // interest // if we havent already or dont have an initial condition for the adjoint if (!this->is_adjoint_already_solved()) { this->adjoint_solve(qoi_indices); } // Get ready to fill in sensitivities: sensitivities.allocate_data(qoi_indices, *this, parameters); // We can't solve for all the solution sensitivities u'_l or for all // of the parameter sensitivity adjoint solutions z^l without // requiring O(Nq*Np) linear solves. So we'll solve directly for their // weighted sum - this is just O(Nq) solves. // First solve for sum_l(w_l u'_l). this->weighted_sensitivity_solve(parameters, vector); // Then solve for sum_l(w_l z^l). this->weighted_sensitivity_adjoint_solve(parameters, vector, qoi_indices); for (unsigned int k=0; k != Np; ++k) { // We approximate sum_l(w_l * Q''_{kl}) with a central // differencing perturbation: // sum_l(w_l * Q''_{kl}) ~= // (Q(p + dp*w_l*e_l + dp*e_k) - Q(p - dp*w_l*e_l + dp*e_k) - // Q(p + dp*w_l*e_l - dp*e_k) + Q(p - dp*w_l*e_l - dp*e_k))/(4*dp^2) // The sum(w_l*R''_kl) term requires the same sort of perturbation, // and so we subtract it in at the same time: // sum_l(w_l * R''_{kl}) ~= // (R(p + dp*w_l*e_l + dp*e_k) - R(p - dp*w_l*e_l + dp*e_k) - // R(p + dp*w_l*e_l - dp*e_k) + R(p - dp*w_l*e_l - dp*e_k))/(4*dp^2) ParameterVector oldparameters, parameterperturbation; parameters.deep_copy(oldparameters); vector.deep_copy(parameterperturbation); parameterperturbation *= delta_p; parameters += parameterperturbation; Number old_parameter = *parameters[k]; *parameters[k] = old_parameter + delta_p; this->assemble_qoi(qoi_indices); this->assembly(true, false, true); this->rhs->close(); std::vector<Number> partial2q_term = this->qoi; std::vector<Number> partial2R_term(this->n_qois()); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) partial2R_term[i] = this->rhs->dot(this->get_adjoint_solution(i)); *parameters[k] = old_parameter - delta_p; this->assemble_qoi(qoi_indices); this->assembly(true, false, true); this->rhs->close(); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { partial2q_term[i] -= this->qoi[i]; partial2R_term[i] -= this->rhs->dot(this->get_adjoint_solution(i)); } oldparameters.value_copy(parameters); parameterperturbation *= -1.0; parameters += parameterperturbation; // Re-center old_parameter, which may be affected by vector old_parameter = *parameters[k]; *parameters[k] = old_parameter + delta_p; this->assemble_qoi(qoi_indices); this->assembly(true, false, true); this->rhs->close(); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { partial2q_term[i] -= this->qoi[i]; partial2R_term[i] -= this->rhs->dot(this->get_adjoint_solution(i)); } *parameters[k] = old_parameter - delta_p; this->assemble_qoi(qoi_indices); this->assembly(true, false, true); this->rhs->close(); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { partial2q_term[i] += this->qoi[i]; partial2R_term[i] += this->rhs->dot(this->get_adjoint_solution(i)); } for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { partial2q_term[i] /= (4. * delta_p * delta_p); partial2R_term[i] /= (4. * delta_p * delta_p); } for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) sensitivities[i][k] = partial2q_term[i] - partial2R_term[i]; // We get (partial q / partial u), R, and // (partial R / partial u) from the user, but centrally // difference to get q_uk, R_k, and R_uk terms: // (partial R / partial k) // R_k*sum(w_l*z^l) = (R(p+dp*e_k)*sum(w_l*z^l) - R(p-dp*e_k)*sum(w_l*z^l))/(2*dp) // (partial^2 q / partial u partial k) // q_uk = (q_u(p+dp*e_k) - q_u(p-dp*e_k))/(2*dp) // (partial^2 R / partial u partial k) // R_uk*z*sum(w_l*u'_l) = (R_u(p+dp*e_k)*z*sum(w_l*u'_l) - R_u(p-dp*e_k)*z*sum(w_l*u'_l))/(2*dp) // To avoid creating Nq temporary vectors for q_uk or R_uk, we add // subterms to the sensitivities output one by one. // // FIXME: this is probably a bad order of operations for // controlling floating point error. *parameters[k] = old_parameter + delta_p; this->assembly(true, true); this->rhs->close(); this->matrix->close(); this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ true, /* apply_constraints = */ false); this->matrix->vector_mult(*tempvec, this->get_weighted_sensitivity_solution()); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { this->get_adjoint_rhs(i).close(); sensitivities[i][k] += (this->get_adjoint_rhs(i).dot(this->get_weighted_sensitivity_solution()) - this->rhs->dot(this->get_weighted_sensitivity_adjoint_solution(i)) - this->get_adjoint_solution(i).dot(*tempvec)) / (2.*delta_p); } *parameters[k] = old_parameter - delta_p; this->assembly(true, true); this->rhs->close(); this->matrix->close(); this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ true, /* apply_constraints = */ false); this->matrix->vector_mult(*tempvec, this->get_weighted_sensitivity_solution()); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { this->get_adjoint_rhs(i).close(); sensitivities[i][k] += (-this->get_adjoint_rhs(i).dot(this->get_weighted_sensitivity_solution()) + this->rhs->dot(this->get_weighted_sensitivity_adjoint_solution(i)) + this->get_adjoint_solution(i).dot(*tempvec)) / (2.*delta_p); } } // All parameters have been reset. // Don't leave the qoi or system changed - principle of least // surprise. this->assembly(true, true); this->rhs->close(); this->matrix->close(); this->assemble_qoi(qoi_indices); }
void ImplicitSystem::forward_qoi_parameter_sensitivity (const QoISet & qoi_indices, const ParameterVector & parameters_in, SensitivityData & sensitivities) { ParameterVector & parameters = const_cast<ParameterVector &>(parameters_in); const unsigned int Np = cast_int<unsigned int> (parameters.size()); const unsigned int Nq = this->n_qois(); // An introduction to the problem: // // Residual R(u(p),p) = 0 // partial R / partial u = J = system matrix // // This implies that: // d/dp(R) = 0 // (partial R / partial p) + // (partial R / partial u) * (partial u / partial p) = 0 // We first solve for (partial u / partial p) for each parameter: // J * (partial u / partial p) = - (partial R / partial p) this->sensitivity_solve(parameters); // Get ready to fill in sensitivities: sensitivities.allocate_data(qoi_indices, *this, parameters); // We use the identity: // dq/dp = (partial q / partial p) + (partial q / partial u) * // (partial u / partial p) // We get (partial q / partial u) from the user this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ true, /* apply_constraints = */ false); // We don't need these to be closed() in this function, but libMesh // standard practice is to have them closed() by the time the // function exits for (unsigned int i=0; i != this->n_qois(); ++i) if (qoi_indices.has_index(i)) this->get_adjoint_rhs(i).close(); for (unsigned int j=0; j != Np; ++j) { // We currently get partial derivatives via central differencing // (partial q / partial p) ~= (q(p+dp)-q(p-dp))/(2*dp) Number old_parameter = *parameters[j]; const Real delta_p = TOLERANCE * std::max(std::abs(old_parameter), 1e-3); *parameters[j] = old_parameter - delta_p; this->assemble_qoi(qoi_indices); std::vector<Number> qoi_minus = this->qoi; *parameters[j] = old_parameter + delta_p; this->assemble_qoi(qoi_indices); std::vector<Number> & qoi_plus = this->qoi; std::vector<Number> partialq_partialp(Nq, 0); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) partialq_partialp[i] = (qoi_plus[i] - qoi_minus[i]) / (2.*delta_p); // Don't leave the parameter changed *parameters[j] = old_parameter; for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) sensitivities[i][j] = partialq_partialp[i] + this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(j)); } // All parameters have been reset. // We didn't cache the original rhs or matrix for memory reasons, // but we can restore them to a state consistent solution - // principle of least surprise. this->assembly(true, true); this->rhs->close(); this->matrix->close(); this->assemble_qoi(qoi_indices); }
void ImplicitSystem::adjoint_qoi_parameter_sensitivity (const QoISet & qoi_indices, const ParameterVector & parameters_in, SensitivityData & sensitivities) { ParameterVector & parameters = const_cast<ParameterVector &>(parameters_in); const unsigned int Np = cast_int<unsigned int> (parameters.size()); const unsigned int Nq = this->n_qois(); // An introduction to the problem: // // Residual R(u(p),p) = 0 // partial R / partial u = J = system matrix // // This implies that: // d/dp(R) = 0 // (partial R / partial p) + // (partial R / partial u) * (partial u / partial p) = 0 // We first do an adjoint solve: // J^T * z = (partial q / partial u) // if we havent already or dont have an initial condition for the adjoint if (!this->is_adjoint_already_solved()) { this->adjoint_solve(qoi_indices); } this->assemble_residual_derivatives(parameters_in); // Get ready to fill in sensitivities: sensitivities.allocate_data(qoi_indices, *this, parameters); // We use the identities: // dq/dp = (partial q / partial p) + (partial q / partial u) * // (partial u / partial p) // dq/dp = (partial q / partial p) + (J^T * z) * // (partial u / partial p) // dq/dp = (partial q / partial p) + z * J * // (partial u / partial p) // Leading to our final formula: // dq/dp = (partial q / partial p) - z * (partial R / partial p) // In the case of adjoints with heterogenous Dirichlet boundary // function phi, where // q := S(u) - R(u,phi) // the final formula works out to: // dq/dp = (partial S / partial p) - z * (partial R / partial p) // Because we currently have no direct access to // (partial S / partial p), we use the identity // (partial S / partial p) = (partial q / partial p) + // phi * (partial R / partial p) // to derive an equivalent equation: // dq/dp = (partial q / partial p) - (z-phi) * (partial R / partial p) // Since z-phi degrees of freedom are zero for constrained indices, // we can use the same constrained -(partial R / partial p) that we // use for forward sensitivity solves, taking into account the // differing sign convention. // // Since that vector is constrained, its constrained indices are // zero, so its product with phi is zero, so we can neglect the // evaluation of phi terms. for (unsigned int j=0; j != Np; ++j) { // We currently get partial derivatives via central differencing // (partial q / partial p) ~= (q(p+dp)-q(p-dp))/(2*dp) // (partial R / partial p) ~= (rhs(p+dp) - rhs(p-dp))/(2*dp) Number old_parameter = *parameters[j]; const Real delta_p = TOLERANCE * std::max(std::abs(old_parameter), 1e-3); *parameters[j] = old_parameter - delta_p; this->assemble_qoi(qoi_indices); std::vector<Number> qoi_minus = this->qoi; NumericVector<Number> & neg_partialR_partialp = this->get_sensitivity_rhs(j); *parameters[j] = old_parameter + delta_p; this->assemble_qoi(qoi_indices); std::vector<Number> & qoi_plus = this->qoi; std::vector<Number> partialq_partialp(Nq, 0); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) partialq_partialp[i] = (qoi_plus[i] - qoi_minus[i]) / (2.*delta_p); // Don't leave the parameter changed *parameters[j] = old_parameter; for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) sensitivities[i][j] = partialq_partialp[i] + neg_partialR_partialp.dot(this->get_adjoint_solution(i)); } // All parameters have been reset. // Reset the original qoi. this->assemble_qoi(qoi_indices); }
std::pair<unsigned int, Real> ImplicitSystem::weighted_sensitivity_adjoint_solve (const ParameterVector & parameters_in, const ParameterVector & weights, const QoISet & qoi_indices) { // Log how long the linear solve takes. LOG_SCOPE("weighted_sensitivity_adjoint_solve()", "ImplicitSystem"); // We currently get partial derivatives via central differencing const Real delta_p = TOLERANCE; ParameterVector & parameters = const_cast<ParameterVector &>(parameters_in); // The forward system should now already be solved. // The adjoint system should now already be solved. // Now we're assembling a weighted sum of adjoint-adjoint systems: // // dR/du (u, sum_l(w_l*z^l)) = sum_l(w_l*(Q''_ul - R''_ul (u, z))) // FIXME: The derivation here does not yet take adjoint boundary // conditions into account. for (unsigned int i=0; i != this->n_qois(); ++i) if (qoi_indices.has_index(i)) libmesh_assert(!this->get_dof_map().has_adjoint_dirichlet_boundaries(i)); // We'll assemble the rhs first, because the R'' term will require // perturbing the jacobian // We'll use temporary rhs vectors, because we haven't (yet) found // any good reasons why users might want to save these: std::vector<std::unique_ptr<NumericVector<Number>>> temprhs(this->n_qois()); for (unsigned int i=0; i != this->n_qois(); ++i) if (qoi_indices.has_index(i)) temprhs[i] = this->rhs->zero_clone(); // We approximate the _l partial derivatives via a central // differencing perturbation in the w_l direction: // // sum_l(w_l*v_l) ~= (v(p + dp*w_l*e_l) - v(p - dp*w_l*e_l))/(2*dp) // PETSc doesn't implement SGEMX, so neither does NumericVector, // so we want to avoid calculating f -= R'*z. We'll thus evaluate // the above equation by first adding -v(p+dp...), then multiplying // the intermediate result vectors by -1, then adding -v(p-dp...), // then finally dividing by 2*dp. ParameterVector oldparameters, parameterperturbation; parameters.deep_copy(oldparameters); weights.deep_copy(parameterperturbation); parameterperturbation *= delta_p; parameters += parameterperturbation; this->assembly(false, true); this->matrix->close(); // Take the discrete adjoint, so that we can calculate R_u(u,z) with // a matrix-vector product of R_u and z. matrix->get_transpose(*matrix); this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ false, /* apply_constraints = */ true); for (unsigned int i=0; i != this->n_qois(); ++i) if (qoi_indices.has_index(i)) { this->get_adjoint_rhs(i).close(); *(temprhs[i]) -= this->get_adjoint_rhs(i); this->matrix->vector_mult_add(*(temprhs[i]), this->get_adjoint_solution(i)); *(temprhs[i]) *= -1.0; } oldparameters.value_copy(parameters); parameterperturbation *= -1.0; parameters += parameterperturbation; this->assembly(false, true); this->matrix->close(); matrix->get_transpose(*matrix); this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ false, /* apply_constraints = */ true); for (unsigned int i=0; i != this->n_qois(); ++i) if (qoi_indices.has_index(i)) { this->get_adjoint_rhs(i).close(); *(temprhs[i]) -= this->get_adjoint_rhs(i); this->matrix->vector_mult_add(*(temprhs[i]), this->get_adjoint_solution(i)); *(temprhs[i]) /= (2.0*delta_p); } // Finally, assemble the jacobian at the non-perturbed parameter // values. Ignore assemble_before_solve; if we had a good // non-perturbed matrix before we've already overwritten it. oldparameters.value_copy(parameters); // if (this->assemble_before_solve) { // Build the Jacobian this->assembly(false, true); this->matrix->close(); // Take the discrete adjoint matrix->get_transpose(*matrix); } // The weighted adjoint-adjoint problem is linear LinearSolver<Number> * linear_solver = this->get_linear_solver(); // Our iteration counts and residuals will be sums of the individual // results std::pair<unsigned int, Real> solver_params = this->get_linear_solve_parameters(); std::pair<unsigned int, Real> totalrval = std::make_pair(0,0.0); for (unsigned int i=0; i != this->n_qois(); ++i) if (qoi_indices.has_index(i)) { const std::pair<unsigned int, Real> rval = linear_solver->solve (*matrix, this->add_weighted_sensitivity_adjoint_solution(i), *(temprhs[i]), solver_params.second, solver_params.first); totalrval.first += rval.first; totalrval.second += rval.second; } this->release_linear_solver(linear_solver); // The linear solver may not have fit our constraints exactly #ifdef LIBMESH_ENABLE_CONSTRAINTS for (unsigned int i=0; i != this->n_qois(); ++i) if (qoi_indices.has_index(i)) this->get_dof_map().enforce_constraints_exactly (*this, &this->get_weighted_sensitivity_adjoint_solution(i), /* homogeneous = */ true); #endif return totalrval; }
void ImplicitSystem::qoi_parameter_hessian (const QoISet & qoi_indices, const ParameterVector & parameters_in, SensitivityData & sensitivities) { // We currently get partial derivatives via finite differencing const Real delta_p = TOLERANCE; ParameterVector & parameters = const_cast<ParameterVector &>(parameters_in); // We'll use one temporary vector for matrix-vector-vector products std::unique_ptr<NumericVector<Number>> tempvec = this->solution->zero_clone(); // And another temporary vector to hold a copy of the true solution // so we can safely perturb this->solution. std::unique_ptr<NumericVector<Number>> oldsolution = this->solution->clone(); const unsigned int Np = cast_int<unsigned int> (parameters.size()); const unsigned int Nq = this->n_qois(); // For each quantity of interest q, the parameter sensitivity // Hessian is defined as q''_{kl} = {d^2 q}/{d p_k d p_l}. // // We calculate it from values and partial derivatives of the // quantity of interest function Q, solution u, adjoint solution z, // and residual R, as: // // q''_{kl} = // Q''_{kl} + Q''_{uk}(u)*u'_l + Q''_{ul}(u) * u'_k + // Q''_{uu}(u)*u'_k*u'_l - // R''_{kl}(u,z) - // R''_{uk}(u,z)*u'_l - R''_{ul}(u,z)*u'_k - // R''_{uu}(u,z)*u'_k*u'_l // // See the adjoints model document for more details. // We first do an adjoint solve to get z for each quantity of // interest // if we havent already or dont have an initial condition for the adjoint if (!this->is_adjoint_already_solved()) { this->adjoint_solve(qoi_indices); } // And a sensitivity solve to get u_k for each parameter this->sensitivity_solve(parameters); // Get ready to fill in second derivatives: sensitivities.allocate_hessian_data(qoi_indices, *this, parameters); for (unsigned int k=0; k != Np; ++k) { Number old_parameterk = *parameters[k]; // The Hessian is symmetric, so we just calculate the lower // triangle and the diagonal, and we get the upper triangle from // the transpose of the lower for (unsigned int l=0; l != k+1; ++l) { // The second partial derivatives with respect to parameters // are all calculated via a central finite difference // stencil: // F''_{kl} ~= (F(p+dp*e_k+dp*e_l) - F(p+dp*e_k-dp*e_l) - // F(p-dp*e_k+dp*e_l) + F(p-dp*e_k-dp*e_l))/(4*dp^2) // We will add Q''_{kl}(u) and subtract R''_{kl}(u,z) at the // same time. // // We have to be careful with the perturbations to handle // the k=l case Number old_parameterl = *parameters[l]; *parameters[k] += delta_p; *parameters[l] += delta_p; this->assemble_qoi(qoi_indices); this->assembly(true, false, true); this->rhs->close(); std::vector<Number> partial2q_term = this->qoi; std::vector<Number> partial2R_term(this->n_qois()); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) partial2R_term[i] = this->rhs->dot(this->get_adjoint_solution(i)); *parameters[l] -= 2.*delta_p; this->assemble_qoi(qoi_indices); this->assembly(true, false, true); this->rhs->close(); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { partial2q_term[i] -= this->qoi[i]; partial2R_term[i] -= this->rhs->dot(this->get_adjoint_solution(i)); } *parameters[k] -= 2.*delta_p; this->assemble_qoi(qoi_indices); this->assembly(true, false, true); this->rhs->close(); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { partial2q_term[i] += this->qoi[i]; partial2R_term[i] += this->rhs->dot(this->get_adjoint_solution(i)); } *parameters[l] += 2.*delta_p; this->assemble_qoi(qoi_indices); this->assembly(true, false, true); this->rhs->close(); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { partial2q_term[i] -= this->qoi[i]; partial2R_term[i] -= this->rhs->dot(this->get_adjoint_solution(i)); partial2q_term[i] /= (4. * delta_p * delta_p); partial2R_term[i] /= (4. * delta_p * delta_p); } for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { Number current_terms = partial2q_term[i] - partial2R_term[i]; sensitivities.second_derivative(i,k,l) += current_terms; if (k != l) sensitivities.second_derivative(i,l,k) += current_terms; } // Don't leave the parameters perturbed *parameters[l] = old_parameterl; *parameters[k] = old_parameterk; } // We get (partial q / partial u) and // (partial R / partial u) from the user, but centrally // difference to get q_uk and R_uk terms: // (partial^2 q / partial u partial k) // q_uk*u'_l = (q_u(p+dp*e_k)*u'_l - q_u(p-dp*e_k)*u'_l)/(2*dp) // R_uk*z*u'_l = (R_u(p+dp*e_k)*z*u'_l - R_u(p-dp*e_k)*z*u'_l)/(2*dp) // // To avoid creating Nq temporary vectors, we add these // subterms to the sensitivities output one by one. // // FIXME: this is probably a bad order of operations for // controlling floating point error. *parameters[k] = old_parameterk + delta_p; this->assembly(false, true); this->matrix->close(); this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ true, /* apply_constraints = */ false); for (unsigned int l=0; l != Np; ++l) { this->matrix->vector_mult(*tempvec, this->get_sensitivity_solution(l)); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { this->get_adjoint_rhs(i).close(); Number current_terms = (this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(l)) - tempvec->dot(this->get_adjoint_solution(i))) / (2.*delta_p); sensitivities.second_derivative(i,k,l) += current_terms; // We use the _uk terms twice; symmetry lets us reuse // these calculations for the _ul terms. sensitivities.second_derivative(i,l,k) += current_terms; } } *parameters[k] = old_parameterk - delta_p; this->assembly(false, true); this->matrix->close(); this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ true, /* apply_constraints = */ false); for (unsigned int l=0; l != Np; ++l) { this->matrix->vector_mult(*tempvec, this->get_sensitivity_solution(l)); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { this->get_adjoint_rhs(i).close(); Number current_terms = (-this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(l)) + tempvec->dot(this->get_adjoint_solution(i))) / (2.*delta_p); sensitivities.second_derivative(i,k,l) += current_terms; // We use the _uk terms twice; symmetry lets us reuse // these calculations for the _ul terms. sensitivities.second_derivative(i,l,k) += current_terms; } } // Don't leave the parameter perturbed *parameters[k] = old_parameterk; // Our last remaining terms are -R_uu(u,z)*u_k*u_l and // Q_uu(u)*u_k*u_l // // We take directional central finite differences of R_u and Q_u // to approximate these terms, e.g.: // // Q_uu(u)*u_k ~= (Q_u(u+dp*u_k) - Q_u(u-dp*u_k))/(2*dp) *this->solution = this->get_sensitivity_solution(k); *this->solution *= delta_p; *this->solution += *oldsolution; // We've modified solution, so we need to update before calling // assembly since assembly may only use current_local_solution this->update(); this->assembly(false, true); this->matrix->close(); this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ true, /* apply_constraints = */ false); // The Hessian is symmetric, so we just calculate the lower // triangle and the diagonal, and we get the upper triangle from // the transpose of the lower // // Note that, because we took the directional finite difference // with respect to k and not l, we've added an O(delta_p^2) // error to any permutational symmetry in the Hessian... for (unsigned int l=0; l != k+1; ++l) { this->matrix->vector_mult(*tempvec, this->get_sensitivity_solution(l)); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { this->get_adjoint_rhs(i).close(); Number current_terms = (this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(l)) - tempvec->dot(this->get_adjoint_solution(i))) / (2.*delta_p); sensitivities.second_derivative(i,k,l) += current_terms; if (k != l) sensitivities.second_derivative(i,l,k) += current_terms; } } *this->solution = this->get_sensitivity_solution(k); *this->solution *= -delta_p; *this->solution += *oldsolution; // We've modified solution, so we need to update before calling // assembly since assembly may only use current_local_solution this->update(); this->assembly(false, true); this->matrix->close(); this->assemble_qoi_derivative(qoi_indices, /* include_liftfunc = */ true, /* apply_constraints = */ false); for (unsigned int l=0; l != k+1; ++l) { this->matrix->vector_mult(*tempvec, this->get_sensitivity_solution(l)); for (unsigned int i=0; i != Nq; ++i) if (qoi_indices.has_index(i)) { this->get_adjoint_rhs(i).close(); Number current_terms = (-this->get_adjoint_rhs(i).dot(this->get_sensitivity_solution(l)) + tempvec->dot(this->get_adjoint_solution(i))) / (2.*delta_p); sensitivities.second_derivative(i,k,l) += current_terms; if (k != l) sensitivities.second_derivative(i,l,k) += current_terms; } } // Don't leave the solution perturbed *this->solution = *oldsolution; } // All parameters have been reset. // Don't leave the qoi or system changed - principle of least // surprise. // We've modified solution, so we need to update before calling // assembly since assembly may only use current_local_solution this->update(); this->assembly(true, true); this->rhs->close(); this->matrix->close(); this->assemble_qoi(qoi_indices); }