Download mathos.py from Supernova11c/Supernova-Mathos-V6.1: direct link, hf CLI and curl.
- Browser
- Download file 12.9 kB
-
https://huggingface.co/Supernova11c/Supernova-Mathos-V6.1/resolve/main/mathos.py
- Command line
-
hf download hf://Supernova11c/Supernova-Mathos-V6.1/mathos.py
-
curl -L -o mathos.py https://huggingface.co/Supernova11c/Supernova-Mathos-V6.1/resolve/main/mathos.py
12.9 kB
| import re | |
| import numpy as np | |
| from fractions import Fraction | |
| HIDDEN = 128 | |
| BATCH_SIZE = 128 | |
| LEARNING_RATE = 0.003 | |
| OP_NAMES = ['+', '-', '*', '/'] | |
| INPUT_SIZE = 34 | |
| def encode(a, b, state, op): | |
| x = np.zeros(INPUT_SIZE, dtype=np.float64) | |
| x[a] = 1.0 | |
| x[10 + b] = 1.0 | |
| x[20 + state] = 1.0 | |
| x[30 + op] = 1.0 | |
| return x | |
| def build_data(): | |
| datasets = {op: [] for op in range(4)} | |
| for a in range(10): | |
| for b in range(10): | |
| for carry in range(2): | |
| total = a + b + carry | |
| datasets[0].append((encode(a, b, carry, 0), total % 10, total // 10)) | |
| for a in range(10): | |
| for b in range(10): | |
| for borrow in range(2): | |
| total = a - b - borrow | |
| digit = total % 10 | |
| next_borrow = int(total < 0) | |
| datasets[1].append((encode(a, b, borrow, 1), digit, next_borrow)) | |
| for a in range(10): | |
| for b in range(10): | |
| for carry in range(9): | |
| total = a * b + carry | |
| datasets[2].append((encode(a, b, carry, 2), total % 10, total // 10)) | |
| for divisor in range(1, 10): | |
| for remainder in range(divisor): | |
| for next_digit in range(10): | |
| current = remainder * 10 + next_digit | |
| quotient_digit = current // divisor | |
| next_remainder = current % divisor | |
| datasets[3].append((encode(next_digit, divisor, remainder, 3), quotient_digit, next_remainder)) | |
| result = {} | |
| for op, rows in datasets.items(): | |
| result[op] = (np.stack([r[0] for r in rows]), np.array([r[1] for r in rows], dtype=np.int64), np.array([r[2] for r in rows], dtype=np.int64)) | |
| return result | |
| DATA = build_data() | |
| def softmax(z): | |
| z = z - np.max(z, axis=1, keepdims=True) | |
| e = np.exp(z) | |
| return e / np.sum(e, axis=1, keepdims=True) | |
| class MathosV6: | |
| def __init__(self, seed=77): | |
| r = np.random.default_rng(seed) | |
| def init(nin, nout): | |
| limit = np.sqrt(6.0 / (nin + nout)) | |
| return r.uniform(-limit, limit, (nin, nout)) | |
| self.p = {'W1': init(INPUT_SIZE, HIDDEN), 'b1': np.zeros((1, HIDDEN)), 'Wd': init(HIDDEN, 10), 'bd': np.zeros((1, 10)), 'Ws': init(HIDDEN, 10), 'bs': np.zeros((1, 10))} | |
| self.m = {k: np.zeros_like(v) for k, v in self.p.items()} | |
| self.v = {k: np.zeros_like(v) for k, v in self.p.items()} | |
| self.step = 0 | |
| self.experiences_seen = 0 | |
| def forward(self, X): | |
| h = np.tanh(X @ self.p['W1'] + self.p['b1']) | |
| return (h, h @ self.p['Wd'] + self.p['bd'], h @ self.p['Ws'] + self.p['bs']) | |
| def train_batch(self, X, yd, ys): | |
| n = len(X) | |
| h, dl, sl = self.forward(X) | |
| pd, ps = (softmax(dl), softmax(sl)) | |
| rows = np.arange(n) | |
| gd, gs = (pd.copy(), ps.copy()) | |
| gd[rows, yd] -= 1.0 | |
| gs[rows, ys] -= 1.0 | |
| gd /= n | |
| gs /= n | |
| grads = {'Wd': h.T @ gd, 'bd': gd.sum(axis=0, keepdims=True), 'Ws': h.T @ gs, 'bs': gs.sum(axis=0, keepdims=True)} | |
| dh = gd @ self.p['Wd'].T + gs @ self.p['Ws'].T | |
| dz = dh * (1.0 - h * h) | |
| grads['W1'] = X.T @ dz | |
| grads['b1'] = dz.sum(axis=0, keepdims=True) | |
| self.step += 1 | |
| for k in self.p: | |
| g = grads[k] | |
| self.m[k] = 0.9 * self.m[k] + 0.1 * g | |
| self.v[k] = 0.999 * self.v[k] + 0.001 * g * g | |
| mhat = self.m[k] / (1.0 - 0.9 ** self.step) | |
| vhat = self.v[k] / (1.0 - 0.999 ** self.step) | |
| self.p[k] -= LEARNING_RATE * mhat / (np.sqrt(vhat) + 1e-08) | |
| self.experiences_seen += n | |
| loss_d = -np.log(pd[rows, yd] + 1e-12).mean() | |
| loss_s = -np.log(ps[rows, ys] + 1e-12).mean() | |
| return float(loss_d + loss_s) | |
| def transition(self, a, b, state, op): | |
| x = encode(a, b, state, op).reshape(1, -1) | |
| _, dl, sl = self.forward(x) | |
| return (int(dl[0].argmax()), int(sl[0].argmax())) | |
| def transition_accuracy(self, op=None): | |
| selected = range(4) if op is None else [op] | |
| correct, total = (0, 0) | |
| for operation in selected: | |
| X, yd, ys = DATA[operation] | |
| _, dl, sl = self.forward(X) | |
| pd, ps = (dl.argmax(axis=1), sl.argmax(axis=1)) | |
| correct += int(np.sum((pd == yd) & (ps == ys))) | |
| total += len(X) | |
| return correct / total | |
| def train_operation(self, op, epochs, seed): | |
| X, yd, ys = DATA[op] | |
| r = np.random.default_rng(seed) | |
| name = OP_NAMES[op] | |
| for ep in range(1, epochs + 1): | |
| order = r.permutation(len(X)) | |
| total_loss = 0.0 | |
| for start in range(0, len(X), BATCH_SIZE): | |
| ids = order[start:start + BATCH_SIZE] | |
| loss = self.train_batch(X[ids], yd[ids], ys[ids]) | |
| total_loss += loss * len(ids) | |
| total_loss /= len(X) | |
| if ep == 1 or ep % 50 == 0 or ep == epochs: | |
| acc = self.transition_accuracy(op) | |
| print(f'Learn {name} | Epoch {ep:03d}/{epochs} | loss={total_loss:.5f} | transition accuracy={100 * acc:.2f}%') | |
| def add(self, a, b): | |
| if a < 0 or b < 0: | |
| raise ValueError('Addition accepts nonnegative integers.') | |
| result, carry, pos = (0, 0, 0) | |
| while pos < max(len(str(a)), len(str(b))) or carry: | |
| da = a // 10 ** pos % 10 | |
| db = b // 10 ** pos % 10 | |
| digit, carry = self.transition(da, db, carry, 0) | |
| result += digit * 10 ** pos | |
| pos += 1 | |
| return result | |
| def subtract(self, a, b): | |
| if a < b or b < 0: | |
| raise ValueError('Subtraction requires a >= b >= 0.') | |
| result, borrow = (0, 0) | |
| for pos in range(len(str(a))): | |
| da = a // 10 ** pos % 10 | |
| db = b // 10 ** pos % 10 | |
| digit, borrow = self.transition(da, db, borrow, 1) | |
| result += digit * 10 ** pos | |
| if borrow != 0: | |
| raise ArithmeticError('Learned subtraction ended with a borrow.') | |
| return result | |
| def multiply_digit(self, n, digit): | |
| if not 0 <= digit <= 9: | |
| raise ValueError('Single-digit multiplier must be 0..9.') | |
| result, carry, pos = (0, 0, 0) | |
| while pos < len(str(n)) or carry: | |
| da = n // 10 ** pos % 10 | |
| out_digit, carry = self.transition(da, digit, carry, 2) | |
| result += out_digit * 10 ** pos | |
| pos += 1 | |
| return result | |
| def multiply(self, a, b): | |
| if a < 0 or b < 0: | |
| raise ValueError('Multiplication accepts nonnegative integers.') | |
| result = 0 | |
| for pos, ch in enumerate(str(b)[::-1]): | |
| partial = self.multiply_digit(a, int(ch)) | |
| for _ in range(pos): | |
| partial *= 10 | |
| result = self.add(result, partial) | |
| return result | |
| def divide(self, dividend, divisor): | |
| if dividend < 0 or divisor <= 0: | |
| raise ValueError('Division requires dividend >= 0, divisor > 0.') | |
| quotient, remainder = (0, 0) | |
| for ch in str(dividend): | |
| digit = int(ch) | |
| if divisor <= 9: | |
| qdigit, new_remainder = self.transition(digit, divisor, remainder, 3) | |
| else: | |
| current = int(str(remainder) + ch) | |
| qdigit = 0 | |
| for trial in range(9, -1, -1): | |
| product = self.multiply(divisor, trial) | |
| if product <= current: | |
| qdigit = trial | |
| break | |
| product = self.multiply(divisor, qdigit) | |
| new_remainder = self.subtract(current, product) | |
| quotient = quotient * 10 + qdigit | |
| remainder = new_remainder | |
| return (quotient, remainder) | |
| def save(self, path): | |
| np.savez(path, **self.p, experiences_seen=np.array(self.experiences_seen)) | |
| def signed_add(model, x, y, subtract=False): | |
| if subtract: | |
| y = -y | |
| sx = -1 if x < 0 else 1 | |
| sy = -1 if y < 0 else 1 | |
| ax, ay = (abs(x), abs(y)) | |
| if sx == sy: | |
| return sx * model.add(ax, ay) | |
| if ax >= ay: | |
| return sx * model.subtract(ax, ay) | |
| return sy * model.subtract(ay, ax) | |
| def signed_multiply(model, x, y): | |
| sign = -1 if (x < 0) != (y < 0) else 1 | |
| return sign * model.multiply(abs(x), abs(y)) | |
| def fraction_operation(model, left, right, op): | |
| left, right = (Fraction(left), Fraction(right)) | |
| if op == '+' or op == '-': | |
| n1 = signed_multiply(model, left.numerator, right.denominator) | |
| n2 = signed_multiply(model, right.numerator, left.denominator) | |
| numerator = signed_add(model, n1, n2, subtract=op == '-') | |
| denominator = model.multiply(left.denominator, right.denominator) | |
| return Fraction(numerator, denominator) | |
| if op == '*': | |
| numerator = signed_multiply(model, left.numerator, right.numerator) | |
| denominator = model.multiply(left.denominator, right.denominator) | |
| return Fraction(numerator, denominator) | |
| if op == '/': | |
| if right == 0: | |
| raise ZeroDivisionError('Division by zero.') | |
| numerator = signed_multiply(model, left.numerator, right.denominator) | |
| denominator = signed_multiply(model, left.denominator, right.numerator) | |
| return Fraction(numerator, denominator) | |
| raise ValueError('Unsupported operation.') | |
| def fraction_power(model, base, exponent): | |
| base = Fraction(base) | |
| exponent = Fraction(exponent) | |
| if exponent.denominator != 1: | |
| raise ValueError('Only integer powers are supported.') | |
| power = exponent.numerator | |
| if abs(power) > 20: | |
| raise ValueError('Power magnitude is limited to 20.') | |
| if power < 0: | |
| if base == 0: | |
| raise ZeroDivisionError('Zero cannot have a negative power.') | |
| base = Fraction(base.denominator, base.numerator) | |
| power = -power | |
| result = Fraction(1, 1) | |
| for _ in range(power): | |
| result = fraction_operation(model, result, base, '*') | |
| return result | |
| def tokenize(expression): | |
| cleaned = re.sub('\\s+', '', expression) | |
| tokens = re.findall('\\d+|[()+*/^%-]', cleaned) | |
| if ''.join(tokens) != cleaned: | |
| raise ValueError('Invalid character in expression.') | |
| return tokens | |
| def bodmas(model, expression): | |
| tokens = tokenize(expression) | |
| index = 0 | |
| def parse_expression(): | |
| nonlocal index | |
| value = parse_term() | |
| while index < len(tokens) and tokens[index] in ('+', '-'): | |
| op = tokens[index] | |
| index += 1 | |
| rhs = parse_term() | |
| value = fraction_operation(model, value, rhs, op) | |
| return value | |
| def parse_term(): | |
| nonlocal index | |
| value = parse_power() | |
| while index < len(tokens) and tokens[index] in ('*', '/'): | |
| op = tokens[index] | |
| index += 1 | |
| rhs = parse_power() | |
| value = fraction_operation(model, value, rhs, op) | |
| return value | |
| def parse_power(): | |
| nonlocal index | |
| value = parse_factor() | |
| if index < len(tokens) and tokens[index] == '^': | |
| index += 1 | |
| exponent = parse_power() | |
| value = fraction_power(model, value, exponent) | |
| return value | |
| def parse_factor(): | |
| nonlocal index | |
| if index >= len(tokens): | |
| raise ValueError('Incomplete expression.') | |
| if tokens[index] == '+': | |
| index += 1 | |
| return parse_factor() | |
| if tokens[index] == '-': | |
| index += 1 | |
| return -parse_factor() | |
| if tokens[index] == '(': | |
| index += 1 | |
| value = parse_expression() | |
| if index >= len(tokens) or tokens[index] != ')': | |
| raise ValueError('Missing closing parenthesis.') | |
| index += 1 | |
| return value | |
| token = tokens[index] | |
| if not token.isdigit(): | |
| raise ValueError(f'Unexpected token: {token}') | |
| index += 1 | |
| return Fraction(int(token), 1) | |
| result = parse_expression() | |
| if index != len(tokens): | |
| raise ValueError('Unexpected trailing tokens.') | |
| return result | |
| from importlib.resources import files | |
| class mathos: | |
| """Supernova Mathos V6.1 with bundled trained NumPy weights.""" | |
| def __init__(self): | |
| self.model = MathosV6(seed=77) | |
| weight_path = files("supernova_mathos").joinpath( | |
| "weights/supernova_mathos_v6_1.npz" | |
| ) | |
| with np.load(weight_path) as saved: | |
| self.model.p = { | |
| key: saved[key].copy() | |
| for key in ("W1", "b1", "Wd", "bd", "Ws", "bs") | |
| } | |
| if "experiences_seen" in saved: | |
| self.model.experiences_seen = int(saved["experiences_seen"]) | |
| def __getattr__(self, name): | |
| return getattr(self.model, name) | |
| def calculate(self, expression): | |
| return bodmas(self.model, expression) | |