diff --git a/README b/README new file mode 100644 index 0000000..67cc69b --- /dev/null +++ b/README @@ -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? diff --git a/prbraid/math.py b/prbraid/math.py deleted file mode 100644 index 896faa2..0000000 --- a/prbraid/math.py +++ /dev/null @@ -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)) diff --git a/py/NOTES b/py/NOTES new file mode 100644 index 0000000..d50a96b --- /dev/null +++ b/py/NOTES @@ -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 diff --git a/py/mquiet.py b/py/mquiet.py new file mode 100644 index 0000000..49811a2 --- /dev/null +++ b/py/mquiet.py @@ -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 diff --git a/m.py b/py/noether.py similarity index 56% rename from m.py rename to py/noether.py index 0c7dec4..04fb684 100644 --- a/m.py +++ b/py/noether.py @@ -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: diff --git a/prbraid/__init__.py b/py/noether/__init__.py similarity index 100% rename from prbraid/__init__.py rename to py/noether/__init__.py diff --git a/py/noether/ansi.py b/py/noether/ansi.py new file mode 100644 index 0000000..efeae57 --- /dev/null +++ b/py/noether/ansi.py @@ -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 diff --git a/prbraid/color.py b/py/noether/cli.py similarity index 100% rename from prbraid/color.py rename to py/noether/cli.py diff --git a/py/noether/lib/__init__.py b/py/noether/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/noether/math.py b/py/noether/math.py new file mode 100644 index 0000000..3418c4e --- /dev/null +++ b/py/noether/math.py @@ -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 diff --git a/py/primes.py b/py/primes.py new file mode 100644 index 0000000..de6bf96 --- /dev/null +++ b/py/primes.py @@ -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