commit state before changing what noether considers primitive roots

This commit is contained in:
Emile Clark-Boman 2025-06-12 14:42:03 +10:00
parent a168a728ce
commit 33bcffdc69
11 changed files with 441 additions and 36 deletions

19
README Normal file
View file

@ -0,0 +1,19 @@
Noether is a project I plan to complete over a LONG period of time (years).
A collection of useful commands for solving various math problems.
A personal little MATLAB alternative I suppose :)
In future I'd love to start using a developed TUI library like [textual](https://github.com/Textualize/textual?tab=readme-ov-file),
but for now everything is just a custom ANSI wrapper thing.
NOTES:
1. could I define something like "primality classes"?
ie class one: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, ...
class two: 3, 5, 11, 17, 31, ... (class 1 with a prime index, ie 11 is the 5th class 1 prime)
class three: 5, 11, 31, ... (class 2 with a prime index, ie 11 is the 3rd class 2 prime)
2. figure out all possible patterns that can appear amongst primitive roots
can we then categorise primes by their primitive root behaviour?

View file

@ -1,24 +0,0 @@
# Euler's Totient (Phi) Function
def totient(n):
phi = int(n > 1 and n)
for p in range(2, int(n ** .5) + 1):
if not n % p:
phi -= phi // p
while not n % p:
n //= p
#if n is > 1 it means it is prime
if n > 1: phi -= phi // n
return phi
def orbit(b, m):
generated = []
x = b
for i in range(m):
x = (x * b) % m
if x not in generated:
generated.append(x)
return generated
def order(b, m):
return len(orbit(b, m))

8
py/NOTES Normal file
View file

@ -0,0 +1,8 @@
TODO:
NOTE: currently my "primitive roots" are actually just the numbers that generate every integer below the modulus, that is `a` such that `orbit(a) = Z_n`
1. How I'm calculating something as a primtive root, and its order is not correct.
For instance, consider a modulus n=4, a=3 is a primitive root, and a=2 SHOULD have order 0 (it is aperiodic)
This site is useful as a reference:
https://owlsmath.neocities.org/Primitive%20Root%20Calculator/calculator

76
py/mquiet.py Normal file
View file

@ -0,0 +1,76 @@
# Modulo Test
from prbraid.math import *
from prbraid.color import *
import sys
PROMPT = '[n]: '
'''
Pairwise orbit cumulative summation (cum orb)
'''
def orb_cum(cum, orb):
for i in range(len(cum)):
cum[i] += orb[i]
def main():
while True:
n = None
strlen_n = None
try:
uprint(PROMPT, color=Color.Blue, end='')
n = input()
strlen_n = len(n)
n = int(n)
except ValueError:
continue
# calculate phi of n
phi = totient(n)
# determine if n is prime
prime = n - 1 == phi
if prime:
uprint('', moveup=1, end='', flush=False)
column = len(PROMPT) + strlen_n + 1
sys.stdout.write(f'\033[{column}C')
uprint('[PRIME]', color=Color.Magenta, style=Color.Bold, flush=True)
# primitive root values
proot_v = []
# primitive root count
proot_c = 0
# cumulative sum of primitive root orbits
prorb_cum = []
# find all invertible elements (skipped for now)
for a in range(n):
orb, ord = orbit(a, n)
# check if `a` is a primitive root
proot = (ord + 1 == n)
proot_c += proot
if proot:
proot_v.append(a)
if not prorb_cum:
prorb_cum = orb
else:
orb_cum(prorb_cum, orb)
print(a)
uprint('Cum Orb: ', end='', color=Color.Cyan)
uprint(f'{prorb_cum}', flush=True)
prorb_cum_mod = [x % n for x in prorb_cum]
uprint(f' {prorb_cum_mod}', flush=True)
uprint('Roots: ', end='', color=Color.Cyan)
uprint(proot_v, flush=True)
root_delta = [proot_v[i+1] - proot_v[i] for i in range(proot_c - 1)]
uprint('Delta: ', end='', color=Color.Cyan)
uprint(root_delta, flush=True)
uprint('Roots/Phi: ', end='', color=Color.Cyan)
uprint(f'{proot_c}/{phi}\n', flush=True)
if __name__ == '__main__':
try:
main()
except (KeyboardInterrupt, EOFError):
pass

