In [ ]:
import numpy as np
from fractions import Fraction as frac

def Matrix(*a):
    if len(a)==1 and isinstance(a[0], np.ndarray):
        a = a[0]
    return np.array([[frac(x) for x in r] for r in a])

def Vector(*a):
    if len(a)==1 and isinstance(a[0], np.ndarray):
        a = a[0]
    return np.array([frac(x) for x in a]).reshape(-1,1)
In [ ]:
# 巫術
from IPython.display import Latex, SVG, display
from IPython.core.interactiveshell import InteractiveShell
def frac_to_latex(self):
    if self._denominator == 1:
        return str(self._numerator)
    return "\\frac{{{}}}{{{}}}".format(self._numerator, self._denominator)
frac.__str__= frac_to_latex
frac._repr_latex_ = lambda x:"$"+frac_to_latex(x)+"$"
def ndarray_to_latex(arr): 
    if len(arr.shape)==1: 
        arr=arr.reshape(1,-1)
    if len(arr.shape) != 2:
        return None
    str_arr = np.vectorize(str)(arr)
    return r'\begin{{pmatrix}}{}\end{{pmatrix}}'.format(r'\\ '.join(map('&'.join, str_arr))) 
sh = InteractiveShell.instance()
sh.display_formatter.formatters['text/latex'].type_printers[np.ndarray]=ndarray_to_latex

def matrix_dot(A,B):
    if isinstance(A, np.ndarray):
        assert len(A.shape)==2==len(B.shape)
        return np.array([(A * x).sum(axis=1) for x in B.T]).T
    assert callable(A)
    if isinstance(B, np.ndarray):
        return A(B)
    assert callable(B)
    return lambda x:A(B(x))

import ast
def int_to_frac(x):
    if isinstance(x, int):
        return frac(x)
    return x
class NumberWrapper(ast.NodeTransformer):
    def visit_BinOp(self, node):
        node = self.generic_visit(node)
        left = node.left
        right = node.right
        if isinstance(node.op, ast.MatMult):
            return ast.Call(func=ast.Name(id='matrix_dot', ctx=ast.Load()),
                            args=[left, right], keywords=[])
        elif isinstance(node.op, ast.Div):
            right = ast.Call(func=ast.Name(id='int_to_frac', ctx=ast.Load()),
                            args=[right], keywords=[])
        return ast.BinOp(left, node.op, right)

    def visit_Num(self, node):
        if isinstance(node.n, float):
            print("convert",repr(node.n), )
            return ast.Call(func=ast.Name(id='frac', ctx=ast.Load()),
                            args=[ast.Str(str(node.n))], keywords=[])
        return node
sh.ast_transformers.append(NumberWrapper())
smiley=SVG('<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 5.8208332 5.8208335"><defs><linearGradient id="0" x1="488.2" y1="547.74" x2="488.11" y2="537.68" gradientUnits="userSpaceOnUse"><stop stop-color="#ffeb96"/><stop offset="1" stop-color="#fff1b7"/></linearGradient></defs><g transform="translate(0-291.18)"><g transform="matrix(.43294 0 0 .43294-209.18 68.12)"><circle cx="488.27" cy="542.35" r="5.5" fill="url(#0)" transform="translate(1.612-20.413)"/><g transform="translate(-6.818-.4)"><path d="m499.05 523.96c0 .07.783.139.779.207-.11 1.575-1.461 2.821-3.116 2.827-1.648.006-3.01-1.222-3.135-2.788-.006-.074.305-.15.304-.225l2.82-.022z" fill="#f7aa86"/><path d="m493.66 523.64h6.077c.049 0 .088.039.088.088v.385c0 .049-.001.206-.001.206h-6.234c0 0-.001-.157-.001-.206v-.385c0-.049.039-.088.088-.088" fill="#f3f3f3"/></g></g><g transform="translate(-.017)" fill="none" fill-rule="evenodd" stroke="#414141" stroke-linejoin="round" stroke-linecap="round" stroke-width=".146"><path d="m1.177 294.07c.138-.384.719-.384.845 0"/><path d="m3.834 294.07c.138-.384.719-.384.845 0"/></g></g></svg>')
def check_answer(result):
    if not result.all():
        print("Try again")
        return result
    return smiley