

# This file was *autogenerated* from the file /home/user/checkit/dashboard/checkit/wrapper/wrapper.sage
from sage.all_cmdline import *   # import sage library

_sage_const_100000 = Integer(100000); _sage_const_999999 = Integer(999999); _sage_const_0 = Integer(0); _sage_const_1 = Integer(1); _sage_const_6 = Integer(6); _sage_const_3 = Integer(3); _sage_const_4 = Integer(4); _sage_const_1000 = Integer(1000); _sage_const_10 = Integer(10); _sage_const_1_000 = Integer(1_000); _sage_const_5 = Integer(5); _sage_const_2 = Integer(2)
import sys,json,os,datetime

# Library of helpful functions
class CheckIt:
    @staticmethod
    def vars(*latex_names, random_order=True):
        """
        Given one or more `latex_names` of strings, returns a tuple
        of Sage variables. `random_order` names them so that they appear
        in expressions in a random order.
        """
        stamp = randrange(_sage_const_100000 ,_sage_const_999999 )
        indices = list(range(len(latex_names)))
        if random_order:
            shuffle(indices)
        import string
        random_letter = choice(list(string.ascii_lowercase))
        return (var(f"{random_letter}_mi_var_{stamp}_{indices[i]}", latex_name=name) for i, name in enumerate(latex_names))

    @staticmethod
    def shuffled_equation(*terms):
        """
        Represents the equation sum(terms)==0, but with terms shuffled randomly
        to each side.
        """
        new_equation = (SR(_sage_const_0 )==_sage_const_0 )
        for term in terms:
            if choice([True,False]):
                new_equation += (SR(term)==_sage_const_0 )
            else:
                new_equation += (_sage_const_0 ==-SR(term))
        return new_equation*choice([-_sage_const_1 ,_sage_const_1 ])

    @staticmethod
    def shuffled_inequality(*terms,strict=True):
        """
        Represents the equation sum(terms)>0 or >=0, but with terms shuffled randomly
        to each side, and random direction of inequality
        """
        if choice([True,False]):
            if strict:
                new_equation = (SR(_sage_const_0 )>_sage_const_0 )
            else:
                new_equation = (SR(_sage_const_0 )>=_sage_const_0 )
            for term in terms:
                if choice([True,False]):
                    new_equation += (SR(term)==_sage_const_0 )
                else:
                    new_equation += (_sage_const_0 ==-SR(term))
        else:
            if strict:
                new_equation = (SR(_sage_const_0 )<_sage_const_0 )
            else:
                new_equation = (SR(_sage_const_0 )<=_sage_const_0 )
            for term in terms:
                if choice([True,False]):
                    new_equation += (-SR(term)==_sage_const_0 )
                else:
                    new_equation += (_sage_const_0 ==SR(term))
        return new_equation

    @staticmethod
    def latex_system_from_matrix(matrix, variables="x", alpha_mode=False, variable_list=[]):
        # Augment with zero vector if not already augmented
        if not matrix.subdivisions()[_sage_const_1 ]:
            matrix=matrix.augment(zero_vector(QQ, len(matrix.rows())), subdivide=true)
        num_vars = matrix.subdivisions()[_sage_const_1 ][_sage_const_0 ]
        # Start using requested variables
        system_vars = variable_list
        # Conveniently add xyzwv if requested
        if alpha_mode:
            system_vars += list(var("x y z w v"))
        # Finally fall back to x_n as needed
        system_vars += [var(f"{variables}_{n+_sage_const_1 }") for n in range(num_vars)]
        # Build matrix
        latex_output = "\\begin{matrix}\n"
        for row in matrix.rows():
            if row[_sage_const_0 ]!= _sage_const_0 :
                latex_output += latex(row[_sage_const_0 ]*system_vars[_sage_const_0 ])
                previous_terms = True
            else:
                previous_terms = False
            for n,cell in enumerate(row[_sage_const_1 :num_vars]):
                latex_output += " & "
                if cell < _sage_const_0 :
                    latex_output += " - "
                elif cell > _sage_const_0  and previous_terms:
                    latex_output += " + "
                latex_output += " & "
                if cell != _sage_const_0 :
                    latex_output += latex(cell.abs()*system_vars[n+_sage_const_1 ])
                if not previous_terms:
                    previous_terms = bool(cell!=_sage_const_0 )
            if not previous_terms:
                latex_output += " 0 "
            latex_output += " & = & "
            latex_output += latex(row[num_vars])
            latex_output += "\\\\\n"
        latex_output += "\\end{matrix}"
        return latex_output

    @staticmethod
    def latex_solution_set_from_matrix(matrix):
        # Augment with zero vector if not already augmented
        if not matrix.subdivisions()[_sage_const_1 ]:
            matrix=matrix.augment(zero_vector(QQ, len(matrix.rows())), subdivide=true)
        if (len(matrix.columns())-_sage_const_1 ) in matrix.pivots():
            return r" \{\} "
        solution_dimension = len(matrix.columns())-_sage_const_1 
        free_variables = list(var("a b c d e f g h i j"))
        kernel_basis=matrix.subdivision(_sage_const_0 ,_sage_const_0 ).right_kernel(basis='pivot').basis()
        span = sum([kernel_basis[i]*free_variables[i] for i in range(len(kernel_basis))])
        offset = zero_vector(QQ,solution_dimension)
        for row_index,col_index in enumerate(matrix.pivots()):
            offset[col_index] = matrix.rref().columns()[-_sage_const_1 ][row_index]
        rep = column_matrix(span+offset)
        predicate = ",".join([latex(a) for a in free_variables[:len(kernel_basis)]])
        return r" \left\{ " + latex(rep) + r" \,\middle|\, " + predicate + r" \in\mathbb R \right\} "

    @staticmethod
    def simple_random_matrix_of_rank(rank,rows=_sage_const_1 ,columns=_sage_const_1 ,augmented=False):
        # get extra rows and columns, at least zero
        extra_rows = max(_sage_const_0 ,rows-rank)
        extra_columns = max(_sage_const_0 ,columns-rank)
        # create matrix with terms between -5 and 5 inclusive, rank in every column, and integer entries RREF
        A = random_matrix(QQ,rank+extra_rows,rank,algorithm='echelonizable',rank=rank,upper_bound=_sage_const_6 )
        # randomly choose pivot indices where dependent columns are injected afterward
        inserts = [randrange(rank) for _ in range(extra_columns)]
        # pedagogically we want final column to be dependent at least half the time
        if extra_columns>_sage_const_0  and choice([True,False]):
            inserts[_sage_const_0 ]=rank-_sage_const_1 
        # we'll insert columns backwards to avoid messing up where to inject columns
        inserts.sort(reverse=True)
        # we won't repeat dependent columns
        inserted_columns = []
        for pivot in inserts:
            while True:
                # get random numbers for pivot rows
                rref_pivot_entries = [randrange(-_sage_const_3 ,_sage_const_4 ) for _ in range(pivot+_sage_const_1 )]
                # ensure at least one is nonzero
                rref_pivot_entries[randrange(pivot+_sage_const_1 )] = randrange(_sage_const_1 ,_sage_const_4 )*choice([-_sage_const_1 ,_sage_const_1 ])
                # create vector
                dependent_vector = sum([rref_pivot_entries[_]*A.column(_) for _ in range(pivot+_sage_const_1 )])
                if dependent_vector not in inserted_columns:
                    inserted_columns.append(dependent_vector)
                    A = matrix(A.columns()[:pivot+_sage_const_1 ]+[dependent_vector]+A.columns()[pivot+_sage_const_1 :]).transpose()
                    break
        if augmented:
            A.subdivide([],[columns-_sage_const_1 ])
        return A

