<!-- canonical: https://efficientnewlanguage.org/eml-p/projects/P069-sequence-explorer/ | updated: 2026-10-14 -->

# P069 Sequence explorer

Collatz paths from starts up to a trillion, worked in exact whole numbers, and the start below a limit with the longest path; exact Fibonacci numbers with Cassini's identity checked and the ratio closing in on the golden ratio; and Pascal's triangle, as numbers and as the pattern its odd entries make.

EML-P project `projects/sequence-explorer` in the EML language repo: 2 module(s), entry `main.eml`, terminal UI. There, `eml project run projects/sequence-explorer` runs it and `eml project verify projects/sequence-explorer` replays every session under CPython (two hash seeds) and in the interpreter; the site build replays every session in the interpreter again and publishes a session only if its screen matches.

Built on verified corpus cases: collatz-sequence (https://efficientnewlanguage.org/cases/016-collatz-sequence/), fibonacci-cassini-exact (https://efficientnewlanguage.org/cases/189-fibonacci-cassini-exact/), pascals-triangle (https://efficientnewlanguage.org/cases/057-pascals-triangle/).

## Sessions

### bad-input - interpreter: byte-equal to the golden

Input:

```text
0
x
1
0
abc

2
1
100001

3
2001

4
16

5
33

6
```

Screen:

```text
== Sequence explorer ==
Collatz paths, exact Fibonacci numbers and Pascal's triangle.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 0
Pick a number from 1 to 6.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> x
Pick a number from 1 to 6.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 1
start (1 to 1000000000000)> 0
Type a whole number from 1 to 1000000000000.
start (1 to 1000000000000)> abc
Type a whole number from 1 to 1000000000000.
start (1 to 1000000000000)> 
Cancelled.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 2
up to (2 to 100000)> 1
Type a whole number from 2 to 100000.
up to (2 to 100000)> 100001
Type a whole number from 2 to 100000.
up to (2 to 100000)> 
Cancelled.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 3
n (1 to 2000)> 2001
Type a whole number from 1 to 2000.
n (1 to 2000)> 
Cancelled.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 4
rows (1 to 15)> 16
Type a whole number from 1 to 15.
rows (1 to 15)> 
Cancelled.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 5
rows (1 to 32)> 33
Type a whole number from 1 to 32.
rows (1 to 32)> 
Cancelled.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 6
Bye.
```

### basic - interpreter: byte-equal to the golden

Input:

```text
1
27
1
1410123943
2
10000
3
10
3
1000
4
7
5
16
6
```

Screen:

```text
== Sequence explorer ==
Collatz paths, exact Fibonacci numbers and Pascal's triangle.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 1
start (1 to 1000000000000)> 27
27 82 41 124 62 31 94 47 142 71 214 107 322 161 484 242 121 364 182 91 ... 80 40 20 10 5 16 8 4 2 1
27 reaches 1 in 111 steps, climbing as high as 9232.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 1
start (1 to 1000000000000)> 1410123943
1410123943 4230371830 2115185915 6345557746 3172778873 9518336620 4759168310 2379584155 7138752466 3569376233 10708128700 5354064350 2677032175 8031096526 4015548263 12046644790 6023322395 18069967186 9034983593 27104950780 ... 13 40 20 10 5 16 8 4 2 1
1410123943 reaches 1 in 770 steps, climbing as high as 7125885122794452160.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 2
up to (2 to 100000)> 10000
Up to 10000, the start that takes longest is 6171: 261 steps.
Steps for each start up to 20:
  0 1 7 2 5 8 16 3 19 6 14 9 9 17 17 4 12 20 20 7

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 3
n (1 to 2000)> 10
F(10) = 55
Cassini: F(9) x F(11) - F(10)^2 = 1, as (-1)^10 says.
F(11) / F(10) = 1.618181818181... - the golden ratio is 1.618033988749...

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 3
n (1 to 2000)> 1000
F(1000) = 4346655768693745643568852...3704476137795166849228875, 209 digits
Cassini: F(999) x F(1001) - F(1000)^2 = 1, as (-1)^1000 says.
F(1001) / F(1000) = 1.618033988749... - the golden ratio is 1.618033988749...

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 4
rows (1 to 15)> 7
        1
       1 1
      1 2 1
     1 3 3 1
    1 4 6 4 1
  1 5 10 10 5 1
1 6 15 20 15 6 1

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 5
rows (1 to 32)> 16
               *
              * *
             *   *
            * * * *
           *       *
          * *     * *
         *   *   *   *
        * * * * * * * *
       *               *
      * *             * *
     *   *           *   *
    * * * *         * * * *
   *       *       *       *
  * *     * *     * *     * *
 *   *   *   *   *   *   *   *
* * * * * * * * * * * * * * * *
Each * is an odd number; the even ones are left blank.

1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit
choice> 6
Bye.
```

## Modules

### main.eml

```eml
# P069 sequence explorer: Collatz paths and the longest one below a limit,
# exact Fibonacci numbers with Cassini's identity and the golden ratio, and
# Pascal's triangle - as numbers and as the pattern its odd entries make.
import sequences

def trim(s):
    0 => i
    len(s) => j
    while i < j and s[i] == " ":
        i + 1 => i
    while j > i and s[j - 1] == " ":
        j - 1 => j
    return s[i:j]

def steps_text(k):
    if k == 1:
        return "1 step"
    return str(k) + " steps"

def number(s):
    if s == "" or len(s) > 13:
        return -1
    0 => n
    for c in s:
        if not (c >= "0" and c <= "9"):
            return -1
        n * 10 + int(c) => n
    return n

def ask(prompt, low, high):
    while True:
        trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> ")) => answer
        if answer == "":
            return -1
        number(answer) => n
        if n >= low and n <= high:
            return n
        ("Type a whole number from " + str(low) + " to " + str(high) + ".") ^0

def joined(xs):
    "" => s
    for x in xs:
        if s != "":
            s + " " => s
        s + str(x) => s
    return s

def collatz():
    ask("start", 1, 1000000000000) => n
    if n == -1:
        "Cancelled." ^0
        return
    sequences.collatz(n) => r
    r[0] => path
    len(path) - 1 => steps
    if len(path) <= 40:
        joined(path) ^0
    else:
        (joined(path[0:20]) + " ... " + joined(path[len(path) - 10:len(path)])) ^0
    (str(n) + " reaches 1 in " + steps_text(steps) + ", climbing as high as " + str(r[1]) + ".") ^0

def longest():
    ask("up to", 2, 100000) => limit
    if limit == -1:
        "Cancelled." ^0
        return
    sequences.steps_up_to(limit) => st
    1 => best
    for k in [2:limit]:
        if st[k] > st[best]:
            k => best
    ("Up to " + str(limit) + ", the start that takes longest is " + str(best) + ": " + steps_text(st[best]) + ".") ^0
    "Steps for each start up to 20:" ^0
    [] => xs
    for k in [1:20]:
        if k <= limit:
            xs + [st[k]] => xs
    ("  " + joined(xs)) ^0

def fib():
    ask("n", 1, 2000) => n
    if n == -1:
        "Cancelled." ^0
        return
    sequences.fibonacci(n) => f
    str(f[1]) => s
    if len(s) <= 60:
        ("F(" + str(n) + ") = " + s) ^0
    else:
        ("F(" + str(n) + ") = " + s[0:25] + "..." + s[len(s) - 25:len(s)] + ", " + str(len(s)) + " digits") ^0
    sequences.cassini(n) => c
    ("Cassini: F(" + str(n - 1) + ") x F(" + str(n + 1) + ") - F(" + str(n) + ")^2 = " + str(c) + ", as (-1)^" + str(n) + " says.") ^0
    # F(n+1) / F(n) to 12 places, exactly rounded down
    f[2] => a
    f[1] => b
    long_div(a, b, 12) => q
    ("F(" + str(n + 1) + ") / F(" + str(n) + ") = " + q + "... - the golden ratio is 1.618033988749...") ^0

def long_div(a, b, places):
    # a / b to `places` decimals, rounded down, by long division - for a
    # quotient below 10, as F(n+1) / F(n) always is.
    0 => q
    a => r
    while r >= b:
        r - b => r
        q + 1 => q
    str(q) + "." => s
    for k in [1:places]:
        r * 10 => r
        0 => d
        while r >= b:
            r - b => r
            d + 1 => d
        s + str(d) => s
    return s

def pascal():
    ask("rows", 1, 15) => n
    if n == -1:
        "Cancelled." ^0
        return
    sequences.pascal_rows(n) => rows
    len(joined(rows[n - 1])) => width
    for row in rows:
        joined(row) => s
        "" => pad
        while len(pad) * 2 + len(s) < width:
            pad + " " => pad
        (pad + s) ^0

def pascal_odd():
    ask("rows", 1, 32) => n
    if n == -1:
        "Cancelled." ^0
        return
    sequences.pascal_rows(n) => rows
    for r in [0:n - 1]:
        "" => line
        while len(line) < n - 1 - r:
            line + " " => line
        for k in [0:r]:
            if k > 0:
                line + " " => line
            if rows[r][k] % 2 == 1:
                line + "*" => line
            else:
                line + " " => line
        trim_right(line) ^0
    "Each * is an odd number; the even ones are left blank." ^0

def trim_right(s):
    len(s) => j
    while j > 0 and s[j - 1] == " ":
        j - 1 => j
    return s[0:j]

"== Sequence explorer ==" ^0
"Collatz paths, exact Fibonacci numbers and Pascal's triangle." ^0
True => running
while running:
    "" ^0
    "1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit" ^0
    trim(input("choice> ")) => choice
    if choice == "1":
        collatz()
    elif choice == "2":
        longest()
    elif choice == "3":
        fib()
    elif choice == "4":
        pascal()
    elif choice == "5":
        pascal_odd()
    elif choice == "6":
        False => running
    else:
        "Pick a number from 1 to 6." ^0
"Bye." ^0
```

Python projection of main.eml:

```python
import sequences

def trim(s):
    i = 0
    j = len(s)
    while i < j and s[i] == " ":
        i = i + 1
    while j > i and s[j - 1] == " ":
        j = j - 1
    return s[i:j]

def steps_text(k):
    if k == 1:
        return "1 step"
    return str(k) + " steps"

def number(s):
    if s == "" or len(s) > 13:
        return -1
    n = 0
    for c in s:
        if not (c >= "0" and c <= "9"):
            return -1
        n = n * 10 + int(c)
    return n

def ask(prompt, low, high):
    while True:
        answer = trim(input(prompt + " (" + str(low) + " to " + str(high) + ")> "))
        if answer == "":
            return -1
        n = number(answer)
        if n >= low and n <= high:
            return n
        print("Type a whole number from " + str(low) + " to " + str(high) + ".")

def joined(xs):
    s = ""
    for x in xs:
        if s != "":
            s = s + " "
        s = s + str(x)
    return s

def collatz():
    n = ask("start", 1, 1000000000000)
    if n == -1:
        print("Cancelled.")
        return
    r = sequences.collatz(n)
    path = r[0]
    steps = len(path) - 1
    if len(path) <= 40:
        print(joined(path))
    else:
        print(joined(path[0:20]) + " ... " + joined(path[len(path) - 10:len(path)]))
    print(str(n) + " reaches 1 in " + steps_text(steps) + ", climbing as high as " + str(r[1]) + ".")

def longest():
    limit = ask("up to", 2, 100000)
    if limit == -1:
        print("Cancelled.")
        return
    st = sequences.steps_up_to(limit)
    best = 1
    for k in range(2, limit+1):
        if st[k] > st[best]:
            best = k
    print("Up to " + str(limit) + ", the start that takes longest is " + str(best) + ": " + steps_text(st[best]) + ".")
    print("Steps for each start up to 20:")
    xs = []
    for k in range(1, 21):
        if k <= limit:
            xs = xs + [st[k]]
    print("  " + joined(xs))

def fib():
    n = ask("n", 1, 2000)
    if n == -1:
        print("Cancelled.")
        return
    f = sequences.fibonacci(n)
    s = str(f[1])
    if len(s) <= 60:
        print("F(" + str(n) + ") = " + s)
    else:
        print("F(" + str(n) + ") = " + s[0:25] + "..." + s[len(s) - 25:len(s)] + ", " + str(len(s)) + " digits")
    c = sequences.cassini(n)
    print("Cassini: F(" + str(n - 1) + ") x F(" + str(n + 1) + ") - F(" + str(n) + ")^2 = " + str(c) + ", as (-1)^" + str(n) + " says.")
    a = f[2]
    b = f[1]
    q = long_div(a, b, 12)
    print("F(" + str(n + 1) + ") / F(" + str(n) + ") = " + q + "... - the golden ratio is 1.618033988749...")

def long_div(a, b, places):
    q = 0
    r = a
    while r >= b:
        r = r - b
        q = q + 1
    s = str(q) + "."
    for k in range(1, places+1):
        r = r * 10
        d = 0
        while r >= b:
            r = r - b
            d = d + 1
        s = s + str(d)
    return s

def pascal():
    n = ask("rows", 1, 15)
    if n == -1:
        print("Cancelled.")
        return
    rows = sequences.pascal_rows(n)
    width = len(joined(rows[n - 1]))
    for row in rows:
        s = joined(row)
        pad = ""
        while len(pad) * 2 + len(s) < width:
            pad = pad + " "
        print(pad + s)

def pascal_odd():
    n = ask("rows", 1, 32)
    if n == -1:
        print("Cancelled.")
        return
    rows = sequences.pascal_rows(n)
    for r in range(0, n):
        line = ""
        while len(line) < n - 1 - r:
            line = line + " "
        for k in range(0, r+1):
            if k > 0:
                line = line + " "
            if rows[r][k] % 2 == 1:
                line = line + "*"
            else:
                line = line + " "
        print(trim_right(line))
    print("Each * is an odd number; the even ones are left blank.")

def trim_right(s):
    j = len(s)
    while j > 0 and s[j - 1] == " ":
        j = j - 1
    return s[0:j]

print("== Sequence explorer ==")
print("Collatz paths, exact Fibonacci numbers and Pascal's triangle.")
running = True
while running:
    print("")
    print("1) Collatz path  2) longest Collatz  3) Fibonacci  4) Pascal  5) Pascal's odd numbers  6) quit")
    choice = trim(input("choice> "))
    if choice == "1":
        collatz()
    elif choice == "2":
        longest()
    elif choice == "3":
        fib()
    elif choice == "4":
        pascal()
    elif choice == "5":
        pascal_odd()
    elif choice == "6":
        running = False
    else:
        print("Pick a number from 1 to 6.")
print("Bye.")
```

### sequences.eml

```eml
# P069 sequence explorer - Collatz, Fibonacci and Pascal, in whole numbers
# of any size.

def half(n):
    # n // 2 for an even n >= 0, exactly. The corpus case collatz-sequence
    # writes int(n / 2), which goes through a float and is exact only below
    # 2^53; a Collatz path from a start below 10^12 can climb past that, so
    # the halving is done by long division instead.
    0 => q
    n => a
    while a >= 2:
        2 => m
        1 => k
        while m + m <= a:
            m + m => m
            k + k => k
        a - m => a
        q + k => q
    return q

def collatz(n):
    # The path from n down to 1: halve an even number, and take 3n + 1 for
    # an odd one. Returns [path, highest value].
    [n] => path
    n => top
    while n != 1:
        if n % 2 == 0:
            half(n) => n
        else:
            3 * n + 1 => n
        path + [n] => path
        if n > top:
            n => top
    return [path, top]

def steps_up_to(limit):
    # How many steps each start from 1 to limit takes to reach 1, using the
    # answers already found: a path is followed only until it drops below
    # its start. Returns the list of step counts, index = start.
    [0] * (limit + 1) => steps
    for start in [2:limit]:
        start => n
        0 => k
        while n >= start:
            if n % 2 == 0:
                half(n) => n
            else:
                3 * n + 1 => n
            k + 1 => k
        k + steps[n] => steps[start]
    return steps

def fibonacci(n):
    # F(0) = 0, F(1) = 1, each the sum of the two before: [F(n-1), F(n), F(n+1)].
    0 => a
    1 => b
    for i in [1:n]:
        a + b => c
        b => a
        c => b
    return [b - a, a, b]

def cassini(n):
    # F(n-1) F(n+1) - F(n)^2, which Cassini (1680) showed is (-1)^n: the two
    # products are huge and differ by exactly one, which floats cannot see
    # (the corpus case fibonacci-cassini-exact).
    fibonacci(n) => f
    return f[0] * f[2] - f[1] * f[1]

def pascal_rows(count):
    # The first `count` rows: each number the sum of the two above it.
    [[1]] => rows
    for r in [1:count - 1]:
        rows[r - 1] => prev
        [1] => row
        for k in [1:r - 1]:
            row + [prev[k - 1] + prev[k]] => row
        rows + [row + [1]] => rows
    return rows
```

Python projection of sequences.eml:

```python
def half(n):
    q = 0
    a = n
    while a >= 2:
        m = 2
        k = 1
        while m + m <= a:
            m = m + m
            k = k + k
        a = a - m
        q = q + k
    return q

def collatz(n):
    path = [n]
    top = n
    while n != 1:
        if n % 2 == 0:
            n = half(n)
        else:
            n = 3 * n + 1
        path = path + [n]
        if n > top:
            top = n
    return [path, top]

def steps_up_to(limit):
    steps = [0] * (limit + 1)
    for start in range(2, limit+1):
        n = start
        k = 0
        while n >= start:
            if n % 2 == 0:
                n = half(n)
            else:
                n = 3 * n + 1
            k = k + 1
        steps[start] = k + steps[n]
    return steps

def fibonacci(n):
    a = 0
    b = 1
    for i in range(1, n+1):
        c = a + b
        a = b
        b = c
    return [b - a, a, b]

def cassini(n):
    f = fibonacci(n)
    return f[0] * f[2] - f[1] * f[1]

def pascal_rows(count):
    rows = [[1]]
    for r in range(1, count):
        prev = rows[r - 1]
        row = [1]
        for k in range(1, r):
            row = row + [prev[k - 1] + prev[k]]
        rows = rows + [row + [1]]
    return rows
```

## README

# P069 - Sequence explorer

Three famous sequences, in whole numbers of any size. The Collatz path of a
start up to 10^12, and the start below a limit whose path is longest.
Fibonacci numbers up to F(2000), each checked with Cassini's identity. And
Pascal's triangle, either as numbers or as the pattern its odd numbers
make.

- `main.eml` - the menu, reading numbers, and laying out the output
- `sequences.eml` - exact halving, Collatz paths and step counts,
  Fibonacci numbers, Cassini's identity and Pascal's rows

How each part works:

- Collatz: as in the corpus case `collatz-sequence`, halve an even number
  and take 3n + 1 for an odd one until 1 is reached. The corpus case halves
  with int(n / 2), which goes through a float and is exact only below
  2^53. A path from a start below 10^12 can climb past that:
  1,410,123,943 climbs to 7,125,885,122,794,452,160 on its 770 steps. So
  the halving here is exact long division.
- Longest path: the step counts for every start up to the limit, each
  using the ones before it. A path is followed only until it drops below
  its start, and the count already known for that number is added. The
  first start with the most steps is reported: 6171, with 261, up to 10,000.
- Fibonacci: each number the sum of the two before, in whole numbers, so
  F(1000) has all 209 of its digits. Cassini's identity
  F(n-1) x F(n+1) - F(n)^2 = (-1)^n is checked every time. As the corpus
  case `fibonacci-cassini-exact` shows, those two products are huge and
  differ by exactly 1, a check that floats cannot pass. F(n+1) / F(n) is
  shown to 12 places by long division, closing in on the golden ratio.
- Pascal: each number the sum of the two above it, built row by row as in
  the corpus case `pascals-triangle`, and centred. Marking only the odd
  ones, with 16 or 32 rows, draws Sierpinski's triangle.

What is checked: menu choices 1 to 6; a Collatz start from 1 to
1,000,000,000,000; a limit from 2 to 100,000 for the longest path; n from
1 to 2000 for Fibonacci; 1 to 15 rows of numbers, or 1 to 32 rows of the
odd pattern. An empty answer cancels. A path of more than 40 numbers shows
its first 20 and last 10; a Fibonacci number over 60 digits shows its
first and last 25.

Sessions: `sessions/basic.in` gives:
- the paths of 27 (111 steps, up to 9232) and 1,410,123,943;
- the longest path up to 10,000, and the step counts of the starts 1 to 20;
- F(10) and F(1000), with Cassini's identity and the ratio to the next;
- 7 rows of Pascal's triangle, and 16 rows of its odd numbers.

`sessions/bad-input.in` gives:
- menu choices 0 and x;
- Collatz starts 0 and abc, then a cancel;
- longest-path limits 1 and 100001, then a cancel;
- n = 2001 for Fibonacci, 16 rows of numbers and 33 rows of the pattern,
  each then cancelled.

Built on the verified corpus cases `collatz-sequence` (the halve-or-3n+1
step), `fibonacci-cassini-exact` (Fibonacci numbers checked with an
identity floats cannot fake) and `pascals-triangle` (each row from the one
before).
