Ensim3
Engine Simulator 3, or ensim3, is my third attempt at modelling an open source internal combustion engine simulator. Built on graph theory fundamentals (Directed Cyclic Graphs and such), ensim3 executes node pairs connected by edge breadth-first as an isentropic flow transaction. Audio is sampled in the engine collector and convoluted with an impulse reverb.
This video showcases some of an inline 3’s intrinsic gas properties - air fuel mixture, when unburned during an engine overrun, pumps a gas mixture of greater molar mass into the collector, causing a pressure fluctuation phenomena known as overrun burble. Modifying exhaust runner length changes pulse arrival time, which changes the sound of the burble.
To accurately capture burble, and avoid unequal flow transactions, Directed Cyclic Graphs (DCG) traverse breadth first. Seen below, an inline 8 engine hypothetical execution with plenum intake left, and four exhaust system, ejecting to the atmosphere right, with a turbo charger making use of latent heat and pressure of each exhaust system:
While ensim3 does not support feedback mechanisms like turbo chargers yet, the video above exemplifies that a DCG framework can.
The workings for this discussion rely on shared pointers, though clever use of unique pointers can offer a nice 30% performance boost. With a widget base class, each node is connected together by user input. Shared or weak pointers are automatically deduced, and each node can at runtime be swapped for static volume (plenum, runner, exhaust, etc), and/or dynamic volume (a piston). The conventional flow math, directed by isentropic choked flow, moves gas mass left to right, from widget to widget.
A node (as a parent) holds shared or weak children (next):
template <typename T>
using non_unique_ptr = variant<shared_ptr<T>, weak_ptr<T>>;
struct node_t : enable_shared_from_this<node_t>
{
unique_ptr<widget_t> widget = {};
list<non_unique_ptr<node_t>> next = {};
}
...
BFS iteration as a public member function serves to operate on the parent (finding a node, rendering a node, etc), and serves to operate on parent to child (rendering lines between nodes, flowing from node to node, etc):
...
using handle_node = function<shared_ptr<node_t>(const shared_ptr<node_t>&)>;
using handle_edge = function<shared_ptr<node_t>(const shared_ptr<node_t>&, const shared_ptr<node_t>&)>;
shared_ptr<node_t> node_t::iterate(const handle_node& handle_node, const handle_edge& handle_edge)
{
queue<shared_ptr<node_t>> q;
q.push(shared_from_this());
while(!q.empty())
{
shared_ptr<node_t> parent = q.front();
q.pop();
if(handle_node(parent))
{
return parent;
}
for(const non_unique_ptr<node_t>& node : parent->nodes)
{
if(holds_alternative<shared_ptr<node_t>>(node))
{
shared_ptr<node_t> child = get<shared_ptr<node_t>>(node);
q.push(child);
handle_edge(parent, child);
}
else
if(shared_ptr<node_t> child = get<weak_ptr<node_t>>(node).lock())
{
handle_edge(parent, child);
}
}
}
return nullptr;
}
...
Where utilization might be defined as:
...
graph->iterate(
[](const shared_ptr<node_t>& parent)
{
parent->widget->do_work();
return nullptr;
},
[](const shared_ptr<node_t>& parent, const shared_ptr<node_t>& child)
{
parent->widget->flow(child->widget);
return nullptr;
}
);
...
do_work() performs mechanical and electrical work on a widget (adiabatic compression, spark plug ignition), and flow() performs the transfer of moles, via isentropic flow, from one node to the next.
Ensim3’s procedural audio improves upon Ensim2’s procedural audio, effectively capturing nuances like engine burble. Ensim3 currently relies on impulse convolutions to capture exhaust reverb. Impulse reverbs capture room temperature acoustics, and do not account for the fluid dynamics present in collector exhaust pipes, especially that of reflected pressure waves and the varying speed of sound impacted by gas density and gas temperature. Nevertheless, one dimensional computational fluid dynamics (1DCFD) solves such a problem, and a fourth attempt, aptly named ensim4 in the future, will solve this problem. Though, ensim3 is still very much worth the read for anyone curious on dependency free inline engine modeling.