Compare commits

...

10 Commits

Author SHA1 Message Date
Aryadev Chavali
bd96755654 Added README 2024-07-27 02:25:52 +01:00
Aryadev Chavali
564bac363f Clean up and use the right initial fraction for CW tree 2024-07-27 02:20:31 +01:00
Aryadev Chavali
76d407ae1b Added timer to automatically do iterations
Looks very cool.
2024-07-27 01:44:07 +01:00
Aryadev Chavali
f1b878c991 Left and right fractions are now drawn at the top with counter 2024-07-27 01:35:43 +01:00
Aryadev Chavali
feabcf24a0 Draw the three iteration nodes in special colours
green for the centre, blue for the new nodes generated.
2024-07-27 01:25:27 +01:00
Aryadev Chavali
1c06a4b613 iterate now returns a tuple of the three fractions worked on
The left, currently processed (centre) and right fractions are
returned by iterate.  This allows us to see exactly what fractions
have been generated/worked on in every iteration.
2024-07-27 01:23:31 +01:00
Aryadev Chavali
32314708d5 Made nested Bounds structure to encapsulate work with bounds
Looks a bit nicer, probably increases padding but whatever.
2024-07-26 21:58:53 +01:00
Aryadev Chavali
bb6ded706f Move all functions and state into one struct
One God structure which will hold all the necessary state, ensuring I
only need to compute stuff like bounds at most once, better than
computing it on every draw (which is WAY slower at scale).
2024-07-26 21:44:08 +01:00
Aryadev Chavali
ca7931e0c2 NodeAllocator constructor now reserves space in vector
This is different to vec{capacity}, which would adjust vec::size.
This adjusts vec::capacity instead, which is what I want.
2024-07-26 21:43:19 +01:00
Aryadev Chavali
7720544e07 NodeAllocator has default constructor
capacity at 0 by default, because it screws with usage of size later.
2024-07-26 21:25:40 +01:00
4 changed files with 176 additions and 77 deletions

34
README.org Normal file
View File

@@ -0,0 +1,34 @@
#+title: Calkin-Wilf trees
#+author: Aryadev Chavali
#+date: 2024-07-27
A graphical visualisation of
[[https://en.wikipedia.org/wiki/Calkin%E2%80%93Wilf_tree][Calkin-Wilf
trees]].
Currently visualises it using a self adjusting number line, from the
smallest fraction to the largest fraction generated. Both are always
positive.
The bound fractions are drawn in white, while all other fractions are
in red. On any one iteration (taking any one fraction and generating
its two children fractions), the generated fractions are in blue while
the generator fraction is in green.
This was done just for fun really, but it's quite fun to see it
generate a dense number line over many iterations.
* TODOs
** TODO Multithreading
Currently single threaded. A multithreaded implementation could have
multiple nodes generated at once, which would speed up the
implementation.
Might need to study my current implementation to see if it could be
done better.
** TODO Prettify code base
It's a big blob of code currently in the graphics portion. Not very
pretty but it gets the job done. Try modularisation.
** TODO Tree visualisation
Instead of a number line, how about visualising the actual tree at
work as a graph of nodes? Maybe colouring nodes based on where it is
on the number line.

View File

