MUQ  0.4.3
Combining Components: Model Graphs

Description

Individual ModPieces form the building blocks for larger more complicated models. MUQ makes it possible to connect ModPieces together on a computational graph to define larger models and enable the reuse of existing ModPiece implementations. The idea is to great graph of ModPieces where each node in the graph corresponds to a ModPiece and each edge represents a composition of one ModPiece with another. For example, if functions \(g(y)\) and \(f(x)\) are implemented as ModPieces, we can define the composition \(g(f(x))\) by creating a graph with two nodes and one edge from the output of \(f\) to the input of \(g\). Visually, the resulting graph would look something like

In MUQ, model graphs are defined using the WorkGraph class. The AddNode function in the WorkGraph class enables new model components to be added while the AddEdge allows us to make connections between the components. Here's an example of implementing the simple $g(f(x))$ model in MUQ. We use \(f(x)=\sin(x)\) and \(g(y)=\exp(y)\).

C++
Python
#include "MUQ/Modeling/WorkGraph.h"
#include "MUQ/Modeling/ModGraphPiece.h"
#include "MUQ/Modeling/CwiseOperators/CwiseUnaryOperator.h"

using namespace muq::Modeling;

int main(){
  unsigned int dim = 2;
  auto f = std::make_shared<SinOperator>(dim);
  auto g = std::make_shared<ExpOperator>(dim);

  auto graph = std::make_shared<WorkGraph>();

  graph->AddNode(f,"f");
  graph->AddNode(g,"g");
  graph->AddEdge("f",0,"g",0); // connect output 0 of f with input 0 of g

  auto gof = graph->CreateModPiece("f");

  return 0;
}

The AddEdge function takes in the names of the nodes you wish to connect as well as integers specifying a particular output of f we want to connect to a particular input of g. In this case, both the SinOperator and ExpOperator ModPieces have a single input and a single input. Hence, we used "0" for both inputs and outputs. The CreateModPiece function returns a chile of the ModPiece class that uses the graph to evaluate \(f(g(x))\). Becuase it's a ModPiece, all of the usual ModPiece derivative functions are available. MUQ uses first and second order chain rules to analytically compute derivative information through the entire graph.

Consider another example where \(x\in\mathbb{R}^4\) and we want to implement a model that computes \(\sin(x_{1:2}) + \exp(x_{3:4})\). First, we'll use the SplitVector ModPiece to separate the vector \(x\) into two vectors: \(x_{1:2}\) and \(x_{3:4}\). After using the SinOperator and ExpOperator, we'll then sum the results using the SumPiece ModPiece. The resulting graph should look like

The code to construct this computational graph in MUQ is given below.

C++
Python
#include "MUQ/Modeling/WorkGraph.h"
#include "MUQ/Modeling/ModGraphPiece.h"
#include "MUQ/Modeling/SumPiece.h"
#include "MUQ/Modeling/SplitVector.h"
#include "MUQ/Modeling/CwiseOperators/CwiseUnaryOperator.h"

using namespace muq::Modeling;

int main(){
  auto f = std::make_shared<SinOperator>(2);
  auto g = std::make_shared<ExpOperator>(2);
  auto sum = std::make_shared<SumPiece>(2);

  // Will split x_{1:dim} into two equally sized vectors
  auto splitter = std::make_shared<SplitVector>(std::vector<int>{0,2}, // indices of output
                                                std::vector<int>{2,2}, // sizes of output
                                                4); // size of input

  auto graph = std::make_shared<WorkGraph>();

  graph->AddNode(splitter, "x12,x34");
  graph->AddNode(g,"g");
  graph->AddNode(f,"f");
  graph->AddEdge("x12,x34",0,"f",0); // connect output 0 of x12,x34 with input 0 of f
  graph->AddEdge("x12,x34",0,"g",0); // connect output 1 of x12,x34 with input 0 of g

  graph->AddNode(sum,"f+g");
  graph->AddEdge("f",0,"f+g",0); // connect output 0 of f with input 0 of f+g
  graph->AddEdge("g",0,"f+g",1); // connect output 0 of g with intpu 1 of f+g

  auto mod = graph->CreateModPiece("f+g");

  return 0;
}