Skip to content

Commit

Permalink
collatz: add new visualizations with greater exponents
Browse files Browse the repository at this point in the history
  • Loading branch information
attilammagyar committed Apr 9, 2024
1 parent dd7e331 commit 30b6586
Show file tree
Hide file tree
Showing 24 changed files with 699 additions and 84 deletions.
337 changes: 311 additions & 26 deletions collatz/README.md

Large diffs are not rendered by default.

82 changes: 54 additions & 28 deletions collatz/collatz-vis.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,57 +65,62 @@
INFINITY = complex("inf+infj")


def func_C(z):
def func_C(z, p):
c = INFINITY
s = INFINITY
piz = cpi * z / 2

try:
c = ccos(piz) ** 2
c = ccos(piz) ** p

except (OverflowError, ValueError):
pass

try:
s = csin(piz) ** 2
s = csin(piz) ** p

except (OverflowError, ValueError):
pass

return (z * c) / 2 + ((3 * z + 1) / 2) * s


def func_T(z):
def func_T(z, p):
s = INFINITY
p = INFINITY
pw = INFINITY

try:
s = csin(cpi * z / 2) ** 2
p = 3 ** s
s = csin(cpi * z / 2) ** p
pw = 3 ** s

except (OverflowError, ValueError, ZeroDivisionError):
pass

return (p * z + s) / 2
return (pw * z + s) / 2


def func_F(z):
def func_F(z, p):
c = INFINITY
piz = cpi * z

try:
c = ccos(piz)
c = ccos(piz) ** p

except OverflowError:
pass

# r = 1

# for i in range(p):
# r *= c

return 0.75 * ((2 * z + 1) / (c + 2)) - 0.25


FUNCTIONS = {
"C": (func_C, "f(z) = (z/2) * cos(z*pi/2)^2 + ((3*z+1)/2) * sin(z*pi/2)^2"),
"T": (func_T, "T(z) = (3^(sin(z*pi/2)^2) + sin(z*pi/2)^2) / 2"),
"F": (func_F, "F(z) = (3/4) * (2*z+1) / (cos(pi*z)+2) - 1/4"),
"C": (func_C, lambda m: 2 * m + 2, "f(z) = (z/2) * cos(z*pi/2)^{p} + ((3*z+1)/2) * sin(z*pi/2)^{p}"),
"T": (func_T, lambda m: 2 * m + 2, "T(z) = (3^(sin(z*pi/2)^{p}) + sin(z*pi/2)^{p}) / 2"),
"F": (func_F, lambda m: 2 * m + 1, "F(z) = (3/4) * (2*z+1) / (cos(pi*z)^{p}+2) - 1/4"),
}


Expand All @@ -128,14 +133,18 @@ def main(argv):
conv_threshold, esc_threshold = float(argv[6]), float(argv[7])
left, top = float(argv[8]), float(argv[9])
right, bottom = float(argv[10]), float(argv[11])
f, f_str = FUNCTIONS[func]
m = int(argv[12]) if len(argv) > 12 else 0
f, f_pow, f_str = FUNCTIONS[func]

if width < 1 or height < 1:
raise ValueError("width and height must be greater than 0")

if m < 0:
raise ValueError("m must be a non-negative integer")