@@ -16,6 +16,7 @@
#include "./numerics.hpp"
#include <chrono>
#include <cmath>
#include <cstdio>
#include <iostream>
@@ -32,106 +33,163 @@
#define LINE_TOP (7 * HEIGHT / 16)
#define LINE_BOTTOM (9 * HEIGHT / 16)
Node rightmost_node(const NodeAllocator &allocator)
std::pair<std::string, int> get_fraction_drawable(Fraction f)
{
auto node = allocator.getVal(0);
while (node.right.has_value())
node = allocator.getVal(node.right.value());
return node;
}
Node leftmost_node(const NodeAllocator &allocator)
{
auto node = allocator.getVal(0);
while (node.left.has_value())
node = allocator.getVal(node.left.value());
return node;
}
constexpr word_t clamp_to_width(long double value, long double min,
long double max)
{
// translate v in [min, max] -> v' in [0, WIDTH]
// [min, max] -> [0, max-min] -> [0, WIDTH]
return WIDTH / (max - min) * (value - min);
std::string s{to_string(f)};
int width = MeasureText(s.c_str(), FONT_SIZE);
return std::make_pair(s, width);
}
void draw_fraction(Fraction f, word_t x, word_t y)
{
std::string s{to_string(f)};
std::string s;
int width;
std::tie(s, width) = get_fraction_drawable(f);
// Centered at (x, y)
int width = MeasureText(s.c_str(), FONT_SIZE);
DrawText(s.c_str(), x - width / 2, y - FONT_SIZE, FONT_SIZE, WHITE);
}
void draw_node_number_line(const NodeAllocator &allocator, long double lower,
long double upper)
struct State
{
std::stack<Node> stack;
stack.push(allocator.getVal(0));
while (!stack.empty())
NodeAllocator allocator;
std::queue<word_t> iteration_queue;
word_t root;
struct Bounds
{
Node n = stack.top();
stack.pop();
word_t x = clamp_to_width(n.value.norm, lower, upper);
DrawLine(x, LINE_TOP, x, LINE_BOTTOM, RED);
if (n.left.has_value())
stack.push(allocator.getVal(n.left.value()));
if (n.right.has_value())
stack.push(allocator.getVal(n.right.value()));
Node leftmost, rightmost;
long double lower, upper;
} bounds;
struct Iteration
{
Fraction left, centre, right;
} iteration;
State(const Fraction start) : allocator{256}
{
root = allocator.alloc(start);
iteration_queue.push(root);
bounds.leftmost = allocator.getVal(root);
bounds.rightmost = allocator.getVal(root);
compute_bounds();
}
}
void draw_number_line(const NodeAllocator &allocator)
{
// Draw a general guide number line
DrawLine(0, HEIGHT / 2, WIDTH, HEIGHT / 2, WHITE);
// Figure out the leftmost and rightmost nodes for bounds
const auto right = rightmost_node(allocator);
const auto left = leftmost_node(allocator);
const auto upper_bound = std::ceill(right.value.norm);
const auto lower_bound = std::floorl(left.value.norm);
void do_iteration(void)
{
std::tie(iteration.left, iteration.centre, iteration.right) =
iterate(iteration_queue, allocator);
compute_bound_nodes();
compute_bounds();
}
// Draw all the nodes
draw_node_number_line(allocator, lower_bound, upper_bound);
void compute_bounds()
{
bounds.lower = std::floorl(bounds.leftmost.value.norm);
bounds.upper = std::ceill(bounds.rightmost.value.norm);
}
// Draw the bounds, with their values, in white
word_t lower_x = clamp_to_width(left.value.norm, lower_bound, upper_bound);
word_t upper_x = clamp_to_width(right.value.norm, lower_bound, upper_bound);
draw_fraction(left.value, lower_x, 3 * HEIGHT / 8);
draw_fraction(right.value, upper_x, 3 * HEIGHT / 8);
DrawLine(lower_x, LINE_TOP, lower_x, LINE_BOTTOM, WHITE);
DrawLine(upper_x, LINE_TOP, upper_x, LINE_BOTTOM, WHITE);
}
void compute_bound_nodes()
{
bounds.leftmost = allocator.getVal(0);
while (bounds.leftmost.left.has_value())
bounds.leftmost = allocator.getVal(bounds.leftmost.left.value());
bounds.rightmost = allocator.getVal(0);
while (bounds.rightmost.right.has_value())
bounds.rightmost = allocator.getVal(bounds.rightmost.right.value());
}
constexpr word_t clamp_to_width(long double value)
{
return (WIDTH / (bounds.upper - bounds.lower)) * (value - bounds.lower);
}
void draw_bounds()
{
word_t lower_x = clamp_to_width(bounds.leftmost.value.norm);
word_t upper_x = clamp_to_width(bounds.rightmost.value.norm);
DrawLine(lower_x, LINE_TOP, lower_x, LINE_BOTTOM, WHITE);
DrawLine(upper_x, LINE_TOP, upper_x, LINE_BOTTOM, WHITE);
}
void draw_nodes()
{
std::stack<Node> stack;
stack.push(allocator.getVal(0));
while (!stack.empty())
{
Node n = stack.top();
stack.pop();
word_t x = clamp_to_width(n.value.norm);
DrawLine(x, LINE_TOP, x, LINE_BOTTOM, RED);
if (n.left.has_value())
stack.push(allocator.getVal(n.left.value()));
if (n.right.has_value())
stack.push(allocator.getVal(n.right.value()));
}
}
void draw_iteration_nodes()
{
word_t x_left = clamp_to_width(iteration.left.norm);
word_t x_centre = clamp_to_width(iteration.centre.norm);
word_t x_right = clamp_to_width(iteration.right.norm);
DrawLine(x_left, LINE_TOP, x_left, LINE_BOTTOM, BLUE);
DrawLine(x_right, LINE_TOP, x_right, LINE_BOTTOM, BLUE);
DrawLine(x_centre, LINE_TOP, x_centre, LINE_BOTTOM, GREEN);
}
};
using Clock = std::chrono::steady_clock;
using Ms = std::chrono::milliseconds;
int main(void)
{
// Setup CW tree
NodeAllocator allocator{0};
std::queue<word_t> to_iterate;
Fraction best_frac{1, 2};
word_t root = allocator.alloc({best_frac});
to_iterate.push(root);
// Setup state
State state{{1, 1}};
// Setup meta text (counter, iterations, etc)
word_t count = 1, prev_count = 0;
std::stringstream format_stream;
std::string format_str;
word_t format_str_width = 0;
Fraction previous_leftmost, previous_rightmost;
// Setup timer
auto time_current = Clock::now();
auto time_previous = time_current;
constexpr auto time_delta = 1;
InitWindow(WIDTH, HEIGHT, "Calkin-Wilf Tree");
while (!WindowShouldClose())
{
if (IsKeyPressed(KEY_SPACE))
// timer logic
time_current = Clock::now();
if (std::chrono::duration_cast<Ms>(time_current - time_previous).count() >=
time_delta)
{
iterate(to_iterate, allocator);
time_previous = time_current;
state.do_iteration();
count += 2;
}
// Input logic
if (IsKeyPressed(KEY_SPACE))
{
state.do_iteration();
count += 2;
}
// Meta text logic
if (prev_count != count)
{
prev_count = count;
format_stream << "Count=" << count << "\n\n";
format_stream << "Iterations=" << (count - 1) / 2;
format_stream << "Count=" << count << "\n\n"
<< "Iterations=" << (count - 1) / 2 << "\n\n"
<< "Lower=" << to_string(state.bounds.leftmost.value)
<< "\n\n"
<< "Upper=" << to_string(state.bounds.rightmost.value);
format_str = format_stream.str();
format_stream.str("");
format_str_width = MeasureText(format_str.c_str(), FONT_SIZE * 2);
@@ -139,9 +197,12 @@ int main(void)
ClearBackground(BLACK);
BeginDrawing();
draw_number_line(allocator);
DrawText(format_str.c_str(), WIDTH / 2 - format_str_width / 2,
LINE_TOP - HEIGHT / 4, FONT_SIZE * 2, WHITE);
DrawLine(0, HEIGHT / 2, WIDTH, HEIGHT / 2, WHITE);
state.draw_nodes();
state.draw_bounds();
state.draw_iteration_nodes();
DrawText(format_str.c_str(), WIDTH / 2 - format_str_width / 2, HEIGHT / 8,
FONT_SIZE, WHITE);
EndDrawing();
}
CloseWindow();

View File

@@ -48,8 +48,9 @@ Node::Node(Fraction val, index_t left, index_t right)
{
}
NodeAllocator::NodeAllocator(word_t capacity) : vec{capacity}
NodeAllocator::NodeAllocator(word_t capacity)
{
vec.reserve(capacity);
}
word_t NodeAllocator::alloc(Node n)
@@ -85,7 +86,8 @@ word_t gcd(word_t a, word_t b)
return a;
}
Fraction iterate(std::queue<word_t> &queue, NodeAllocator &allocator)
std::tuple<Fraction, Fraction, Fraction> iterate(std::queue<word_t> &queue,
NodeAllocator &allocator)
{
if (queue.empty())
return {};
@@ -104,10 +106,10 @@ Fraction iterate(std::queue<word_t> &queue, NodeAllocator &allocator)
queue.pop();
queue.push(allocator.getVal(index).left.value());
queue.push(allocator.getVal(index).right.value());
node = allocator.getVal(index);
Fraction best = MAX(node.value, allocator.getVal(node.left.value()).value);
best = MAX(best, allocator.getVal(node.right.value()).value);
return best;
node = allocator.getVal(index);
// NOTE: We can be assured that left and right DO have values
return std::tuple(allocator.getVal(node.left.value()).value, node.value,
allocator.getVal(node.right.value()).value);
}
std::string to_string(const Fraction &f)

View File

@@ -22,6 +22,7 @@
#include <optional>
#include <queue>
#include <string>
#include <tuple>
#include <vector>
#define MIN(A, B) ((A) < (B) ? (A) : (B))
@@ -52,14 +53,15 @@ struct NodeAllocator
{
std::vector<Node> vec;
NodeAllocator(word_t capacity);
NodeAllocator(word_t capacity = 0);
word_t alloc(Node n);
Node getVal(word_t n) const;
Node &getRef(word_t n);
};
word_t gcd(word_t a, word_t b);
Fraction iterate(std::queue<word_t> &queue, NodeAllocator &allocator);
std::tuple<Fraction, Fraction, Fraction> iterate(std::queue<word_t> &queue,
NodeAllocator &allocator);
std::string to_string(const Fraction &);
std::string to_string(const NodeAllocator &, const index_t, int depth = 1);