View file

@ -1,11 +1,20 @@
# Modulo Test
from prbraid.math import *
from prbraid.color import *
#!/usr/bin/env python3
import sys
import readline
from noether.math import *
from noether.cli import *
PROMPT = '[n]: '
'''
Pairwise orbit cumulative summation (cum orb)
'''
def orb_cum(cum, orb):
for i in range(len(cum)):
cum[i] += orb[i]
def main():
while True:
n = None
@ -22,13 +31,17 @@ def main():
phi = totient(n)
# determine if n is prime
prime = n - 1 == phi
# sys.stdout.write('\x1b[1A')
# sys.stdout.flush()
if prime:
uprint('', moveup=1, end='', flush=False)
column = len(PROMPT) + strlen_n + 1
sys.stdout.write(f'\033[{column}C')
uprint('[PRIME]', color=Color.Magenta, style=Color.Bold, flush=True)
# primitive root values
proot_v = []
# primitive root count
proot_c = 0
# cumulative sum of primitive root orbits
prorb_cum = []
# calculate left padding to align i values
# lpadded = strlen_n
@ -37,11 +50,11 @@ def main():
# find all invertible elements (skipped for now)
for a in range(n):
orb = orbit(a, n)
ord = len(orb)
orb, ord = orbit(a, n)
# check if `a` is a primitive root
proot = (ord + 1 == n)
proot_c += proot
# calculate padding
lpad = ' ' * (strlen_n - len(str(a)))
@ -51,20 +64,39 @@ def main():
color_ord = None
color_orb = None
style_ord = None
style_orb = None
style_orb = None
if proot:
color_a = Color.Green
color_ord = Color.Yellow
color_orb = Color.Green
style_ord = Color.Bold
style_orb = Color.Bold
proot_v.append(a)
if not prorb_cum:
prorb_cum = orb
else:
orb_cum(prorb_cum, orb)
elif gcd(a, n) == 1:
color_a = Color.Yellow
uprint(f'{lpad}{a}', color=color_a, style=Color.Bold, end=' ', flush=False)
uprint(f'->', end=' ', flush=False)
uprint('->', end=' ', flush=False)
uprint(f'{ord}{rpad}', color=color_ord, style=style_ord, end=' ', flush=False)
uprint(f'|', end=' ', flush=False)
uprint('|', end=' ', flush=False)
uprint(f'{orb}', color=color_orb, style=style_orb, flush=True)
print() # empty new line
uprint('Cum Orb: ', end='', color=Color.Cyan)
uprint(f'{prorb_cum}', flush=True)
prorb_cum_mod = [x % n for x in prorb_cum]
uprint(f' {prorb_cum_mod}', flush=True)
uprint('Roots: ', end='', color=Color.Cyan)
uprint(proot_v, flush=True)
root_delta = [proot_v[i+1] - proot_v[i] for i in range(proot_c - 1)]
uprint('Delta: ', end='', color=Color.Cyan)
uprint(root_delta, flush=True)
uprint('Roots/Phi: ', end='', color=Color.Cyan)
uprint(f'{proot_c}/{phi}\n', flush=True)
if __name__ == '__main__':
try:

172
py/noether/ansi.py Normal file
View file

