void InitialConditionTempl<T>::compute() { // -- NOTE ---- // The following code is a copy from libMesh project_vector.C plus it adds some features, so we // can couple variable values // and we also do not call any callbacks, but we use our initial condition system directly. // ------------ // The dimension of the current element _dim = _current_elem->dim(); // The element type const ElemType elem_type = _current_elem->type(); // The number of nodes on the new element const unsigned int n_nodes = _current_elem->n_nodes(); // Get FE objects of the appropriate type // We cannot use the FE object in Assembly, since the following code is messing with the // quadrature rules // for projections and would screw it up. However, if we implement projections from one mesh to // another, // this code should use that implementation. std::unique_ptr<FEBaseType> fe(FEBaseType::build(_dim, _fe_type)); // Prepare variables for projection std::unique_ptr<QBase> qrule(_fe_type.default_quadrature_rule(_dim)); std::unique_ptr<QBase> qedgerule(_fe_type.default_quadrature_rule(1)); std::unique_ptr<QBase> qsiderule(_fe_type.default_quadrature_rule(_dim - 1)); // The values of the shape functions at the quadrature points _phi = &fe->get_phi(); // The gradients of the shape functions at the quadrature points on the child element. _dphi = nullptr; _cont = fe->get_continuity(); if (_cont == C_ONE) { const std::vector<std::vector<GradientType>> & ref_dphi = fe->get_dphi(); _dphi = &ref_dphi; } // The Jacobian * quadrature weight at the quadrature points _JxW = &fe->get_JxW(); // The XYZ locations of the quadrature points _xyz_values = &fe->get_xyz(); // Update the DOF indices for this element based on the current mesh _var.prepareIC(); _dof_indices = _var.dofIndices(); // The number of DOFs on the element const unsigned int n_dofs = _dof_indices.size(); if (n_dofs == 0) return; // Fixed vs. free DoFs on edge/face projections _dof_is_fixed.clear(); _dof_is_fixed.resize(n_dofs, false); _free_dof.clear(); _free_dof.resize(n_dofs, 0); // Zero the interpolated values _Ue.resize(n_dofs); _Ue.zero(); // In general, we need a series of // projections to ensure a unique and continuous // solution. We start by interpolating nodes, then // hold those fixed and project edges, then // hold those fixed and project faces, then // hold those fixed and project interiors // Interpolate node values first _current_dof = 0; for (_n = 0; _n != n_nodes; ++_n) { // FIXME: this should go through the DofMap, // not duplicate _dof_indices code badly! _nc = FEInterface::n_dofs_at_node(_dim, _fe_type, elem_type, _n); if (!_current_elem->is_vertex(_n)) { _current_dof += _nc; continue; } if (_cont == DISCONTINUOUS) libmesh_assert(_nc == 0); else if (_cont == C_ZERO) setCZeroVertices(); else if (_fe_type.family == HERMITE) setHermiteVertices(); else if (_cont == C_ONE) setOtherCOneVertices(); else libmesh_error(); } // loop over nodes // From here on out we won't be sampling at nodes anymore _current_node = nullptr; // In 3D, project any edge values next if (_dim > 2 && _cont != DISCONTINUOUS) for (unsigned int e = 0; e != _current_elem->n_edges(); ++e) { FEInterface::dofs_on_edge(_current_elem, _dim, _fe_type, e, _side_dofs); // Some edge dofs are on nodes and already // fixed, others are free to calculate _free_dofs = 0; for (unsigned int i = 0; i != _side_dofs.size(); ++i) if (!_dof_is_fixed[_side_dofs[i]]) _free_dof[_free_dofs++] = i; // There may be nothing to project if (!_free_dofs) continue; // Initialize FE data on the edge fe->attach_quadrature_rule(qedgerule.get()); fe->edge_reinit(_current_elem, e); _n_qp = qedgerule->n_points(); choleskySolve(false); } // Project any side values (edges in 2D, faces in 3D) if (_dim > 1 && _cont != DISCONTINUOUS) for (unsigned int s = 0; s != _current_elem->n_sides(); ++s) { FEInterface::dofs_on_side(_current_elem, _dim, _fe_type, s, _side_dofs); // Some side dofs are on nodes/edges and already // fixed, others are free to calculate _free_dofs = 0; for (unsigned int i = 0; i != _side_dofs.size(); ++i) if (!_dof_is_fixed[_side_dofs[i]]) _free_dof[_free_dofs++] = i; // There may be nothing to project if (!_free_dofs) continue; // Initialize FE data on the side fe->attach_quadrature_rule(qsiderule.get()); fe->reinit(_current_elem, s); _n_qp = qsiderule->n_points(); choleskySolve(false); } // Project the interior values, finally // Some interior dofs are on nodes/edges/sides and // already fixed, others are free to calculate _free_dofs = 0; for (unsigned int i = 0; i != n_dofs; ++i) if (!_dof_is_fixed[i]) _free_dof[_free_dofs++] = i; // There may be nothing to project if (_free_dofs) { // Initialize FE data fe->attach_quadrature_rule(qrule.get()); fe->reinit(_current_elem); _n_qp = qrule->n_points(); choleskySolve(true); } // if there are free interior dofs // Make sure every DoF got reached! for (unsigned int i = 0; i != n_dofs; ++i) libmesh_assert(_dof_is_fixed[i]); NumericVector<Number> & solution = _var.sys().solution(); // 'first' and 'last' are no longer used, see note about subdomain-restricted variables below // const dof_id_type // first = solution.first_local_index(), // last = solution.last_local_index(); // Lock the new_vector since it is shared among threads. { Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx); for (unsigned int i = 0; i < n_dofs; i++) // We may be projecting a new zero value onto // an old nonzero approximation - RHS // if (_Ue(i) != 0.) // This is commented out because of subdomain restricted variables. // It can be the case that if a subdomain restricted variable's boundary // aligns perfectly with a processor boundary that the variable will get // no value. To counteract this we're going to let every processor set a // value at every node and then let PETSc figure it out. // Later we can choose to do something different / better. // if ((_dof_indices[i] >= first) && (_dof_indices[i] < last)) { solution.set(_dof_indices[i], _Ue(i)); } _var.setDofValues(_Ue); } }
/* ******************************************************************************** */ int getSearchDir(double *p, double *Grad, double **Hes, double delta, int numSS) { /* Computes the search direction using the dogleg method (Nocedal and Wright, page 71). Notation is consistent with that in this reference. Due to the construction of the problem, the minimization routine to find tau can be solved exactly by solving a quadratic. There can be precision issues with this, so this is checked. If the argument of the square root in the quadratic formula is negative, there must be a precision error, as such a situation is not possible in the construction of the problem. Returns: 1 if step was a pure Newton step (didn't hit trust region boundary) 2 if the step was purely Cauchy in nature (hit trust region boundary) 3 if the step was a dogleg step (part Newton and part Cauchy) 4 if Cholesky decomposition failed and we had to take a Cauchy step 5 if Cholesky decompostion failed but we would've taken Cauchy step anyways 6 if the dogleg calculation failed (should never happen) */ int i,j; // counters double a,b,c,sgnb; // Constants used in quadratic formula double q,x1,x2; // results from quadratic formula double tau; // Multipler in Newtonstep double *pB; // Unconstrained minimizer (the regular Newton step) double *pU; // Minimizer along steepest descent direction double pB2; // pj^2 double pU2; // pu^2 double pBpU; // pj . pc double *CholDiag; // Diagonal from Cholesky decomposition double **HesCopy; // Copy of the upper triangle of the Hessian (don't want to mess // with actual Hessian). int CholSuccess; // Whether of not Cholesky decomposition is successful double *HGrad; // Hessian dotted with the gradient double pUcoeff; // The coefficient on the Cauchy step double delta2; // delta^2 double mag1; // temporary variables for vector magnitudes double mag2; // Initialize pB2 just so compiler doesn't give a warning when optimization is on pB2 = 0.0; delta2 = pow(delta,2); /* ********** Compute the Newton step ************** */ // Memory allocation for computation of Newton step pB = (double *) malloc(numSS * sizeof(double)); CholDiag = (double *) malloc(numSS * sizeof(double)); HesCopy = (double **) malloc(numSS * sizeof(double *)); for (j = 0; j < numSS; j++) { HesCopy[j] = (double *) malloc(numSS * sizeof(double)); } // Make a copy of the Hessian because the Cholesky decomposition messes // with its entries. We only have to copy the upper diagonal. for (j = 0; j < numSS; j++) { for (i = j; i < numSS; i++) { HesCopy[i][j] = Hes[i][j]; } } CholSuccess = choleskyDecomposition(HesCopy,numSS); if (CholSuccess == 1) { choleskySolve(HesCopy,numSS,Grad,pB); // Free memory from Cholesky computation free(CholDiag); for (i = 0; i < numSS; i++) { free(HesCopy[i]); } free(HesCopy); // Newton step is -H^{-1} Grad for (i = 0; i < numSS; i++) { pB[i] *= -1.0; } // If Newton is in trust region, take it pB2 = dot(pB,pB,numSS); if (pB2 <= delta2) { for (i = 0; i < numSS; i++) { p[i] = pB[i]; } free(pB); return 1; // Signifies we took a pure Newton step } } else { // Free memory from failed Newton step computation free(CholDiag); for (i = 0; i < numSS; i++) { free(HesCopy[i]); } free(HesCopy); } /* ************************************************* */ /* ********** Compute the Cauchy step ************** */ // Allocate necessary arrays HGrad = (double *) malloc(numSS * sizeof(double)); pU = (double *) malloc(numSS * sizeof(double)); // The direction of the Cauchy step for (i = 0; i < numSS; i++) { pU[i] = -Grad[i]; } // prefactor for the Cauchy step MatrixVectorMult(HGrad,Hes,Grad,numSS); // Should this be sqrt too? mag1 = dot(Grad,Grad,numSS); mag2 = dot(Grad,HGrad,numSS); pUcoeff = mag1 / mag2; for (i = 0; i < numSS; i++) { pU[i] = pUcoeff * pU[i]; } free(HGrad); // Don't need this any more pU2 = dot(pU,pU,numSS); if (pU2 >= delta2) { // In this case we just take the Cauchy step, 0 < tau <= 1 tau = sqrt(delta2/pU2); for (i = 0; i < numSS; i++) { p[i] = tau*pU[i]; } free(pU); free(pB); if (CholSuccess != 1) { return 5; // Signifies Cholesky failure, but doesn't matter, would take Cauchy } // regardless else { return 2; // Signifies that we just took the Cauchy step } } if (CholSuccess != 1) { // We failed computing Newton step and have to take Cauchy for (i = 0; i < numSS; i++) { p[i] = pU[i]; } free(pU); free(pB); return 4; // Signifies Cholesky failure and we just took the Cauchy step } /* ************************************************* */ /* ************ Take the dogleg step *************** */ pBpU = dot(pB,pU,numSS); // Need this for dogleg calculation // Constants for quadratic formula for solving ||pU + (alpha)(pB-pU)||^2 = delta2 a = pB2 + pU2 - 2.0*pBpU; b = 2*(pBpU - pU2); c = pU2 - delta2; sgnb = 1; if(b < 0) { sgnb = -1; } q = -0.5 * (b + sgnb * sqrt(b*b - 4*a*c)); x1 = q / a; x2 = c / q; // x2 should be the positive root, x1 should be the negative root. if(x2 >= 0 && x2 <= 1.0) { for(i = 0; i < numSS; i++) { p[i] = pU[i] + x2 * (pB[i] - pU[i]); } free(pU); free(pB); return 3; // Signifies we took a dogleg step } else if(x1 >= 0 && x1 <= 1.0) { for(i = 0; i < numSS; i++) { p[i] = pU[i] + x1 * (pB[i] - pU[i]); } free(pU); free(pB); return 3; } else { for (i = 0; i < numSS; i++) { p[i] = pU[i]; } free(pU); free(pB); return 6; // Signifies no root satisfies the dogleg step and we took a Cauchy step } }