use super::aggregate::GraphAggregate;
use crate::{
Arity, Factory, NodeStore,
collections::{Graph, GraphNode, NodeType},
};
impl<T: Clone + Default> Graph<T> {
pub fn directed(
input_size: usize,
output_size: usize,
values: impl Into<NodeStore<T>>,
) -> Graph<T> {
let builder = NodeBuilder::new(values);
let input_nodes = builder.input(input_size);
let output_nodes = builder.output(output_size);
GraphAggregate::new()
.all_to_all(&input_nodes, &output_nodes)
.build()
}
pub fn recurrent(
input_size: usize,
output_size: usize,
values: impl Into<NodeStore<T>>,
) -> Graph<T> {
let builder = NodeBuilder::new(values);
let input = builder.input(input_size);
let aggregate = builder.vertices(input_size);
let output = builder.output(output_size);
GraphAggregate::new()
.one_to_one(&input, &aggregate)
.cycle(&aggregate)
.all_to_all(&aggregate, &output)
.build()
}
pub fn weighted_directed(
input_size: usize,
output_size: usize,
values: impl Into<NodeStore<T>>,
) -> Graph<T> {
let builder = NodeBuilder::new(values);
let input = builder.input(input_size);
let output = builder.output(output_size);
let weights = builder.edge(input_size * output_size);
GraphAggregate::new()
.one_to_many(&input, &weights)
.many_to_one(&weights, &output)
.build()
}
pub fn weighted_recurrent(
input_size: usize,
output_size: usize,
values: impl Into<NodeStore<T>>,
) -> Graph<T> {
let builder = NodeBuilder::new(values);
let input = builder.input(input_size);
let aggregate = builder.vertices(input_size);
let output = builder.output(output_size);
let weights = builder.edge(input_size * output_size);
GraphAggregate::new()
.one_to_one(&input, &aggregate)
.cycle(&aggregate)
.one_to_many(&aggregate, &weights)
.many_to_one(&weights, &output)
.build()
}
pub fn lstm(input_size: usize, output_size: usize, store: impl Into<NodeStore<T>>) -> Graph<T> {
let builder = NodeBuilder::new(store);
let input = builder.input(input_size);
let output = builder.output(output_size);
let cell_state = builder.vertices_with_arity(1, Arity::Any);
let hidden_state = builder.vertices_with_arity(1, Arity::Any);
let forget_gate = builder.vertices_with_arity(1, Arity::Any);
let input_gate = builder.vertices_with_arity(1, Arity::Any);
let output_gate = builder.vertices_with_arity(1, Arity::Any);
let candidate = builder.vertices_with_arity(1, Arity::Any);
GraphAggregate::new()
.all_to_all(&input, &forget_gate)
.all_to_all(&input, &input_gate)
.all_to_all(&input, &output_gate)
.all_to_all(&input, &candidate)
.one_to_one(&hidden_state, &forget_gate)
.one_to_one(&hidden_state, &input_gate)
.one_to_one(&hidden_state, &output_gate)
.one_to_one(&hidden_state, &candidate)
.one_to_one(&forget_gate, &cell_state)
.one_to_one(&input_gate, &candidate)
.one_to_one(&candidate, &cell_state)
.one_to_one(&cell_state, &hidden_state)
.one_to_one(&output_gate, &hidden_state)
.all_to_all(&hidden_state, &output)
.build()
}
pub fn gru(input_size: usize, output_size: usize, values: impl Into<NodeStore<T>>) -> Graph<T> {
let builder = NodeBuilder::new(values);
let input = builder.input(input_size);
let output = builder.output(output_size);
let hidden = builder.vertices_with_arity(1, Arity::Any);
let update = builder.vertices_with_arity(1, Arity::Any);
let reset = builder.vertices_with_arity(1, Arity::Any);
let candidate = builder.vertices_with_arity(1, Arity::Any);
let blend = builder.vertices_with_arity(1, Arity::Any);
let gate_flip = builder.vertices_with_arity(1, Arity::Any);
GraphAggregate::new()
.many_to_one(&input, &reset)
.many_to_one(&input, &update)
.many_to_one(&input, &candidate)
.one_to_one(&hidden, &reset)
.one_to_one(&hidden, &update)
.one_to_one(&hidden, &candidate)
.one_to_one(&update, &blend)
.one_to_one(&candidate, &blend)
.one_to_one(&reset, &hidden)
.one_to_one(&update, &gate_flip)
.one_to_one(&hidden, &gate_flip)
.one_to_one(&gate_flip, &hidden)
.one_to_one(&blend, &hidden)
.one_to_many(&hidden, &output)
.build()
}
pub fn mesh(
input_size: usize,
output_size: usize,
width: usize,
height: usize,
values: impl Into<NodeStore<T>>,
) -> Graph<T> {
let builder = NodeBuilder::new(values);
let inputs = builder.input(input_size);
let outputs = builder.output(output_size);
let nodes = (0..width * height)
.map(|_| builder.vertices(1))
.collect::<Vec<Vec<GraphNode<T>>>>();
let mut aggregate = GraphAggregate::new();
for y in 0..height {
for x in 0..width {
let index = y * width + x;
let current = &nodes[index];
if x + 1 < width {
let right = &nodes[y * width + (x + 1)];
aggregate = aggregate.one_to_one(current, right);
}
if y + 1 < height {
let down = &nodes[(y + 1) * width + x];
aggregate = aggregate.one_to_one(current, down);
}
}
}
aggregate
.many_to_one(&inputs, &nodes[0])
.one_to_many(&nodes[nodes.len() - 1], &outputs)
.build()
}
}
pub struct NodeBuilder<T> {
store: NodeStore<T>,
}
impl<T: Clone + Default> NodeBuilder<T> {
pub fn new(store: impl Into<NodeStore<T>>) -> Self {
NodeBuilder {
store: store.into(),
}
}
pub fn input(&self, size: usize) -> Vec<GraphNode<T>> {
self.new_nodes(NodeType::Input, size, Arity::Zero)
}
pub fn output(&self, size: usize) -> Vec<GraphNode<T>> {
self.new_nodes(NodeType::Output, size, Arity::Any)
}
pub fn edge(&self, size: usize) -> Vec<GraphNode<T>> {
self.new_nodes(NodeType::Edge, size, Arity::Exact(1))
}
pub fn vertices(&self, size: usize) -> Vec<GraphNode<T>> {
self.new_nodes(NodeType::Vertex, size, Arity::Any)
}
pub fn vertices_with_arity(&self, size: usize, arity: Arity) -> Vec<GraphNode<T>> {
(0..size)
.filter_map(|idx| {
self.store
.new_instance((idx, NodeType::Vertex, |a| a == arity))
})
.collect()
}
fn new_nodes(
&self,
node_type: NodeType,
size: usize,
fallback_arity: Arity,
) -> Vec<GraphNode<T>> {
if self.store.contains_type(node_type) {
(0..size)
.filter_map(|idx| self.store.new_instance((idx, node_type)))
.collect()
} else {
(0..size)
.filter_map(|idx| {
self.store
.new_instance((idx, node_type, |arity| arity == fallback_arity))
})
.collect()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Node, Op, node_store};
use radiate_core::Valid;
#[test]
fn test_graph_builder() {
let graph = Graph::directed(3, 3, Op::sigmoid());
assert_eq!(graph.len(), 6);
for node in graph.iter() {
if node.node_type() == NodeType::Input {
assert_eq!(node.arity(), Arity::Zero);
assert_eq!(node.incoming().iter().count(), 0);
assert_eq!(node.outgoing().iter().count(), 3);
} else if node.node_type() == NodeType::Output {
assert_eq!(node.arity(), Arity::Any);
assert_eq!(node.incoming().iter().count(), 3);
assert_eq!(node.outgoing().iter().count(), 0);
assert_eq!(node.value(), &Op::sigmoid());
}
}
}
#[test]
fn test_graph_builder_recurrent() {
let graph = Graph::recurrent(3, 3, Op::sigmoid());
assert_eq!(graph.len(), 9);
for node in graph.iter() {
if node.node_type() == NodeType::Input {
assert_eq!(node.arity(), Arity::Zero);
assert_eq!(node.incoming().iter().count(), 0);
assert_eq!(node.outgoing().iter().count(), 1);
} else if node.node_type() == NodeType::Vertex {
assert_eq!(node.arity(), Arity::Any);
assert!(node.is_recurrent());
assert_eq!(node.value(), &Op::sigmoid());
} else if node.node_type() == NodeType::Output {
assert_eq!(node.arity(), Arity::Any);
assert_eq!(node.incoming().iter().count(), 3);
assert_eq!(node.outgoing().iter().count(), 0);
assert_eq!(node.value(), &Op::sigmoid());
}
}
}
#[test]
fn test_graph_builder_with_no_any() {
let graph = Graph::directed(3, 3, Op::add());
assert_eq!(graph.len(), 6);
assert!(graph.is_valid());
}
#[test]
fn test_graph_builder_weighted() {
let store = vec![
(NodeType::Input, vec![Op::var(0), Op::var(1), Op::var(2)]),
(NodeType::Output, vec![Op::sigmoid()]),
(NodeType::Edge, vec![Op::weight_with(1.0)]),
];
let graph = Graph::weighted_directed(3, 3, store);
assert_eq!(graph.len(), 15);
assert!(graph.is_valid());
for node in graph.iter() {
if node.node_type() == NodeType::Input {
assert_eq!(node.arity(), Arity::Zero);
assert_eq!(node.incoming().iter().count(), 0);
assert_eq!(node.outgoing().iter().count(), 3);
} else if node.node_type() == NodeType::Edge {
assert_eq!(node.arity(), Arity::Exact(1));
assert_eq!(node.incoming().iter().count(), 1);
assert_eq!(node.outgoing().iter().count(), 1);
assert_eq!(node.value(), &Op::weight_with(1.0));
} else if node.node_type() == NodeType::Output {
assert_eq!(node.arity(), Arity::Any);
assert_eq!(node.incoming().iter().count(), 3);
assert_eq!(node.outgoing().iter().count(), 0);
assert_eq!(node.value(), &Op::sigmoid());
}
}
}
#[test]
fn test_graph_builder_weighted_recurrent() {
let store = node_store![
Input => vec![Op::var(0), Op::var(1), Op::var(2)],
Output => vec![Op::sigmoid()],
Edge => vec![Op::weight_with(1.0)]
];
let graph = Graph::weighted_recurrent(3, 3, store);
assert_eq!(graph.len(), 18);
assert!(graph.is_valid());
for node in graph.iter() {
if node.node_type() == NodeType::Input {
assert_eq!(node.arity(), Arity::Zero);
assert_eq!(node.incoming().iter().count(), 0);
assert_eq!(node.outgoing().iter().count(), 1);
} else if node.node_type() == NodeType::Edge {
assert_eq!(node.arity(), Arity::Exact(1));
assert_eq!(node.incoming().iter().count(), 1);
assert_eq!(node.outgoing().iter().count(), 1);
assert_eq!(node.value(), &Op::weight_with(1.0));
} else if node.node_type() == NodeType::Output {
assert_eq!(node.arity(), Arity::Any);
assert_eq!(node.incoming().iter().count(), 3);
assert_eq!(node.outgoing().iter().count(), 0);
assert_eq!(node.value(), &Op::sigmoid());
} else if node.node_type() == NodeType::Vertex {
assert_eq!(node.arity(), Arity::Any);
assert!(node.is_recurrent());
assert_eq!(node.value(), &Op::sigmoid());
}
}
}
#[test]
fn test_graph_builder_lstm() {
let store = node_store![
Input => vec![Op::var(0)],
Output => vec![Op::sigmoid()],
Vertex => vec![Op::sigmoid(), Op::tanh(), Op::mul(), Op::add()],
Edge => vec![Op::weight_with(1.0)]
];
let graph = Graph::lstm(1, 1, store);
assert_eq!(graph.len(), 8);
assert!(graph.is_valid());
for node in graph.iter() {
if node.node_type() == NodeType::Input {
assert_eq!(node.arity(), Arity::Zero);
assert_eq!(node.incoming().iter().count(), 0);
assert_eq!(node.outgoing().iter().count(), 4);
} else if node.node_type() == NodeType::Output {
assert_eq!(node.arity(), Arity::Any);
assert_eq!(node.incoming().iter().count(), 1);
assert_eq!(node.outgoing().iter().count(), 0);
assert_eq!(node.value(), &Op::sigmoid());
} else if node.node_type() == NodeType::Vertex {
assert_eq!(node.arity(), Arity::Any);
assert!(
vec![Op::sigmoid(), Op::tanh(), Op::mul(), Op::add()].contains(&node.value())
);
} else if node.node_type() == NodeType::Edge {
assert_eq!(node.arity(), Arity::Exact(1));
assert_eq!(node.incoming().iter().count(), 1);
assert_eq!(node.outgoing().iter().count(), 1);
assert_eq!(node.value(), &Op::weight_with(1.0));
}
}
}
#[test]
fn test_graph_builder_gru() {
let store = node_store![
Input => vec![Op::var(0)],
Output => vec![Op::sigmoid()],
Vertex => vec![Op::sigmoid(), Op::tanh(), Op::mul(), Op::add()],
Edge => vec![Op::weight_with(1.0)]
];
let graph = Graph::gru(1, 1, store);
assert_eq!(graph.len(), 8);
assert!(graph.is_valid());
for node in graph.iter() {
if node.node_type() == NodeType::Input {
assert_eq!(node.arity(), Arity::Zero);
assert_eq!(node.incoming().iter().count(), 0);
assert_eq!(node.outgoing().iter().count(), 3);
} else if node.node_type() == NodeType::Output {
assert_eq!(node.arity(), Arity::Any);
assert_eq!(node.incoming().iter().count(), 1);
assert_eq!(node.outgoing().iter().count(), 0);
assert_eq!(node.value(), &Op::sigmoid());
} else if node.node_type() == NodeType::Vertex {
assert_eq!(node.arity(), Arity::Any);
assert!(
vec![Op::sigmoid(), Op::tanh(), Op::mul(), Op::add()].contains(&node.value())
);
} else if node.node_type() == NodeType::Edge {
assert_eq!(node.arity(), Arity::Exact(1));
assert_eq!(node.incoming().iter().count(), 1);
assert_eq!(node.outgoing().iter().count(), 1);
assert_eq!(node.value(), &Op::weight_with(1.0));
}
}
}
}