---
title: "Technical details"
output:
  html_document:
    css: greta.css
    toc: yes
    toc_float:
      collapsed: false
      toc_depth: 4
    theme: lumen
    highlight: default
vignette: >
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteIndexEntry{Technical details}
  %\usepackage[utf8]{inputenc}
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE,
                      eval = greta:::check_tf_version("message"),
                      cache = TRUE,
                      comment = NA,
                      progress = FALSE)
set.seed(1)
library(greta)
```

This page provides technical implementation details for potential contributors or the curious. You don't need to read this to be able to use greta.

## greta arrays, nodes and tensors

There are three layers to how greta defines a model: users manipulate *greta arrays*, these define *nodes*, and nodes then define *Tensors*.

### greta arrays

greta arrays are the user-facing representation of the model. greta arrays are actually just `lists`, but with the classes `greta_array` and `array`.

```{r greta_array1}
x <- ones(3, 3)
is.list(x)
class(x)
```

### nodes

The only list element of a greta array is `node`, an R6 object inheriting from the R6 class 'node', as well as one of the three node types: 'data', 'operation' or 'variable'.

```{r greta_array2}
class(x$node)
```

There is a fourth node type: 'distribution', but these are never directly associated with greta arrays.

These R6 node objects are where the magic happens.
When created, each node points to its 'child' nodes - the nodes for the greta arrays that were used to create this one.

```{r nodes1}
# data nodes have no children
length(x$node$children)

# operation nodes have one or more children
z <- x * 3
length(z$node$children)
```

Each node also has a list of its parents, the nodes that have been created from this one.

When `model()` is called, that inheritance information is used to construct the directed acyclic graph (DAG) that defines the model. The inheritance also preserves intermediate nodes, such as those creates in multi-part operations, but not assigned as greta arrays.

Nodes also have a value member, which is an array for data nodes or an 'unknowns' object for other node types. The unknowns class is a thin wrapper around arrays, which prints the question marks. Generic functions for working on arrays (e.g. `dim`, `length`, `print`) make use these node values to return something familiar to the user.
```{r nodes2}
x$node$value()
# R calls this a matrix because it's 2d, but it's an array
class(x$node$value())
z$node$value()
class(z$node$value())
```

### Tensors

In addition to remembering their shape and size and where they are in the DAG, each node has methods to define a corresponding TensorFlow Tensor object in a specified environment. That doesn't happen until the user runs `model()`, which creates a 'dag_class' object to store the relevant nodes, the environment for the tensors, and methods to talk to the TensorFlow graph.

The node `tf()` method takes the dag as an argument, and defines a tensor representing itself in the tensorflow environment, with a name determined by the dag object.

```{r tensors1}
x$node$tf
```

Because R6 objects are pass-by-reference (rather than pass-by-value), the dag accumulates all of the defined tensors, rather than being re-written each time. Similarly, because nodes are R6 objects and know which are their children, they can make sure those children are defined as tensors before they are. The `define_tf()` member function ensures that that happens, enabling operation nodes to use the tensors for their children when defining their own tensor.

```{r tensors2}
z$node$tf
```

## variables and free states

Hamiltonian Monte Carlo (HMC) requires all of the parameters to be transformed to a continuous scale for sampling. Variable nodes are therefore converted to tensors by first defining a 'free' (unconstrained) version of themselves as a tensor, and then applying a transformation function to convert them back to the scale of interest.

```{r free_state}
a = variable(lower = 0)
class(a$node)
a$node$tf_from_free
```

## distributions

distribution nodes are node objects just like the others, but they are not *directly* associated with greta arrays. Instead, greta arrays may have a distribution node in their `distribution` slot to indicate that their values are assumed to follow that distribution. The distribution node will also be listed as a parent node, and likewise the 'target node' will be listed as a child of the distribution. Distribution nodes also have child nodes (data, variables or operations) representing their parameters.

```{r distributions1}
b = normal(0, 1)
class(b$node)
class(b$node$distribution)
# a is the target of its own distribution
class(b$node$distribution$target)
```

When they define themselves as tensors, distribution nodes define the log density of their target node/tensor given the tensors representing their parameters.

```{r distributions2}
b$node$distribution$tf_log_density
```

If the distribution was truncated, the log density is normalised using the cumulative distribution function.

## Joint density

Those log-densities for these distributions are summed on the TensorFlow graph to create a Tensor for the joint log-density of the model.
TensorFlow's automatic gradient capabilities are then used to define a Tensor for the gradient of the log-density with respect to each parameter in the model.

The `dag` R6 object contained within the model then exposes methods to send parameters to the TensorFlow graph and return the joint density and gradient.

```{r dag1}
model <- model(b)
model$dag$send_parameters
model$dag$log_density
model$dag$gradients
```

These methods are used by MCMC samplers to explore the model parameters.