except:
print(
"Usage: {} conv.png stop.png C|T|F width height conv_threshold esc_threshold left top right bottom\n".format(
"Usage: {} conv.png stop.png C|T|F width height conv_threshold esc_threshold left top right bottom [m]\n".format(
os.path.basename(argv[0])
),
file=sys.stderr
Expand Down Expand Up @@ -176,6 +185,9 @@ def main(argv):
max_stop = 0
min_stop = ITERS + 1
avg_stop = []
p = f_pow(m)

f_str = f_str.format(p=p)

lines = []

Expand All @@ -202,7 +214,7 @@ def main(argv):
z0 = x + y * 1j
z_str = "({x}+i*{y})".format(x=x, y=y)

result, steps, stop, cycle, seq = repeat(f, z0, ITERS, conv_threshold, esc_threshold)
result, steps, stop, cycle, seq = repeat(f, p, z0, ITERS, conv_threshold, esc_threshold)
line.append((result, steps, stop))

if stop is not None:
Expand Down Expand Up @@ -358,31 +370,45 @@ def main(argv):
return 0


def repeat(f, z, iters, conv_threshold, esc_threshold):
def repeat(f, p, z, iters, conv_threshold, esc_threshold):
result = None
stop = None
seen = {z}
seq = [z]
cycle = []
cycle_threshold = conv_threshold * 10
z0 = abs(z)

try:
az0 = abs(z)

except OverflowError:
return ESCAPE, 1, stop, cycle, seq

for steps in range(ITERS):
z = f(z)
z = f(z, p)
seq.append(z)
az = abs(z)

if stop is None and abs(z) < z0:
try:
az = abs(z)

except OverflowError:
return ESCAPE, steps + 1, stop, cycle, seq

if stop is None and az < az0:
stop = steps + 1

if z in seen or min(abs(z - s) for s in seen) < conv_threshold:
cycle = build_cycle(f, z, cycle_threshold)
try:
is_cycle = z in seen or min(abs(z - s) for s in seen) < conv_threshold

except OverflowError:
return ESCAPE, steps + 1, stop, cycle, seq

if is_cycle:
cycle = build_cycle(f, p, z, cycle_threshold)
result = analyze_cycle(cycle, cycle_threshold)
break

else:
az = abs(z)

if az > esc_threshold or isnan(az):
result = ESCAPE
cycle.append(z)
Expand All @@ -393,13 +419,13 @@ def repeat(f, z, iters, conv_threshold, esc_threshold):
return result, steps + 1, stop, cycle, seq


def build_cycle(f, z, threshold):
def build_cycle(f, p, z, threshold):
cycle = [z]
z = f(z)
z = f(z, p)

while (min(abs(z - c) for c in cycle) >= threshold) and (len(cycle) < CYCLE_MAX):
cycle.append(z)
z = f(z)
z = f(z, p)

return cycle

Expand Down
30 changes: 18 additions & 12 deletions collatz/gen-img.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,41 @@ gen_img()
local top="$4"
local right="$5"
local bottom="$6"
local m="$7"

local l=""
local p=$(
printf "%s" "$w $h $conv $esc $left $top $right $bottom" \
printf "%s" "$w $h $conv $esc $left $top $right $bottom $m" \
| sed "s/[ .]/_/g"
)

for f in F C T
do
l=$(echo "$f" | tr [[:upper:]] [[:lower:]])

python collatz-vis.py \
python3 collatz-vis.py \
"$img_dir/${l}_conv_$p.png" \
"$img_dir/${l}_stop_$p.png" \
"$f" \
"$w" "$h" \
"$conv" "$esc" \
"$left" "$top" "$right" "$bottom" \
"$m" \
| gzip -9 -c - > "$log_dir/${l}_log_$p.txt.gz"
done
}

gen_img 2880 1620 95.0 2.8125 105.0 -2.8125
gen_img 2880 1620 1095.0 2.8125 1105.0 -2.8125
gen_img 2880 1620 9095.0 2.8125 9105.0 -2.8125
gen_img 5760 3240 -20.0 11.25 20.0 -11.25
gen_img 5760 3240 -10.0 5.625 10.0 -5.625
gen_img 5760 3240 -5.0 2.8125 5.0 -2.8125
gen_img 5760 3240 -2.5 1.40625 2.5 -1.40625
gen_img 5760 3240 0.25 0.703125 2.75 -0.703125
gen_img 5760 3240 0.875 0.3515625 2.125 -0.3515625
gen_img 5760 3240 1.1875 0.17578125 1.8125 -0.17578125
gen_img 2880 1620 95.0 2.8125 105.0 -2.8125 0
gen_img 2880 1620 1095.0 2.8125 1105.0 -2.8125 0
gen_img 2880 1620 9095.0 2.8125 9105.0 -2.8125 0
gen_img 5760 3240 -20.0 11.25 20.0 -11.25 0
gen_img 5760 3240 -10.0 5.625 10.0 -5.625 0
gen_img 5760 3240 -5.0 2.8125 5.0 -2.8125 0
gen_img 5760 3240 -2.5 1.40625 2.5 -1.40625 0
gen_img 5760 3240 0.25 0.703125 2.75 -0.703125 0
gen_img 5760 3240 0.875 0.3515625 2.125 -0.3515625 0
gen_img 5760 3240 1.1875 0.17578125 1.8125 -0.17578125 0

gen_img 2880 1620 -5.0 2.8125 5.0 -2.8125 5
gen_img 2880 1620 0.25 0.703125 2.75 -0.703125 5
gen_img 2880 1620 1.1875 0.17578125 1.8125 -0.17578125 5
49 changes: 31 additions & 18 deletions collatz/generate_image_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
RAW_URL = "https://github.com/attilammagyar/toys/raw/gh-pages/collatz"
DESCRIPTION_INDENT = " " * 8
TITLES = {
"stop": "Stopping Times\n\nMaximum iterations: $500$",
"conv": "Convergence\n\nMaximum iterations: $500$, convergence threshold: $10^{-9}$, escape threshold:\n$10^{50}$"
"stop": "Stopping Times ({m})\n\nMaximum iterations: $500$",
"conv": "Convergence ({m})\n\nMaximum iterations: $500$, convergence threshold: $10^{{-9}}$, escape threshold:\n$10^{{50}}$"
}
FUNCS = {"f": "F", "c": "f", "t": "T"}

Expand Down Expand Up @@ -78,6 +78,13 @@ def main(argv):

def store(imgs, file_name, img_type, func_name, left_top, right_bottom, description):
if file_name != "" and len(description) > 0:
parts = file_name.split("_")

if len(parts) >= 15:
m = parts[14].split(".", 1)[0]
else:
m = "0"

markdown = "".join(
[
f" * {FUNCS[func_name]}: [`{file_name}`]({RAW_URL}/{file_name})\n",
Expand All @@ -89,6 +96,7 @@ def store(imgs, file_name, img_type, func_name, left_top, right_bottom, descript

(
imgs
.setdefault(m, {})
.setdefault(img_type, {})
.setdefault(region, {})
.setdefault(func_name, markdown)
Expand All @@ -100,29 +108,34 @@ def print_list(imgs):
toc = []
i = 0

for img_type in sorted(imgs.keys()):
title = TITLES.get(img_type, img_type)
toc_entry = title.split("\n", 1)[0]
toc.append(f" * [{toc_entry}](#{img_type})")
for m in sorted(imgs.keys(), key=int):
for img_type in sorted(imgs[m]):
title = TITLES.get(img_type, img_type)
toc_entry = title.split("\n", 1)[0]

print(f"<a name=\"{img_type}\"></a>")
print("")
print(f"### {title}")
print("")
title = title.format(m=f"$m = {m}$")
toc_entry = toc_entry.format(m=f"m = {m}")

for region in sorted(imgs[img_type].keys(), key=parse_region):
i += 1
title = f"Region: {region}"
toc_entry = title.split("\n", 1)[0]
toc.append(f" * [{toc_entry}](#region-{i})")
toc.append(f" * [{toc_entry}](#{img_type}-{m})")

print(f"<a name=\"region-{i}\"></a>")
print(f"<a name=\"{img_type}-{m}\"></a>")
print("")
print(f"#### {title}")
print("")

for func_name in ("f", "c", "t"):
print(imgs[img_type][region][func_name])
for region in sorted(imgs[m][img_type].keys(), key=parse_region):
i += 1
title = f"Region: {region}"
toc_entry = title.split("\n", 1)[0]
toc.append(f" * [{toc_entry}](#region-{i})")

print(f"<a name=\"region-{i}\"></a>")
print("")
print(f"##### {title}")
print("")

for func_name in ("f", "c", "t"):
print(imgs[m][img_type][region][func_name])

print("\n".join(toc))

Expand Down
Loading

0 comments on commit 30b6586

Please sign in to comment.