MUQ  0.4.3
UMBridgeModelClient.cpp
Go to the documentation of this file.
1 
9 
10 #include <boost/property_tree/ptree.hpp>
11 
12 /***
13 ## Overview
14 
15 The UM-Bridge interface allows coupling model and UQ codes through HTTP. A model may then
16 be implemented in virtually any programming language or framework, run in a container
17 or even on a remote machine. Likewise, the model does not make any assumptions on how the client is implemented.
18 
19 This example shows how to connect to a running UM-Bridge server that is implemented in the UM-Bridge Server example.
20 The server provides the physical model, while the client is responsible for the UQ side.
21 
22 The UM-Bridge interface is fully integrated in MUQ and can be used by means of the UMBridgeModPiece class.
23 Once such an UMBridgeModPiece is set up, it can be used like any other ModPiece. If the model supports the respective
24 functionality, the ModPiece then provides simple model evaluations,
25 gradient evaluations, applications of the Jacobian etc.
26 
27 */
28 int main(){
29 
30  using namespace muq::Modeling;
31 
32 /***
33 ## Connect to model server
34 First, we set up an UMBridgeModPiece that connects to our model server, giving the server's address
35 and the model name we'd like to use. This assumes that, before the client is started,
36 the model server from the UM-Bridge Server example is already running on your machine.
37 */
38 
39  auto mod = std::make_shared<UMBridgeModPiece>("http://localhost:4242", "forward");
40 
41  std::cout << mod->inputSizes << std::endl;
42  std::cout << mod->outputSizes << std::endl;
43 
44 /***
45 ## Set up dimensions
46 We then set up some helpers for determining dimensions and coordinates needed below.
47 
48 Note that, from this point on, this example is completely identical to the FlowEquation example.
49 */
50 
51  unsigned int numCells = 200;
52 
53  // Vector containing the location of each node in the "mesh"
54  Eigen::VectorXd nodeLocs = Eigen::VectorXd::LinSpaced(numCells+1,0,1);
55 
56  // Vector containing the midpoint of each cell
57  Eigen::VectorXd cellLocs = 0.5*(nodeLocs.head(numCells) + nodeLocs.tail(numCells));
58 
59 
60 /***
61 ## Evaluate Model
62 Here we construct a vector of conductivity values and call the `FlowEquation.Evaluate` function to evaluate the model.
63 Recall that you should implement the `EvaluateImpl` function, but call the `Evaluate` function.
64 In addition to calling `EvaluateImpl`, the `Evaluate` function checks the size of the input vectors, tracks run
65 times, counts function evaluations, and manages a one-step cache of outputs.
66 */
67 
68  // Define a conductivity field and evaluate the model k(x) = exp(cos(20*x))
69  Eigen::VectorXd cond = (20.0*cellLocs.array()).cos().exp();
70 
71  Eigen::VectorXd h = mod->Evaluate(cond).at(0);
72 
73  std::cout << "Solution: \n" << h.transpose() << std::endl << std::endl;
74 
75 /***
76 ## Check Model Gradient
77 To check our adjoint gradient implementation, we will employ a finite difference approximation of the gradient vector.
78 Before doing that however, we need to define a scalar "objective function" $J(h)$ that can be composed with the flow
79 equation model. In practice, this objective function is often the likelihood function or posterior density in a
80 Bayesian inverse problem. For simplicity, we will just consider the log of a standard normal density:
81 $$
82 J(h) \propto -\frac{1}{2} \|h\|^2.
83 $$
84 
85 The following cell uses the density view of MUQ's `Gaussian` class to define $J(h)$. The `objective`
86 defined in this cell is just another `ModPiece` and be used in the same way as any other `ModPiece`.
87 */
88  auto objective = std::make_shared<Gaussian>(numCells+1)->AsDensity();
89 
90 /***
91 To obtain the gradient of the objective function $J(h)$ with respect to the vector of cell-wise conductivities
92 $\mathbf{k}$, we need to apply the chain rule:
93 $$
94 \nabla_{\mathbf{k}} J = \left( \nabla_h J \right) \nabla_{\mathbf{k}} h
95 $$
96 The following cell uses the `objective` object to obtain the initial sensitivity $\nabla_h J$. This is then
97 passed to the `Gradient` function of the flow model, which will use our adjoint implementation above to
98 compute $\nabla_{\mathbf{k}} J$.
99 */
100  Eigen::VectorXd objSens = Eigen::VectorXd::Ones(1);
101  Eigen::VectorXd sens = objective->Gradient(0,0,h,objSens);
102  Eigen::VectorXd grad = mod->Gradient(0,0,cond,sens);
103 
104  std::cout << "Gradient: \n" << grad.transpose() << std::endl << std::endl;
105 
106 /***
107 To verify our `FlowEquation.GradientImpl` function, we can call the built in `ModPiece.GradientByFD` function
108 to construct a finite difference approximation of the gradient. If all is well, the finite difference and adjoint
109 gradients will be close.
110 */
111  Eigen::VectorXd gradFD = mod->GradientByFD(0,0,std::vector<Eigen::VectorXd>{cond},sens);
112  std::cout << "Finite Difference Gradient:\n" << gradFD.transpose() << std::endl << std::endl;
113 
114 /***
115 ## Test Jacobian of Model
116 Here we randomly choose a vector $v$ (`jacDir`) and compute the action of the Jacobian $Jv$ using both our adjoint method and MUQ's built-in finite difference implementation.
117 */
118  Eigen::VectorXd jacDir = Eigen::VectorXd::Ones(numCells);//RandomGenerator::GetUniform(numCells);
119 
120  Eigen::VectorXd jacAct = mod->ApplyJacobian(0,0, cond, jacDir);
121  std::cout << "Jacobian Action: \n" << jacAct.transpose() << std::endl << std::endl;
122 
123  Eigen::VectorXd jacActFD = mod->ApplyJacobianByFD(0,0, std::vector<Eigen::VectorXd>{cond}, jacDir);
124  std::cout << "Finite Difference Jacobian Action \n" << jacActFD.transpose() << std::endl << std::endl;
125 
126 /***
127 ## Test Hessian of Model
128 We now take a similar approach to verifying our Hessian action implementation. Here we randomly choose a vector $v$ (`hessDir`)
129 and compute $Hv$ using both our adjoint method and MUQ's built-in finite difference implementation.
130 */
131  Eigen::VectorXd hessDir = Eigen::VectorXd::Ones(numCells);//RandomGenerator::GetUniform(numCells);
132 
133  Eigen::VectorXd hessAct = mod->ApplyHessian(0,0,0,cond,sens,hessDir);
134  std::cout << "Hessian Action: \n" << hessAct.transpose() << std::endl << std::endl;
135 
136  Eigen::VectorXd hessActFD = mod->ApplyHessianByFD(0,0,0,std::vector<Eigen::VectorXd>{cond},sens,hessDir);
137  std::cout << "Finite Difference Hessian Action \n" << hessActFD.transpose() << std::endl << std::endl;
138 
139 /***
140 ## Test Hessian of Objective
141 In the tests above, we manually evaluate the `objective` and `mod` components separately. They can also be combined
142 in a MUQ `WorkGraph`, which is more convenient when a large number of components are used or Hessian information needs
143 to be propagated through multiple different components.
144 
145 The following code creates a `WorkGraph` that maps the output of the flow model to the input of the objective function.
146 It then creates a new `ModPiece` called `fullMod` that evaluates the composition $J(h(k))$.
147 */
148  WorkGraph graph;
149  graph.AddNode(mod, "Model");
150  graph.AddNode(objective, "Objective");
151  graph.AddEdge("Model",0,"Objective",0);
152 
153  auto fullMod = graph.CreateModPiece("Objective");
154 
155 /***
156 As before, we can apply the Hessian of the full model to the randomly generated `hessDir` vector and compare the results
157 with finite differences. Notice that the results shown here are slightly different than the Hessian actions computed above.
158 Above, we manually fixed the sensitivity $s$ independently of $h$ and did not account for the relationship between the
159 conductivity $k$ on the sensitivity $s$. The `WorkGraph` however, captures all of those dependencies.
160 */
161 
162  hessAct = fullMod->ApplyHessian(0,0,0,cond,objSens,hessDir);
163  hessActFD = fullMod->ApplyHessianByFD(0,0,0,std::vector<Eigen::VectorXd>{cond},objSens,hessDir);
164 
165  std::cout << "Hessian Action: \n" << hessAct.transpose() << std::endl << std::endl;
166 
167  std::cout << "Finite Difference Hessian Action \n" << hessActFD.transpose() << std::endl << std::endl;
168 
169  return 0;
170 }
int main()
A graph of connected muq::Modeling::WorkPiece's.
Definition: WorkGraph.h:20
void AddNode(std::shared_ptr< WorkPiece > input, std::string const &name)
Add a new node to the graph.
Definition: WorkGraph.cpp:195
void AddEdge(std::string const &nameFrom, unsigned int const outputDim, std::string const &nameTo, unsigned int const inputDim)
Add a new edge to the graph.
Definition: WorkGraph.cpp:206
std::shared_ptr< ModGraphPiece > CreateModPiece(std::string const &node, std::vector< std::string > const &inNames=std::vector< std::string >()) const
Create a muq::Modeling::ModPiece whose output matches a given node.
Definition: WorkGraph.cpp:591