From 4b20f9961bf5ec1f5f8f287f3780bb47d451c181 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 16 Jun 2025 20:47:52 +1000 Subject: [PATCH] begin shift to nim code base --- lang/demo/cycgrp.no | 103 +++++++++++++++++++++++ noether.nimble | 14 ++++ py/NOTES | 8 ++ py/m.py | 107 ++++++++++++++++++++++++ py/mquiet.py | 9 +- py/noether.py | 119 ++++++--------------------- py/noether/cli.py | 59 ------------- py/noether/cli/__init__.py | 0 py/noether/cli/prompt.py | 66 +++++++++++++++ py/noether/{ansi.py => cli/style.py} | 108 ++++++++++++++++++------ py/noether/cmd/__init__.py | 0 py/noether/cmd/cycsub.py | 60 ++++++++++++++ py/noether/exceptions.py | 3 + py/noether/lib/groups.py | 37 +++++++++ py/noether/lib/primes.py | 36 ++++++++ py/noether/lib/structs/__init__.py | 1 + py/noether/lib/structs/__result.py | 19 +++++ py/noether/lib/util.py | 2 + py/noether/math.py | 42 ---------- py/primes.py | 80 ------------------ src/noether.nim | 7 ++ src/noether/lexer.nim | 29 +++++++ src/noether/submodule.nim | 6 ++ tests/config.nims | 1 + tests/test1.nim | 12 +++ 25 files changed, 625 insertions(+), 303 deletions(-) create mode 100644 lang/demo/cycgrp.no create mode 100644 noether.nimble create mode 100644 py/m.py delete mode 100644 py/noether/cli.py create mode 100644 py/noether/cli/__init__.py create mode 100644 py/noether/cli/prompt.py rename py/noether/{ansi.py => cli/style.py} (59%) create mode 100644 py/noether/cmd/__init__.py create mode 100644 py/noether/cmd/cycsub.py create mode 100644 py/noether/exceptions.py create mode 100644 py/noether/lib/groups.py create mode 100644 py/noether/lib/primes.py create mode 100644 py/noether/lib/structs/__init__.py create mode 100644 py/noether/lib/structs/__result.py create mode 100644 py/noether/lib/util.py delete mode 100644 py/noether/math.py delete mode 100644 py/primes.py create mode 100644 src/noether.nim create mode 100644 src/noether/lexer.nim create mode 100644 src/noether/submodule.nim create mode 100644 tests/config.nims create mode 100644 tests/test1.nim diff --git a/lang/demo/cycgrp.no b/lang/demo/cycgrp.no new file mode 100644 index 0000000..3892a6c --- /dev/null +++ b/lang/demo/cycgrp.no @@ -0,0 +1,103 @@ +/* +Type Traits (TTrait) +Operation Traits (OTrait) +Types (implement TTraits) +Operations on types (implement OTraits) +*/ + +trait QuotientRemainder: + proc quotient(a: T, b: T): T + proc remainder(a: T, b: T): T + +induce QuotientRemainder on Self: Any having + `+`: BinOp | Invertible | UniqueIdentity, + `<`: POrder, // NOTE: PartialOrders implement BinOp + `==`: EqRel -> // NOTE: EqRels implement BinOp + // `let` is a macro that replaces all instances following code's + // AST with whatever values were defined via alias + // (it does NOT change scope at all) + with ( + // define aliases for the inverse of an element as both a + // prefixed unary operation and a infix binary operation + preunary `-`(a: Self) -> +.inverse(a) // -a = +(inverse(a)) + binop `-`(a: Self, b: Self) -> a + +.inverse(b) // a - b = a +(inverse(b)) + // define aliases for <= by simply inducing <= from < and = on Self + binop `<=`(a: Self, b: Self) -> a == b or a < b + ): + // NOTE: any proc for a trait can be overriden, the standard definitions + // NOTE: are just the standard "induced" definitions + + proc quotient(a: Self, b: Self): Self -> + var quotient: Integer = 1 + alias delta -> b + while not (delta <= b): + delta = delta - b + quotient++ + return quotient + + // NOTE: assumes a is passed by value not by reference + proc remainder(a: Self, b: Self): Self -> + alias delta -> a // compiler alias + while not (delta <= b): + delta = delta - b + return delta + +// define the standard induce of Modulo via QuotientRemainder +induce Modulo on T: QuotientRemainder -> + binop mod(a: T, b: T): T -> T.remainder(a, b) + +// NOTE: the Slicable trait allow us to take slices ie `1..5` +type CycGrp: BinOp|Invertible|UniqueIdentity>: Group -> + // intrinsic variables + intrinsic modulus: T + intrinsic elements : Set + + structure(modulus: T) -> + self.modulus = modulus + // the following line is a generalised implementation of `1..(self.modulus-1)` + self.elements = (self.modulus + +.inverse(self.modulus))..(self.modulus + +.inverse(+.identity)) + + // "functors" allow one type to be represented as another type + // aka functors == typecasts + functor(value: T) -> value mod self.modulus + + + + + + +/* My attempt at formalising rings and fields in Noether + * NOTE: the following traits are "formed" from a specific structure + * NOTE: they have properties (`prop`) which are just aliases to + * NOTE: the structure that formed them + */ +trait Magma on (SET: Set, ACTION: BinOp) -> + prop SET -> SET + prop ACTION -> ACTION + +trait SemiGroup + on (M: Magma) + having M#ACTION: Associative -> + inherit M#SET, M#ACTION + +trait Group + on (SG: SemiGroup) + having SG#ACTION: UniqueIdentity | Invertible -> + inherit SG#SET, SG#ACTION + +trait Ring + on (SET: Set, ADD: BinOp, MUL: BinOp) + having (SET, ADD) : Group, + (SET, MUL) : SemiGroup, + (ADD, MUL) : Distributive -> + prop SET -> SET + prop ADD -> ADD + prop MUL -> MUL + +/* + * Notation: (`->` as `return`) + * x() -> expr + * // same as: + * x(): + * return expr + */ diff --git a/noether.nimble b/noether.nimble new file mode 100644 index 0000000..9cdf1f2 --- /dev/null +++ b/noether.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Emile Clark-Boman" +description = "Type theoretic imperative and logic language for mathematical programming" +license = "MIT" +srcDir = "src" +installExt = @["nim"]` +bin = @["noether"] + + +# Dependencies + +requires "nim >= 2.2.0" diff --git a/py/NOTES b/py/NOTES index d50a96b..7a41923 100644 --- a/py/NOTES +++ b/py/NOTES @@ -6,3 +6,11 @@ NOTE: currently my "primitive roots" are actually just the numbers that generate This site is useful as a reference: https://owlsmath.neocities.org/Primitive%20Root%20Calculator/calculator + + +2. Make Noether into a mathematical programming language (like MATLAB), + with the ability to create custom types (ie the integers modulo n) + with specific properties like operator overloading. Then you could + create functions that check whether a set and an operator form a + group or not, etc. + 2.1 Use ^ to indicate exponentiation, and use ^| to indicate xor diff --git a/py/m.py b/py/m.py new file mode 100644 index 0000000..e2b60c5 --- /dev/null +++ b/py/m.py @@ -0,0 +1,107 @@ +#!/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 + 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 = [] + + # calculate left padding to align i values + # lpadded = strlen_n + # calculate right padding to align len(orb) values + rpadded = len(str(phi)) + + # find all invertible elements (skipped for now) + for a in range(n): + orb, ord, periodic = orbit(a, n) + + # check if `a` is a primitive root + # NOTE: this should actually be `proot = (ord == phi(n))` + # proot = (ord + 1 == n) + proot = (ord == phi) + proot_c += proot + + # calculate padding + lpad = ' ' * (strlen_n - len(str(a))) + rpad = ' ' * (rpadded - len(str(ord))) + # calculate coloring + color_a = Color.Red + color_ord = None + color_orb = None + style_ord = 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('->', end=' ', flush=False) + uprint(f'{ord}{rpad}', color=color_ord, style=style_ord, end=' ', flush=False) + uprint('|', end=' ', flush=False) + uprint(f'{orb}', color=color_orb, style=style_orb, flush=True) + 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/py/mquiet.py b/py/mquiet.py index 49811a2..f635bc6 100644 --- a/py/mquiet.py +++ b/py/mquiet.py @@ -1,6 +1,7 @@ # Modulo Test -from prbraid.math import * -from prbraid.color import * +from noether.lib.groups import * +from noether.cli.ansi import * +from noether.cli._old import * import sys @@ -18,7 +19,7 @@ def main(): n = None strlen_n = None try: - uprint(PROMPT, color=Color.Blue, end='') + uprint(PROMPT, color=Color.BLUE, end='') n = input() strlen_n = len(n) n = int(n) @@ -43,7 +44,7 @@ def main(): # find all invertible elements (skipped for now) for a in range(n): - orb, ord = orbit(a, n) + orb, ord, periodic = cyclic_subgrp(a, n) # check if `a` is a primitive root proot = (ord + 1 == n) proot_c += proot diff --git a/py/noether.py b/py/noether.py index 04fb684..d5e455a 100644 --- a/py/noether.py +++ b/py/noether.py @@ -1,105 +1,34 @@ -#!/usr/bin/env python3 -import sys -import readline +from noether.cli.style import * +from noether.cli.prompt import * +from noether.lib.structs import Result + +class Noether(Prompt): + DEFAULT_PROMPT = style('~>> ', Color.BLUE) + def __init__(self) -> None: + super().__init__() -from noether.math import * -from noether.cli import * + def _parse(self, command: str) -> int: + try: + return Result.succeed(int(command)) + except ValueError: + return Result.fail('Not an integer.') - -PROMPT = '[n]: ' - -''' -Pairwise orbit cumulative summation (cum orb) -''' -def orb_cum(cum, orb): - for i in range(len(cum)): - cum[i] += orb[i] + def _exec(self, command: int) -> None: + print(style(f'OMG {command}', Color.CYAN)) 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 + try: + noether = Noether() + while True: + noether.prompt() + except (KeyboardInterrupt, EOFError): + err = style('Exit Requested...', Effect.ITALICS) + print('\n', style('[!]', Color.RED), err) - # 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 = [] - - # calculate left padding to align i values - # lpadded = strlen_n - # calculate right padding to align len(orb) values - rpadded = len(str(phi)) - - # 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 - - # calculate padding - lpad = ' ' * (strlen_n - len(str(a))) - rpad = ' ' * (rpadded - len(str(ord))) - # calculate coloring - color_a = Color.Red - color_ord = None - color_orb = None - style_ord = 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('->', end=' ', flush=False) - uprint(f'{ord}{rpad}', color=color_ord, style=style_ord, end=' ', flush=False) - uprint('|', end=' ', flush=False) - uprint(f'{orb}', color=color_orb, style=style_orb, flush=True) - 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 + # handles premature SIGINT/EOF + pass diff --git a/py/noether/cli.py b/py/noether/cli.py deleted file mode 100644 index 9cbe350..0000000 --- a/py/noether/cli.py +++ /dev/null @@ -1,59 +0,0 @@ -from enum import Enum -from typing import Optional - -class Color(Enum): - Reset = 0 - Bold = 1 - Italics = 2 - Underline = 4 - - Black = 30 - Red = 31 - Green = 32 - Yellow = 33 - Blue = 34 - Magenta = 35 - Cyan = 36 - White = 37 - - # escape sequence format string - __fescseq = '\033[{0}' - # ansi reset code - __ansi_rst = '\033[0m' - - @staticmethod - def _ansi_ret(ansi): - return ansi, Color.__ansi_white - - @staticmethod - def code(color: 'Color'): - return Color.__fescseq.format(f'{color.value}m') - - # move the cursor up n lines - @staticmethod - def mvup_code(n: int): - return Color.__fescseq.format(f'{n}A') - - def ansi(self): - code = Color.code(self) - if self == Color.White: - return code, '' - return code, Color.__ansi_rst - -# !! ULTRA PRINT !! -def uprint(text: str, - color: Optional[Color] = None, - style: Optional[Color] = None, - moveup: int = 0, - end: str = '\n', - flush: bool = True): - if color is not None: - c = color.ansi() - text = f'{c[0]}{text}{c[1]}' - if style is not None: - s = style.ansi() - tail = '' if (c is not None and c[1] == '') else s[1] - text = f'{s[0]}{text}{tail}' - if moveup > 0: - text = Color.mvup_code(moveup) + text - print(text, end=end, flush=flush) diff --git a/py/noether/cli/__init__.py b/py/noether/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/noether/cli/prompt.py b/py/noether/cli/prompt.py new file mode 100644 index 0000000..0a6d05c --- /dev/null +++ b/py/noether/cli/prompt.py @@ -0,0 +1,66 @@ +''' +===== Prompt Handling ===== +This file implements the Prompt class, allowing for +inheritance and instantiation of Prompt objects. +The CLI class uses these instead of containing the +logic immediately within itself. +''' + +from typing import Any + +from noether.cli.style import * +from noether.lib.structs import Result + +class Prompt: + DEFAULT_PROMPT = 'DEF> ' + def __init__(self) -> None: + self._prompt = self.DEFAULT_PROMPT + + ''' + Prompt and await command via stdin. + ''' + def prompt(self): + command = self.__request() + result = self._parse(command) + if result.success: + self._exec(result.value) + else: + self.__parse_error(result) + + ''' + !! OVERRIDE ON INHERITANCE !! + Handles the parsing of a given command. + ''' + def _parse(self, command: str) -> Result: + return Result.succeed(command) + + def __parse_error(self, error: Result) -> None: + err = f' ↳ {style(error.reason, Effect.ITALICS)}' + print(style(err, Effect.DIM)) + + ''' + !! OVERRIDE ON INHERITANCE !! + Handles the execution of a command that + was successfully parsed by a Prompt inheritor. + ''' + def _exec(self, command: Any) -> None: + pass + + ''' + Internal use only. Handles a raw request with no validation. + ''' + def __request(self) -> None: + print(self.__get_prompt(), end='', flush=True) + return input() + + ''' + !! OVERRIDE ON INHERITANCE !! + ''' + def _style_prompt(self, prompt: str) -> str: + return prompt + + def __get_prompt(self) -> str: + return self._style_prompt(self._prompt) + + def set_prompt(self, prompt: str) -> None: + self._prompt = prompt diff --git a/py/noether/ansi.py b/py/noether/cli/style.py similarity index 59% rename from py/noether/ansi.py rename to py/noether/cli/style.py index efeae57..eda6fd9 100644 --- a/py/noether/ansi.py +++ b/py/noether/cli/style.py @@ -8,8 +8,12 @@ for management of the CLI. from sys import stdout from enum import StrEnum +# Removes ALL set colors/styling RESET = '\x1b[0m' +''' +Implements text foreground coloring. +''' class Color(StrEnum): BLACK = '\x1b[30m' RED = '\x1b[31m' @@ -20,7 +24,18 @@ class Color(StrEnum): CYAN = '\x1b[36m' WHITE = '\x1b[37m' -class Style(StrEnum): + ''' + Handles the application of a Color to a given text. + Unset the `temp` flag to leave this style on (after the given text) + ''' + @staticmethod + def apply(color: 'Color', text: str, temp: bool = True) -> str: + return color + text + Color.WHITE if temp else '' + +''' +Implements text effects. +''' +class Effect(StrEnum): BOLD = '\x1b[1m' DIM = '\x1b[2m' ITALICS = '\x1b[3m' @@ -29,14 +44,39 @@ class Style(StrEnum): 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 - ] + ''' + Returns the opposite ESC code to a given Effect. + ie the opposite of BOLD '\x1b[1m]' is '\x1b[21m' + ''' + @staticmethod + def _inverse(effect: 'Effect') -> str: + return f'{effect[:2]}2{effect[2:]}' + + ''' + Handles the application of a Effect to a given text. + Unset the `temp` flag to leave this style on (after the given text) + ''' + @staticmethod + def apply(effect: 'Effect', text: str, temp: bool = True) -> str: + return effect + text + Effect._inverse(effect) if temp else '' + +''' +Applies styling (color/effects/etc) to a given string +Unset the `temp` flag to leave this style on (after the given text) +''' +def style(text: str, *args: Color | Effect, temp: bool = True) -> str: + # unsures we don't add redundant "set white" commands + unset_color = False + for arg in args: + unset = temp + if isinstance(arg, Color): + unset_color |= temp + unset = False + text = type(arg).apply(arg, text, temp=True) + if unset_color: + text += Color.WHITE + return text + ''' Implements cursor movement functionality. @@ -48,8 +88,8 @@ NOTE: ''' class Cursor(StrEnum): # SAVE current / RESTORE last saved cursor position - SAVE = f'\x1b[7' - RESTORE = f'\x1b[8' + _SAVE = f'\x1b[7' + _RESTORE = f'\x1b[8' _MV_UP = 'A' _MV_DOWN = 'B' @@ -137,7 +177,7 @@ class Cursor(StrEnum): ''' @staticmethod def save() -> None: - stdout.write(Cursor.SAVE) + stdout.write(Cursor._SAVE) ''' Restores the cursor position to a saved position. @@ -145,7 +185,7 @@ class Cursor(StrEnum): ''' @staticmethod def restore() -> None: - stdout.write(Cursor.RESTORE) + stdout.write(Cursor._RESTORE) ''' Handles erasing content displayed on the screen. @@ -155,18 +195,40 @@ 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 + _SCR_AFTER = '\x1b[0J' + _SCR_BEFORE = '\x1b[1J' + _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' + _LINE_AFTER = '\x1b[0K' + _LINE_BEFORE = '\x1b[1K' + _LINE_ALL = '\x1b[2K' + ''' + Erase characters on the entire screen. + Set `before` flag to only erase before the cursor, + set `after` flag to only erase after the cursor. + ''' @staticmethod - # TODO COME BACK HERE + def screen(before: bool = False, after: bool = False) -> None: + if before: + stdout.write(Erase._SCR_BEFORE) + elif after: + stdout.write(Erase._SCR_AFTER) + else: + stdout.write(Erase._SCR_ALL) + + ''' + Erase characters on the current line. + Set `before` flag to only erase before the cursor, + set `after` flag to only erase after the cursor. + ''' + @staticmethod + def line(before: bool = False, after: bool = False) -> None: + if before: + stdout.write(Erase._LINE_BEFORE) + elif after: + stdout.write(Erase._LINE_AFTER) + else: + stdout.write(Erase._LINE_ALL) diff --git a/py/noether/cmd/__init__.py b/py/noether/cmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py/noether/cmd/cycsub.py b/py/noether/cmd/cycsub.py new file mode 100644 index 0000000..afb3ca3 --- /dev/null +++ b/py/noether/cmd/cycsub.py @@ -0,0 +1,60 @@ +from sys import stdout + +from noether.cli.style import * +from noether.cli.prompt import * +from noether.lib.structs import Result + +from noether.lib.util import digits +from noether.lib.groups import cyclic_subgrp +from noether.lib.primes import totient, is_prime + +class cycsub(Prompt): + DEFAULT_PROMPT = style('[n]: ', Color.BLUE) + def __init__(self, ignore_zero: bool = True) -> None: + super().__init__() + self.ignore_zero = ignore_zero + + def _parse(self, command: str) -> int: + try: + return Result.succeed(int(command)) + except ValueError: + return Result.fail('Not an integer.') + + def _exec(self, n: int) -> None: + phi = totient(n) + lpadding = digits(n) + rpadding = digits(phi) + if is_prime(n, phi=n): + Cursor.save() + Cursor.move_y(-1, reset=False) + Cursor.set_x(len(self.DEFAULT_PROMPT) + lpadding + 1) + stdout.write(style('[PRIME]', Color.MAGENTA, Effect.BOLD)) + Cursor.restore() + stdout.flush() + + # keeps track of all primitive roots + # (note that there will be exactly totient(phi) of them) + proots = [] + for g in range(n): + G, order, periodic = cyclic_subgrp(g, n, ignore_zero=self.ignore_zero) + primitive = (order == phi) # primitive root + + lpad = ' ' * (lpadding - digits(a)) + rpad = ' ' * (rpadding - digits(order)) + + color_g = Color.RED + style_G = [] + style_order = [] + if primitive: + color_g = Color.GREEN + style_G = [Color.GREEN, Effect.BOLD] + style_order = [Color.YELLOW, Effect.BOLD] + proots.append(g) + elif gcd(g, n) == 1: + color_g = Color.Yellow + line = style(f'{lpad}{g}', color_g, Effect.BOLD) + \ + '-> ' + \ + style(f'{order}{rpad} ', *style_order) + \ + '| ' + \ + style(str(G), *style_G) + print(line, flush=True) diff --git a/py/noether/exceptions.py b/py/noether/exceptions.py new file mode 100644 index 0000000..75c314e --- /dev/null +++ b/py/noether/exceptions.py @@ -0,0 +1,3 @@ +class PromptParseError(Exception): + def __init__(self, message: str) -> None: + super().__init__(message) diff --git a/py/noether/lib/groups.py b/py/noether/lib/groups.py new file mode 100644 index 0000000..25028ef --- /dev/null +++ b/py/noether/lib/groups.py @@ -0,0 +1,37 @@ +''' +This library exists to isolate all math functions +related to groups and their representations. +''' + +from math import gcd + +''' +Returns the multiplicative cyclic subgroup +generated by an element g modulo m. +Returns the cyclic subgroup as a list[int], + the order of that subgroup, and a boolean + indicating whether g is infinitely repeating + with period == ord (or otherwise if it + terminates with g**ord == 0). +''' +def cyclic_subgrp(g: int, + m: int, + ignore_zero: bool = True) -> tuple[list[int], int, bool]: + G = [] + order = 0 + periodic = True + a = 1 # start at identity + for _ in range(m): + a = (a * g) % m + if a == 0: + if not ignore_zero: + G.append(a) + order += 1 + periodic = False + break + # check if we've reached something periodic + elif a in G[:1]: + break + G.append(a) + order += 1 + return G, order, periodic diff --git a/py/noether/lib/primes.py b/py/noether/lib/primes.py new file mode 100644 index 0000000..772e4a7 --- /dev/null +++ b/py/noether/lib/primes.py @@ -0,0 +1,36 @@ +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 + +''' +Tests the primality of an integer using its totient. +NOTE: If totient(n) has already been calculated + then pass it as the optional phi parameter. +''' +def is_prime(n: int, phi: int = None): + return n - 1 == phi if phi is not None else totient(n): + +''' +Prime number generator function. +Returns the tuple (p, phi(p)) where p is prime + and phi is Euler's totient function. +''' +def prime_gen(): + n = 1 + while True: + n += 1 + phi = totient(n) + if is_prime(n, phi=phi): + return n, phi diff --git a/py/noether/lib/structs/__init__.py b/py/noether/lib/structs/__init__.py new file mode 100644 index 0000000..2e02aaf --- /dev/null +++ b/py/noether/lib/structs/__init__.py @@ -0,0 +1 @@ +from noether.lib.structs.__result import Result diff --git a/py/noether/lib/structs/__result.py b/py/noether/lib/structs/__result.py new file mode 100644 index 0000000..8746de0 --- /dev/null +++ b/py/noether/lib/structs/__result.py @@ -0,0 +1,19 @@ +from enum import Enum +from typing import Optional + +class Result: + def __init__(self, + success: bool, + reason: str, + value: Optional[any] = None) -> None: + self.success = success + self.reason = reason + self.value = value + + @classmethod + def succeed(cls, value: any, reason: str = 'Ok') -> 'Result': + return cls(True, reason, value=value) + + @classmethod + def fail(cls, reason: str, value: Optional[any] = None) -> 'Result': + return cls(False, reason, value=value) diff --git a/py/noether/lib/util.py b/py/noether/lib/util.py new file mode 100644 index 0000000..e6c160d --- /dev/null +++ b/py/noether/lib/util.py @@ -0,0 +1,2 @@ +def digits(n: int) -> int: + return len(str(n)) diff --git a/py/noether/math.py b/py/noether/math.py deleted file mode 100644 index 3418c4e..0000000 --- a/py/noether/math.py +++ /dev/null @@ -1,42 +0,0 @@ -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 deleted file mode 100644 index de6bf96..0000000 --- a/py/primes.py +++ /dev/null @@ -1,80 +0,0 @@ -# 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 diff --git a/src/noether.nim b/src/noether.nim new file mode 100644 index 0000000..e0c840d --- /dev/null +++ b/src/noether.nim @@ -0,0 +1,7 @@ +# This is just an example to get you started. A typical hybrid package +# uses this file as the main entry point of the application. + +import noether/submodule + +when isMainModule: + echo(getWelcomeMessage()) diff --git a/src/noether/lexer.nim b/src/noether/lexer.nim new file mode 100644 index 0000000..d65a0b6 --- /dev/null +++ b/src/noether/lexer.nim @@ -0,0 +1,29 @@ +import std/streams + +type + nlLexer* = object + stream: Stream + pos: Natural + +proc newLexerFromStream(stream: Stream): nlLexer = + result = nlLexer( + stream: stream, + pos: 0, + ) +) + +proc newLexer*(content: string, isFile: bool): nlLexer = + result = newLexerFromStream( + streamFile(content) if isFile else streamString(content) + ) +) + +proc streamFile(filename: string): FileStream = + result = newFileStream(filename, fmRead) + +proc streamString(str: string): StringStream = + result = newStringStream(str) + + +proc nextToken*(lexer: nlLexer): nlToken = + result = newToken[] diff --git a/src/noether/submodule.nim b/src/noether/submodule.nim new file mode 100644 index 0000000..d8146ba --- /dev/null +++ b/src/noether/submodule.nim @@ -0,0 +1,6 @@ +# This is just an example to get you started. Users of your hybrid library will +# import this file by writing ``import srcpkg/submodule``. Feel free to rename or +# remove this file altogether. You may create additional modules alongside +# this file as required. + +proc getWelcomeMessage*(): string = "Hello, World!" diff --git a/tests/config.nims b/tests/config.nims new file mode 100644 index 0000000..3bb69f8 --- /dev/null +++ b/tests/config.nims @@ -0,0 +1 @@ +switch("path", "$projectDir/../src") \ No newline at end of file diff --git a/tests/test1.nim b/tests/test1.nim new file mode 100644 index 0000000..f4a5d43 --- /dev/null +++ b/tests/test1.nim @@ -0,0 +1,12 @@ +# This is just an example to get you started. You may wish to put all of your +# tests into a single file, or separate them into multiple `test1`, `test2` +# etc. files (better names are recommended, just make sure the name starts with +# the letter 't'). +# +# To run these tests, simply execute `nimble test`. + +import unittest + +import src/submodule +test "correct welcome": + check getWelcomeMessage() == "Hello, World!"