@ -0,0 +1,172 @@
'''
=== ANSI Escape Sequences ===
This file exists to organise and name
all important ANSI escape sequences
for management of the CLI.
'''
from sys import stdout
from enum import StrEnum
RESET = '\x1b[0m'
class Color(StrEnum):
BLACK = '\x1b[30m'
RED = '\x1b[31m'
GREEN = '\x1b[32m'
YELLOW = '\x1b[33m'
BLUE = '\x1b[34m'
MAGENTA = '\x1b[35m'
CYAN = '\x1b[36m'
WHITE = '\x1b[37m'
class Style(StrEnum):
BOLD = '\x1b[1m'
DIM = '\x1b[2m'
ITALICS = '\x1b[3m'
UNDERLINE = '\x1b[4m'
BLINK = '\x1b[5m'
REVERSE = '\x1b[7m'
HIDE = '\x1b[8m'
_DISABLE = [
'\x1b[21m', # BOLD
'\x1b[22m', # DIM
'\x1b[24m', # UNDERLINE
'\x1b[25m', # BLINK
'\x1b[27m', # REVERSE
'\x1b[28m' # HIDE
]
'''
Implements cursor movement functionality.
NOTE:
The Cursor class currently has no ability
to handle EXACT line (row) numbers. Currently
I'm assuming all functionality of noether can
be implemented solely via relative movements.
'''
class Cursor(StrEnum):
# SAVE current / RESTORE last saved cursor position
SAVE = f'\x1b[7'
RESTORE = f'\x1b[8'
_MV_UP = 'A'
_MV_DOWN = 'B'
_MV_LEFT = 'C'
_MV_RIGHT = 'D'
# NEXT/PREV are the same as DOWN/UP (respectively)
# except that the cursor will reset to column 0
_MV_NEXT = 'E'
_MV_PREV = 'F'
# move cursor to the start of the current line
_MV_START = '\r' # there is no ESC CSI for carriage return
_MV_COLUMN = 'G'
'''
Generates an ESC code sequence corresponding
to a horizontal movement relative to the
cursor's current vertical position.
+ive = right
-ive = left
'''
@staticmethod
def _XMOVE_REL(n: int) -> str:
if n > 0:
return f'\x1b[{n}{Cursor._MV_RIGHT}'
if n < 0:
return f'\x1b[{n}{Cursor._MV_LEFT}'
return ''
'''
Generates an ESC code sequence corresponding
to a horizontal movement to an EXACT column.
'''
@staticmethod
def _XMOVE(n: int) -> str:
return f'\x1b[{n}{Cursor._MV_COLUMN}'
'''
Generates an ESC code sequence corresponding
to a vertical movement relative to the
cursor's current vertical position.
+ive = down
-ive = up
'''
@staticmethod
def _YMOVE_REL(n: int, reset: bool = False) -> str:
if n > 0:
if reset:
return f'\x1b[{n}{Cursor._MV_NEXT}'
return f'\x1b[{n}{Cursor._MV_DOWN}'
if n < 0:
if reset:
return f'\x1b[{n}{Cursor._MV_PREV}'
return f'\x1b[{n}{Cursor._MV_UP}'
return ''
'''
Sets the cursor column (horizontal) position to an exact value.
NOTE: does NOT flush stdout buffer
'''
@staticmethod
def set_x(n: int) -> None:
stdout.write(Cursor.XMOVE(n))
'''
Moves the cursor left/right n columns (relative).
NOTE: does NOT flush stdout buffer
'''
@staticmethod
def move_x(n: int) -> None:
stdout.write(Cursor._XMOVE_REL(n))
'''
Moves the cursor up/down n rows and resets the
cursor to be at the start of the line
NOTE: does NOT flush stdout buffer
'''
@staticmethod
def move_y(n: int, reset: bool = True) -> None:
stdout.write(Cursor._YMOVE_REL(n, reset=reset))
'''
Saves the current cursor position.
NOTE: does NOT flush stdout buffer
'''
@staticmethod
def save() -> None:
stdout.write(Cursor.SAVE)
'''
Restores the cursor position to a saved position.
NOTE: does NOT flush stdout buffer
'''
@staticmethod
def restore() -> None:
stdout.write(Cursor.RESTORE)
'''
Handles erasing content displayed on the screen.
NOTE that the cursor position is NOT updated
via these sequences. The \r code should be given after.
'''
class Erase(StrEnum):
# erase everything from the current cursor
# position to the START/END of the screen
ER_SCR_END = '\x1b[0J'
ER_SCR_START = '\x1b[1J'
# ER_SCR_ALL = '\x1b[2J'
ER_SCR_ALL = '\x1b[3J' # erase screen and delete all saved cursors
# ER_SAVED = '\x1b[3J' # erase saved lines
# erase everything from the current cursor
# position to the START/END of the current line
ER_LINE_END = '\x1b[0K'
ER_LINE_START = '\x1b[1K'
ER_LINE_ALL = '\x1b[2K'
@staticmethod
# TODO COME BACK HERE

