MUQ  0.4.3
FlowEquation.h
Go to the documentation of this file.
1 #ifndef FLOWEQUATION_H_
2 #define FLOWEQUATION_H_
3 
9 
11 
14 #include <boost/property_tree/ptree.hpp>
15 
16 #include <vector>
17 
18 #include <Eigen/Core>
19 #include <Eigen/Sparse>
20 #include <Eigen/SparseCholesky>
21 
22 
23 /***
24 ## Class Definition
25 
26 This class solves the 1D elliptic PDE of the form
27  $$
28  -\frac{\partial}{\partial x}\cdot(K(x) \frac{\partial h}{\partial x}) = f(x).
29  $$
30  over $x\in[0,1]$ with boundary conditions $h(0)=0$ and
31  $\partial h/\partial x =0$ at $x=1$. This equation is a basic model of steady
32  state fluid flow in a porous media, where $h(x)$ is the hydraulic head, $K(x)$
33  is the hydraulic conductivity, and $f(x)$ is the recharge.
34 
35  This ModPiece uses linear finite elements on a uniform grid. There is a single input,
36  the conductivity $k(x)$, which is represented as piecewise constant within each
37  of the $N$ cells. There is a single output of this ModPiece: the head $h(x)$ at the
38  $N+1$ nodes in the discretization.
39 
40  INPUTS:
41 
42 
43 */
44 class FlowEquation : public muq::Modeling::ModPiece
45 {
46 public:
47 
52  FlowEquation(Eigen::VectorXd const& sourceTerm) : muq::Modeling::ModPiece({int(sourceTerm.size())},
53  {int(sourceTerm.size()+1)})
54  {
55  numCells = sourceTerm.size();
56  numNodes = sourceTerm.size()+1;
57 
58  xs = Eigen::VectorXd::LinSpaced(numNodes,0,1);
59  dx = xs(1)-xs(0);
60 
61  rhs = BuildRhs(sourceTerm);
62  };
63 
64 protected:
65 
74  void EvaluateImpl(muq::Modeling::ref_vector<Eigen::VectorXd> const& inputs) override
75  {
76  // Extract the conductivity vector from the inptus
77  auto& cond = inputs.at(0).get();
78 
79  // Build the stiffness matrix
80  auto K = BuildStiffness(cond);
81 
82  // Solve the sparse linear system and store the solution in the outputs vector
83  Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
84  solver.compute(K);
85 
86  outputs.resize(1);
87  outputs.at(0) = solver.solve(rhs);
88  };
89 
118  virtual void GradientImpl(unsigned int outWrt,
119  unsigned int inWrt,
121  Eigen::VectorXd const& sens) override
122  {
123  // Extract the conductivity vector from the inptus
124  auto& cond = inputs.at(0).get();
125 
126  // Build the stiffness matrix
127  auto A = BuildStiffness(cond);
128 
129  // Factor the stiffness matrix for forward and adjoint solves
130  Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
131  solver.compute(A);
132 
133  // Solve the forward problem
134  Eigen::VectorXd sol = solver.solve(rhs);
135 
136  // Solve the adjoint problem
137  Eigen::VectorXd adjRhs = BuildAdjointRHS(sens);
138  Eigen::VectorXd adjSol = solver.solve(adjRhs);
139 
140  // Compute the gradient from the adjoint solution
141  gradient = (1.0/dx)*(sol.tail(numCells)-sol.head(numCells)).array() * (adjSol.tail(numCells) - adjSol.head(numCells)).array();
142  }
143 
144 
145  /***
146  This function computes the application of the model Jacobian's matrix $J$
147  on a vector $v$. In addition to the model parameter $k$, this function also
148  requires the vector $v$.
149 
150  The gradient with respect to the conductivity field is computed by solving
151  the forward model to get $h(x)$ and then using the tangent linear approach
152  described above to obtain the Jacobian action.
153 
154  @param[in] outWrt For a model with multiple outputs, this would be the index
155  of the output list that corresponds to the sensitivity vector.
156  Since this ModPiece only has one output, the outWrt argument
157  is not used in the GradientImpl function.
158 
159  @param[in] inWrt Specifies the index of the input for which we want to compute
160  the gradient. For inWrt==0, then the Jacobian with respect
161  to the conductivity is used. Since this ModPiece only has one
162  input, 0 is the only valid value for inWrt.
163 
164  @param[in] inputs Just like the EvalauteImpl function, this is a list of vector-valued inputs.
165 
166  @param[in] vec A vector with the same size of inputs[0]. The Jacobian will be applied to this vector.
167 
168  @return This function returns nothing. It stores the result in the private
169  ModPiece::jacobianAction variable that is then returned by the `Jacobian` function.
170 
171  */
172  virtual void ApplyJacobianImpl(unsigned int outWrt,
173  unsigned int inWrt,
175  Eigen::VectorXd const& vec) override
176  {
177  // Extract the conductivity vector from the inptus
178  auto& cond = inputs.at(0).get();
179 
180  // Build the stiffness matrix
181  auto A = BuildStiffness(cond);
182 
183  // Factor the stiffness matrix for forward and tangent linear solves
184  Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
185  solver.compute(A);
186 
187  // Solve the forward system
188  Eigen::VectorXd sol = solver.solve(rhs);
189 
190  // Build the tangent linear rhs
191  Eigen::VectorXd incrRhs = Eigen::VectorXd::Zero(numNodes);
192 
193  Eigen::VectorXd dh_dx = (sol.tail(numCells)-sol.head(numCells))/dx; // Spatial derivative of solution
194 
195  incrRhs.head(numCells) += ( vec.array()*dh_dx.array() ).matrix();
196  incrRhs.tail(numCells) -= ( vec.array()*dh_dx.array() ).matrix();
197 
198  // Solve the tangent linear model for the jacobian action
199  jacobianAction = solver.solve(incrRhs);
200  }
201 
240  virtual void ApplyHessianImpl(unsigned int outWrt,
241  unsigned int inWrt1,
242  unsigned int inWrt2,
244  Eigen::VectorXd const& sens,
245  Eigen::VectorXd const& vec) override
246  {
247  // Extract the conductivity vector from the inptus
248  auto& cond = inputs.at(0).get();
249 
250  // Build the stiffness matrix
251  auto K = BuildStiffness(cond);
252 
253  // Factor the stiffness matrix for forward and adjoint solves
254  Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
255  solver.compute(K);
256 
257  // Solve the forward problem
258  Eigen::VectorXd sol = solver.solve(rhs);
259 
260  // If we're using the Hessian $\nabla_{kk} J$
261  if((inWrt1==0)&&(inWrt2==0)){
262 
263  // Solve the adjoint problem
264  Eigen::VectorXd adjRhs = BuildAdjointRHS(sens);
265  Eigen::VectorXd adjSol = solver.solve(adjRhs);
266 
267  // Solve the incremental forward problem
268  Eigen::VectorXd incrForRhs = BuildIncrementalRhs(sol, vec);
269  Eigen::VectorXd incrForSol = solver.solve(incrForRhs);
270 
271  // Solve the incremental adjoint problem
272  Eigen::VectorXd incrAdjRhs = BuildIncrementalRhs(adjSol, vec);
273  Eigen::VectorXd incrAdjSol = solver.solve(incrAdjRhs);
274 
275  // Construct the Hessian action
276  auto solDeriv = (sol.tail(numCells)-sol.head(numCells))/dx;
277  auto adjDeriv = (adjSol.tail(numCells)-adjSol.head(numCells))/dx;
278  auto incrForDeriv = (incrForSol.tail(numCells) - incrForSol.head(numCells))/dx;
279  auto incrAdjDeriv = (incrAdjSol.tail(numCells) - incrAdjSol.head(numCells))/dx;
280 
281  hessAction = -(incrAdjDeriv.array() * solDeriv.array() + incrForDeriv.array() * adjDeriv.array());
282 
283  // If we're using the mixed Hessian $\nabla_{ks} J$
284  }else if((inWrt1==0)&&(inWrt2==1)){
285 
286  Eigen::VectorXd temp = solver.solve(vec);
287  auto solDeriv = (sol.tail(numCells) - sol.head(numCells))/dx;
288  auto tempDeriv = (temp.tail(numCells)-temp.head(numCells))/dx;
289 
290  hessAction = -dx * solDeriv.array() * tempDeriv.array();
291 
292  // We should never see any other options...
293  }else{
294  assert(false);
295  }
296  }
297 
299  Eigen::VectorXd BuildRhs(Eigen::VectorXd const& sourceTerm) const
300  {
301  Eigen::VectorXd rhs = Eigen::VectorXd::Zero(numNodes);
302 
303  rhs.segment(1,numNodes-2) = 0.5*dx*(sourceTerm.tail(numNodes-2) + sourceTerm.head(numNodes-2));
304  rhs(numNodes-1) = 0.5*dx*sourceTerm(numNodes-2);
305 
306  return rhs;
307  }
308 
310  Eigen::VectorXd BuildAdjointRHS(Eigen::VectorXd const& sensitivity) const
311  {
312  Eigen::VectorXd rhs = -1.0*sensitivity;
313  rhs(0) = 0.0; // <- To enforce Dirichlet BC
314  return rhs;
315  }
316 
318  Eigen::VectorXd BuildIncrementalRhs(Eigen::VectorXd const& sol, Eigen::VectorXd const& khat)
319  {
320  // Compute the derivative of the solution in each cell
321  Eigen::VectorXd solGrad = (sol.tail(numCells)-sol.head(numCells))/dx;
322  Eigen::VectorXd rhs = Eigen::VectorXd::Zero(numNodes);
323 
324  unsigned int leftNode, rightNode;
325  for(unsigned int cellInd=0; cellInd<numCells; ++cellInd)
326  {
327  leftNode = cellInd;
328  rightNode = cellInd + 1;
329 
330  rhs(leftNode) -= dx*khat(cellInd)*solGrad(cellInd);
331  rhs(rightNode) += dx*khat(cellInd)*solGrad(cellInd);
332  }
333 
334  rhs(0) = 0.0; // <- To enforce Dirichlet BC at x=0
335  return rhs;
336  }
337 
339  Eigen::SparseMatrix<double> BuildStiffness(Eigen::VectorXd const& condVals) const{
340 
341  typedef Eigen::Triplet<double> T;
342  std::vector<T> nzVals;
343 
344  // Add a large number to K[0,0] to enforce Dirichlet BC
345  nzVals.push_back( T(0,0,1e10) );
346 
347  unsigned int leftNode, rightNode;
348  for(unsigned int cellInd=0; cellInd<numCells; ++cellInd)
349  {
350  leftNode = cellInd;
351  rightNode = cellInd+1;
352 
353  nzVals.push_back( T(leftNode, rightNode, -condVals(cellInd)/dx) );
354  nzVals.push_back( T(rightNode, leftNode, -condVals(cellInd)/dx) );
355  nzVals.push_back( T(rightNode, rightNode, condVals(cellInd)/dx) );
356  nzVals.push_back( T(leftNode, leftNode, condVals(cellInd)/dx) );
357  }
358 
359  Eigen::SparseMatrix<double> stiffMat(numNodes,numNodes);
360  stiffMat.setFromTriplets(nzVals.begin(), nzVals.end());
361 
362  return stiffMat;
363  }
364 
365 
366 private:
367 
368  // Store "mesh" information. xs contains the node locations and dx is the uniform spacing between nodes
369  Eigen::VectorXd xs;
370  double dx;
371  unsigned int numCells;
372  unsigned int numNodes;
373 
374  // Will store the precomputed RHS for the forward problem
375  Eigen::VectorXd rhs;
376 
377 }; // end of class FlowEquation
378 
379 #endif
Provides an abstract interface for defining vector-valued model components.
Definition: ModPiece.h:148
virtual void GradientImpl(unsigned int const outputDimWrt, unsigned int const inputDimWrt, ref_vector< Eigen::VectorXd > const &input, Eigen::VectorXd const &sensitivity)
Definition: ModPiece.cpp:237
virtual void ApplyJacobianImpl(unsigned int const outputDimWrt, unsigned int const inputDimWrt, ref_vector< Eigen::VectorXd > const &input, Eigen::VectorXd const &vec)
Definition: ModPiece.cpp:262
std::vector< Eigen::VectorXd > outputs
Definition: ModPiece.h:503
ModPiece(std::vector< int > const &inputSizes, std::vector< int > const &outputSizes)
Definition: ModPiece.cpp:9
virtual void EvaluateImpl(ref_vector< boost::any > const &inputs) override
User-implemented function that determines the behavior of this muq::Modeling::WorkPiece.
Definition: ModPiece.cpp:179
Eigen::VectorXd gradient
Definition: ModPiece.h:504
Eigen::VectorXd hessAction
Definition: ModPiece.h:507
Eigen::VectorXd jacobianAction
Definition: ModPiece.h:505
virtual void ApplyHessianImpl(unsigned int const outWrt, unsigned int const inWrt1, unsigned int const inWrt2, ref_vector< Eigen::VectorXd > const &input, Eigen::VectorXd const &sens, Eigen::VectorXd const &vec)
Definition: ModPiece.cpp:436
std::vector< std::reference_wrapper< const T > > ref_vector
A vector of references to something ...
Definition: WorkPiece.h:37
@ array
array (ordered collection of values)