# decorator to help authors avoid confusing .data() with .get_data() in a Generator
def provide_data(func):
    return lambda self: func(self.get_data())

# BaseGenerator class inherited by each outcome's Generator class to minimize boilerplate
class BaseGenerator:
    def __init__(self):
        self.__data = None
        self.__seed = None

    def data(self):
        return {}

    @provide_data
    def graphics(data):
        return None
    
    def roll_data(self,seed=None):
        if seed is None:
            set_random_seed()
            seed = randrange(_sage_const_1000 )
        self.__seed = seed
        set_random_seed(seed)
        self.__data = self.data()

    def get_data(self):
        data = self.__data
        data["__seed__"] = f"{self.__seed:04}"
        return self.__data

# converts SageMath objects into latexified strings
# note Python numbers are latexified into strings as well
def json_ready(obj):
    if isinstance(obj,str) or isinstance(obj,bool):
        return obj
    elif isinstance(obj,list):
        return [json_ready(item) for item in obj]
    elif isinstance(obj,dict):
        return {key:json_ready(obj[key]) for key in obj.keys()}
    else:
        return str(latex(obj))

# sage /path/to/wrapper.sage /path/to/generator.sage /path/to/output/seeds.json preview|build images?
if len(sys.argv) >= _sage_const_4 :
    # this script should be called from the root directory of the bank
    # so loads in the generator file work as intended
    generator_path = sys.argv[_sage_const_1 ]
    load(generator_path) # must provide Generator class extending BaseGenerator
    generator = Generator()

    # preview/build to specified JSON file
    if sys.argv[_sage_const_3 ].lower() == "preview":
        amount = _sage_const_10 
    else:
        amount = _sage_const_1_000 
    seeds = []
    for i in range(amount):
        if sys.argv[_sage_const_3 ].lower() == "preview":
            set_random_seed()
            seed_int = int(randrange(_sage_const_1_000 ))
        else:
            seed_int = int(i)
        gen_images = (len(sys.argv) >= _sage_const_5  and sys.argv[_sage_const_4 ]=="images")
        generator.roll_data(seed=seed_int)
        seed  = {"seed":seed_int,"data":json_ready(generator.get_data())}
        if gen_images:
            graphics = generator.graphics()
            if graphics is not None:
                directory = os.path.join(os.path.dirname(sys.argv[_sage_const_2 ]))
                if not os.path.exists(directory):
                    os.makedirs(directory)
                for filename in graphics:
                    seed_path = os.path.join(directory,f"{seed_int:04}")
                    if not os.path.exists(seed_path):
                        os.makedirs(seed_path)
                    graphics[filename].save(os.path.join(seed_path,f"{filename}.png"))
        seeds.append(seed)
    data = {
        "seeds": seeds,
        "generated_on": datetime.datetime.now(datetime.timezone.utc).isoformat(),
    }
    os.makedirs(os.path.dirname(sys.argv[_sage_const_2 ]), exist_ok=True)
    with open(os.path.join(sys.argv[_sage_const_2 ]), 'w') as f:
        json.dump(data, f)

