A mixer searching and scheduling experiment using equality saturation, more specifically egg
.
This repo can be built with cargo or using the nix flake.
Install rust toolchain manager (rustup):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Build with cargo:
cargo build --release
This repo has a nix flake which can be used to either get a dev environment for working on this repoo or building the project.
To install nix and enable experimental flake feature:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
Assuming nix is installed, building the binary:
nix build
Getting a dev environment to work on this repo:
nix develop
Example code comes with a CLI:
fluido --target-concentration 0.01 --input-space 0.04 --input-space 0 --time-limit 1
The command above will try to find output concentration of 0.01 using the provided input concentrations.
Output for the above example:
> fluido --target-concentration 0.01 --input-space 0.04 --input-space 0 --time-limit 1
Starting to equality saturation, this will take ~60 seconds
Runner report
=============
Stop reason: TimeLimit(60.058731875)
Iterations: 655
Egraph size: 813996 nodes, 1276 classes, 813996 memo
Rebuilds: 0
Total time: 60.064849359999975
Search: (0.08) 4.624326999
Apply: (0.90) 54.213128551000004
Rebuild: (0.02) 1.2272947920000008
Optimized sequence: (mix 0 (mix 0 0.04))
Cost: 0.0
- DOT output of the produced mixer's computation graph can be seen with
--show-dot
flag. - flat-ir of the mixlang produced can be seen with
--show-ir
flag. - liveness analysis over flat-ir can be seen with
--show-liveness
flag.
The saturation starts with a number, the target concentration, for the given example command above:
(0.01)
A rewrite rule that expands a number is applied to the target concentation.
rw!("expand-num";
"(?a)" => "(mix ?a ?a)"),
The expression becomes:
(mix 0.01 0.01)
There is also a rewrite rule for searching different mixer input combinations that would give the same output concentration:
rw!("differentiate-mixer";
"(mix ?a ?b)" => "(mix (- ?a 0.001) (+ ?b 0.001))"),
rw!("differentiate-mixer2";
"(mix ?a ?b)" => "(mix (+ ?a 0.001) (- ?b 0.001))"),
So equality saturation is able to find:
(mix 0.0, 0.02)
and saturation continues like this.
- One thing to note here is the
Concentration
representation used for the saturation. To be able to control the search space, a special representation for concentrations are used rather than a 'normal'f64
. This allows the control over precision. Currently theConcentration::EPSILON
=0.0001
. Which means0.00002
and0.00004
are essentially the same concentation for the purpose of saturation.
egg
is a saturation tool, which does not have a 'explicit' notion of arithmetics. To be able to do arithmetic reasoning, we defined a MixerLang
with some primitive operations such as Add (+)
, Sub (-)
. Once a mixer expression become something like (mix (- ?a 0.001) (+ ?b 0.001))
it is merged with the initial expression (mix ?a ?b)
as they are equivalent (the former derived from the latter by the differentiate-mixer rewrite rule). Once this merge operation happens, our analysis implementation is called. In which we evaluate the expression and add a Num
node equivalent to this node representing the arithmetic operation. This way the results of arithmetic operations can be found and added into the egraph.
MixLang is the intermediate language defined for representing mixer graphs and operations. Currently it consists of following constructs:
Name | Operand count | Operand type(s) | Explanation | Symbolic |
---|---|---|---|---|
Num | 1 | Constant | A consntant number, representing a specific concentration | elem1 |
Add | 2 | ID, ID | Addition of two e-nodes that are num (or equivalent to a num) | elem1 + elem2 |
Sub | 2 | ID, ID | Substraction of two e-nodes that are num (or equivalent to a num) | elem1 - elem2 |
Mix | 2 | ID, ID | Mixing of two e-nodes that are num (or equivalent to a num) | (elem1 + elem2) / 2 |