Ops
The ops module provides sets of operations and formats for building and evolving genetic programs including graphs and trees. In the language of radiate, when using an op, it is the Allele of the GraphNode or TreeNode.
An op is a function that takes a number of inputs and returns a single output. The op can be a constant value, a variable, or a function that operates on the inputs.
The op comes in four flavors, mirroring the variants of the Op<T> enum:
- Function (
Fn): Stateless functions that take inputs and return a value (e.g.Add,Sigmoid). - Variable (
Var): Reads from an input vector, returning the value at that index. - Constant (
Const): A fixed value that does not change - returning the value when called. - Value (
Value): A stateful operation that holds data (aParam<T>) alongside a function, allowing for learnable parameters such as theWeightop.
Each op has an arity, defining the number of inputs it accepts. For example, the Add operation has an arity of 2 because it takes two inputs and returns their sum. The Const operation has an arity of 0 because it does not take any inputs, it just returns its value. The Var operation has an arity of 0 because it takes an index as a parameter, and returns the value of the input at that index.
Provided Ops include:
Basic ops
| Name | Arity | Description | Initialize | Type |
|---|---|---|---|---|
const |
0 | A fixed constant value | Op::constant() |
Const |
named_const |
0 | A constant value with an associated name | Op::named_constant(name) |
Const |
var |
0 | Variable. input[i] - return the value of the input at index i |
Op::var(i) |
Var |
identity |
1 | return the input value | Op::identity() |
Fn |
Basic math operations
| Name | Arity | Description | Initialize | Type |
|---|---|---|---|---|
Add |
2 | x + y | Op::add() |
Fn |
Sub |
2 | x - y | Op::sub() |
Fn |
Mul |
2 | x * y | Op::mul() |
Fn |
Div |
2 | x / y | Op::div() |
Fn |
Sum |
Any | Sum of n values | Op::sum() |
Fn |
Product |
Any | Product of n values | Op::prod() |
Fn |
Difference |
Any | Difference of n values | Op::diff() |
Fn |
Neg |
1 | -x | Op::neg() |
Fn |
Abs |
1 | abs(x) | Op::abs() |
Fn |
pow |
2 | x^y | Op::pow() |
Fn |
Sqrt |
1 | sqrt(x) | Op::sqrt() |
Fn |
Exp |
1 | e^x | Op::exp() |
Fn |
Log |
1 | log(x) | Op::log() |
Fn |
Sin |
1 | sin(x) | Op::sin() |
Fn |
Cos |
1 | cos(x) | Op::cos() |
Fn |
Tan |
1 | tan(x) | Op::tan() |
Fn |
Max |
Any | Max of n values | Op::max() |
Fn |
Min |
Any | Min of n values | Op::min() |
Fn |
Ceil |
1 | ceil(x) | Op::ceil() |
Fn |
Floor |
1 | floor(x) | Op::floor() |
Fn |
Weight |
1 | x * w (input multiplied by a learnable weight) | Op::weight() |
Value |
Activation Ops
These are the most common activation functions used in Neural Networks. Each accepts any number of inputs, sums them first, then applies the activation — so x in the descriptions below refers to the sum of the node's inputs.
| Name | Arity | Description | Initialize | Type |
|---|---|---|---|---|
Sigmoid |
Any | 1 / (1 + e^-x) | Op::sigmoid() |
Fn |
Tanh |
Any | tanh(x) | Op::tanh() |
Fn |
ReLU |
Any | max(0, x) | Op::relu() |
Fn |
LeakyReLU |
Any | x if x > 0 else 0.5x | Op::leaky_relu() |
Fn |
ELU |
Any | x if x > 0 else 0.5(e^x - 1) | Op::elu() |
Fn |
Linear |
Any | x (sum of inputs, identity activation) | Op::linear() |
Fn |
Softplus |
Any | log(1 + e^x) | Op::softplus() |
Fn |
Swish |
Any | x / (1 + e^-x) | Op::swish() |
Fn |
Mish |
Any | x * tanh(ln(1 + e^x)) | Op::mish() |
Fn |
bool Ops
| Name | Arity | Description | Initialize | Type |
|---|---|---|---|---|
And |
2 | x && y | Op::and() |
Fn |
Or |
2 | x || y |
Op::or() |
Fn |
Not |
1 | !x | Op::not() |
Fn |
Xor |
2 | x ^ y | Op::xor() |
Fn |
Nand |
2 | !(x && y) | Op::nand() |
Fn |
Nor |
2 | !(x || y) |
Op::nor() |
Fn |
Xnor |
2 | !(x ^ y) | Op::xnor() |
Fn |
Equal |
2 | x == y | Op::eq() |
Fn |
NotEqual |
2 | x != y | Op::ne() |
Fn |
Greater |
2 | x > y | Op::gt() |
Fn |
Less |
2 | x < y | Op::lt() |
Fn |
GreaterEqual |
2 | x >= y | Op::ge() |
Fn |
LessEqual |
2 | x <= y | Op::le() |
Fn |
IfElse |
3 | if x then y else z | Op::if_else() |
Fn |
import radiate as rd
add = rd.Op.add()
sub = rd.Op.sub()
mul = rd.Op.mul()
div = rd.Op.div()
constant = rd.Op.const(42.0)
variable = rd.Op.var(0)
sigmoid = rd.Op.sigmoid()
relu = rd.Op.relu()
tanh = rd.Op.tanh()
add_result = rd.Op.add().eval(1.0, 2.0) # result is 3.0
const_result = rd.Op.const(42.0).eval() # result is 42.0
var_result = rd.Op.var(0).eval(5.0, 10.0) # result is 5.0 when evaluated with inputs
// Example usage of an Op
let fn_op = Op::add();
let result = fn_op.eval(&[1.0, 2.0]); // result is 3.0
// Example usage of a constant Op
let const_op = Op::constant(42.0);
let result = const_op.eval(&[]); // result is 42.0
// Example usage of a variable Op
let var_op = Op::var(0); // Read from input at index 0
let inputs = var_op.eval(&[5.0, 10.0]); // result is 5.0 when evaluated with inputs
Want to create your own Op<T>? It's pretty simple! But beware, radiate cannot serialize/deserialize custom ops. Let's create a custom Square operation that squares its input.
fn my_square_op(inputs: &[f32]) -> f32 {
inputs[0] * inputs[0]
}
// Supply a name, arity (number of inputs), and function to create the Op.
// Square takes a single input, so its arity is Exact(1).
let square_op = Op::Fn("Square", Arity::Exact(1), my_square_op);
Now you have a new square_op which is completely compatible with the rest of the Radiate GP system and can be plugged in anywhere a regular Op can be used! For more information on creating ops, check out the API docs to see how the rest are created - it's not too crazy.
Alters
OperationMutator
Inputs
rate: f32 - Mutation rate (0.0 to 1.0)replace_rate: f32 - Rate at which to replace an oldopwith a completely new one (0.0 to 1.0)
- Purpose: Randomly mutate an operation within a
TreeNodeorGraphNode.
This mutator randomly changes or alters the op of a node within a TreeChromosome or GraphChromosome. It can replace the op with a new one from the store or modify its parameters.