150 lines
4.7 KiB
Text
150 lines
4.7 KiB
Text
namespace tf {
|
|
|
|
/** @page flipcoins Flip Coins
|
|
|
|
We study dynamic control flow of non-determinism using conditional tasking.
|
|
Non-deterministic control flow is a fundamental building block in many optimization and simulation algorithms
|
|
that rely on stochastic convergence rules or probabilistic pruning.
|
|
|
|
@tableofcontents
|
|
|
|
@section FlipCoinsProblemFormulation Problem Formulation
|
|
|
|
We have a fair binary coin and want to simulate its tosses.
|
|
We flip the coin for five times.
|
|
Apparently, the probability for the result to be all heads is 1/32.
|
|
It is equivalently to say the expected number we need to toss for obtaining five heads is 32.
|
|
|
|
<!-- @image html images/flipcoins_1.svg width=80% -->
|
|
@dotfile images/flipcoins_1.dot
|
|
|
|
@section FlipCoinsProbabilistic Probabilistic Conditions
|
|
|
|
We use condition tasks to simulate the five coin tosses.
|
|
We create five condition tasks each returning a random binary number.
|
|
If the return is zero (head toss), the execution moves to the next condition task;
|
|
or it (tail toss) goes back to the first condition task to start over the simulation.
|
|
|
|
@code{.cpp}
|
|
#include <taskflow/taskflow.hpp>
|
|
|
|
int main() {
|
|
|
|
tf::Taskflow taskflow;
|
|
tf::Executor executor;
|
|
|
|
size_t rounds = 10000;
|
|
size_t tosses = 0;
|
|
size_t total_tosses = 0;
|
|
double average_tosses = 0.0;
|
|
|
|
tf::Task A = taskflow.emplace([&](){ tosses = 0; })
|
|
.name("init");
|
|
|
|
tf::Task B = taskflow.emplace([&](){ ++tosses; return std::rand()%2; })
|
|
.name("flip-coin-1");
|
|
tf::Task C = taskflow.emplace([&](){ return std::rand()%2; })
|
|
.name("flip-coin-2");
|
|
tf::Task D = taskflow.emplace([&](){ return std::rand()%2; })
|
|
.name("flip-coin-3");
|
|
tf::Task E = taskflow.emplace([&](){ return std::rand()%2; })
|
|
.name("flip-coin-4");
|
|
tf::Task F = taskflow.emplace([&](){ return std::rand()%2; })
|
|
.name("flip-coin-5");
|
|
|
|
// reach the target; record the number of tosses
|
|
tf::Task G = taskflow.emplace([&](){ total_tosses += tosses; })
|
|
.name("stop");
|
|
|
|
A.precede(B);
|
|
|
|
// five probabilistic conditions
|
|
B.precede(C, B);
|
|
C.precede(D, B);
|
|
D.precede(E, B);
|
|
E.precede(F, B);
|
|
F.precede(G, B);
|
|
|
|
// repeat the flip-coin simulation by rounds times
|
|
executor.run_n(taskflow, rounds).wait();
|
|
|
|
// calculate the expected number of tosses
|
|
average_tosses = total_tosses / (double)rounds;
|
|
|
|
assert(std::fabs(average_tosses-32.0)<1.0);
|
|
|
|
return 0;
|
|
}
|
|
@endcode
|
|
|
|
Running the taskflow by a fair number of times, the average tosses we have is close to 32.
|
|
The taskflow diagram is depicted below.
|
|
|
|
<!-- @image html images/flipcoins_2.svg width=100% -->
|
|
@dotfile images/flipcoins_2.dot
|
|
|
|
Although the execution of this taskflow is non-deterministic,
|
|
its control flow can expand to a tree of tasks based on our scheduling rule for conditional tasking
|
|
(see @ref ConditionalTasking).
|
|
Each path from the root to a leaf represents a result of five heads,
|
|
and none of them can overlap at the same time (no task race).
|
|
You must follow the same rule when creating a probabilistic framework using conditional tasking.
|
|
|
|
@section FlipCoinsTernaryCoins Ternary Coins
|
|
|
|
We can extend the binary coin example to a ternary case.
|
|
Each condition task has one successor going back to the beginning and two successors moving to the next task.
|
|
The expected number of tosses to reach
|
|
five identical results is 3*3*3*3*3 = 243.
|
|
|
|
@code{.cpp}
|
|
tf::Task A = taskflow.emplace( [&](){ tosses = 0; } )
|
|
.name("init");
|
|
|
|
// start over the flip again
|
|
tf::Task B = taskflow.emplace([&](){ ++tosses; return std::rand()%3; })
|
|
.name("flip-coin-1");
|
|
tf::Task C = taskflow.emplace([&](){ return std::rand()%3; })
|
|
.name("flip-coin-2");
|
|
tf::Task D = taskflow.emplace([&](){ return std::rand()%3; })
|
|
.name("flip-coin-3");
|
|
tf::Task E = taskflow.emplace([&](){ return std::rand()%3; })
|
|
.name("flip-coin-4");
|
|
tf::Task F = taskflow.emplace([&](){ return std::rand()%3; })
|
|
.name("flip-coin-5");
|
|
|
|
// reach the target; record the number of tosses
|
|
tf::Task G = taskflow.emplace([&](){ total_tosses += tosses; })
|
|
.name("stop");
|
|
|
|
A.precede(B);
|
|
|
|
// five probabilistic conditions
|
|
B.precede(C, B, B);
|
|
C.precede(D, B, B);
|
|
D.precede(E, B, B);
|
|
E.precede(F, B, B);
|
|
F.precede(G, B, B);
|
|
|
|
// repeat the flip-coin simulation by rounds times
|
|
executor.run_n(taskflow, rounds).wait();
|
|
|
|
// calculate the expected number of tosses
|
|
average_tosses = total_tosses / (double)rounds;
|
|
|
|
assert(std::fabs(average_tosses-243.0)<1.0);
|
|
@endcode
|
|
|
|
<!-- @image html images/flipcoins_3.svg width=100% -->
|
|
@dotfile images/flipcoins_3.dot
|
|
|
|
Similarly, we can extend the probabilistic condition to any degree.
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|