this project is a lightweight C++ 20 framework for building and evaluating mathematical expressions using directed acyclic graphs. it wields static polymorphism and concepts to accommodate a type-safe environment with support for standard arithmetic, forward automatic differentiation, and structural graph optimizations.
- polymorphic node hierarchy: abstract base classes for unary, binary, input, and constant nodes
- automatic differentiation: built-in support for dual numbers to compute exact derivatives alongside function values
- common subexpression elimination: automatic detection and reuse of idential subcalculations to reduce memory overhead and computation time
- fluent dsl: operator overloading allows for writing math naturally
- swappable evaluation strategies
- graph visualization
| entity | element | purpose |
|---|---|---|
Graph |
structure | owns the lifecycle of nodes and ensures the graph's structural integrity |
Expression |
logic | a wrapper providing the user-facing interface and operator overloads |
Evaluator |
algorithm | reading graphs, eparates how the graph is traversed from the graph itself using template policies |
- role: the centralized owner and container of the computational structure
- responsibilities:
- owns all
Nodeobjects viastd::unique_ptr - provides factory methods like
constant,input,addto ensure valid graph construction - maintains the topological integrity of the DAG
- owns all
- abstraction / design choice:
template<T>allows the entire engine to operate on any numeric type without code duplication- container abstraction ensures that the user doesn't manage
Node*pointers directly and can use safeNodeIDhandles instead
- role: the polymorphic interface for any operation or data source in the graph
- responsibilities:
- defines the contract that all nodes must satisfy
- provides runtime type information via
kind()andlabel()for visualization
- abstraction / design choice:
- runtime polymorphism through virtual functions allows the
GraphandEvaluatorto treat nodes uniformly Numeric Tconcepts ensure at compile time that the underlying data type supports all necessary math operations
- runtime polymorphism through virtual functions allows the
- role: a puny, strongly-typed handle to a node
- responsibilities:
- wraps a
std::size_tindex to prevent misuse
- wraps a
- abstraction / design choice:
- strong typing for primitive obsession bugs
- role: a high-level wrapper that enables the domain specific language
- responsibilities:
- wraps a
NodeIDand a reference to theGraph - enables operator overloading
- wraps a
- abstraction / design choice:
- facade pattern hides the complexity of graph construction
- acts as a view; doesn't own the data, preventing ownership cycles
- role: stateless functors defining the mathematical logic
- responsibilities:
- provide the actual computation
operator()(T x, T y) - deliver visualization metadata
- provide the actual computation
- abstraction / design choice:
- static polymprohism and functors separate the operation logic from the
Nodestructure, so the genericBinaryNode<T, Op>can be reused for addition, subtraction, etc. preventing code redundancy
- static polymprohism and functors separate the operation logic from the
- role: a custom numeric type for FAD
- responsibilities:
- stores a value pair
{value, d} - implements the chain rule <3 via operator overloading
- stores a value pair
- abstraction / design choice:
- thanks to the swappable domain (
Graph<T>template), one can simply changeTfromdoubletoDualand the engine upgrades from a calculator to a differentiatior without changing a single line of code code
- thanks to the swappable domain (
- role: the execution context that delegates how compuation happens to a
Policy - responsibilities:
- provides a consistent API to the user
- abstraction / design choice:
- the compile-time strategy pattern hosts swappable algorithms by changing a template argument
- role: concrete implementations of an evaluation strategy
- responsibilities:
- performs the actual graph traversal (topological sort or recursive DFS)
- manages the
Context(variable lookups)
- abstraction / design choice:
- separation of concerns for adding new evaluation methods wihtout having to meddle with the
Graphcode
- separation of concerns for adding new evaluation methods wihtout having to meddle with the
- C++20 compliant compiler (GCC 10+, Clang 10+, or MSVC 19.28+)
- CMake 3.20+
- [optional] Graphviz for PNG rendering
- clone or download the repository to your local machine
- open the terminal and navigate to the project's root directory
- run the following commands:
mkdir build && cd build
cmake ..
make
./cg_main