View file

42
py/noether/math.py Normal file
View file

@ -0,0 +1,42 @@
from math import gcd
# Euler's Totient (Phi) Function
def totient(n):
phi = int(n > 1 and n)
for p in range(2, int(n ** .5) + 1):
if not n % p:
phi -= phi // p
while not n % p:
n //= p
#if n is > 1 it means it is prime
if n > 1: phi -= phi // n
return phi
def is_prime(n):
return n - 1 == totient(n)
def orbit(b, m):
orb = []
ord = 0
x = 1 # start at identity
for i in range(m):
x = (x * b) % m
if x in orb:
break
orb.append(x)
ord += 1
return orb, ord
def order(b, m):
return len(orbit(b, m))
'''
Functions for Testing my Conjectures
The conjecture is specifically that, for all positive integers n
where n + 1 is prime, then conj_phigcd(n) is also prime.
'''
def conj_phigcd(n):
phi = totient(n)
return phi / gcd(n, phi) - 1

80
py/primes.py Normal file
View file

@ -0,0 +1,80 @@
# Modulo Test
from prbraid.math import *
from prbraid.color import *
import sys
from math import gcd
from time import sleep
PROMPT = '[n]: '
'''
Modify the body of this function, it will be run
against every prime number ascending.
'''
def test_function(p: int, phi: int):
p_color = Color.Green
result_color = Color.Yellow
result = phi / gcd(p, phi) - 1
if result < 0 or not is_prime(result):
p_color = Color.Red
result_color = Color.Red
uprint(p, color=p_color, style=Color.Bold, end=' -> ', flush=False)
uprint(result, color=result_color, flush=True)
sleep(0.1)
def main():
n = -1
while True:
n += 1
# calculate phi of n
phi = totient(n)
# determine if n is prime
prime = n - 1 == phi
if not prime:
continue
# # primitive root values
# proot_v = []
# # primitive root count
# proot_c = 0
# # cumulative sum of primitive root orbits
# prorb_cum = []
# # find all invertible elements (skipped for now)
# for a in range(n):
# orb, ord = orbit(a, n)
# # check if `a` is a primitive root
# proot = (ord + 1 == n)
# proot_c += proot
# if proot:
# proot_v.append(a)
# if not prorb_cum:
# prorb_cum = orb
# else:
# orb_cum(prorb_cum, orb)
# print(a)
# uprint('Cum Orb: ', end='', color=Color.Cyan)
# uprint(f'{prorb_cum}', flush=True)
# prorb_cum_mod = [x % n for x in prorb_cum]
# uprint(f' {prorb_cum_mod}', flush=True)
# uprint('Roots: ', end='', color=Color.Cyan)
# uprint(proot_v, flush=True)
# root_delta = [proot_v[i+1] - proot_v[i] for i in range(proot_c - 1)]
# uprint('Delta: ', end='', color=Color.Cyan)
# uprint(root_delta, flush=True)
# uprint('Roots/Phi: ', end='', color=Color.Cyan)
# uprint(f'{proot_c}/{phi}\n', flush=True)
test_function(n, phi)
if __name__ == '__main__':
try:
main()
except (KeyboardInterrupt, EOFError):
pass