From 80743102a2536c1aacce3b7301d6ab7fddee6605 Mon Sep 17 00:00:00 2001 From: "matthias@arch" Date: Sun, 11 Sep 2022 01:27:55 +0200 Subject: [PATCH] Reworked math lib generation script Script can now generate matrices up to 9x9 --- generate_math_lib.py | 946 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 946 insertions(+) create mode 100644 generate_math_lib.py diff --git a/generate_math_lib.py b/generate_math_lib.py new file mode 100644 index 0000000..7342245 --- /dev/null +++ b/generate_math_lib.py @@ -0,0 +1,946 @@ +#!/bin/python3 +""" +A python script to generate a c++ vector and matrix library for mathematics. +Should work with vectors/matrix up to a length of 9. + +The script generates a header (and source) for each container (vector/matrix). +There are overloads for each container which allow convenient usage. +Eg multiplication with matrix rules is as easy as: (example) +mat3x2 = mat3x3 * mat3x2 +n = rvec4 * vec4 +To be accurate, there is a differentiation between row and column vector, they are basically the same but each has a separate class which differ only for the matrix multiplication overloads. + +To help compile times, the templated definitions of most methods is in the source file (by default). +That means that all templates must be explictly instantiated beforehand. The instantiations are in the tpp file included by the cpp files. +Specify the needed types by adding them to templateInstantiationIntTypes and templateInstantiationFloatTypes respectively (the differentiation is needed for the modulo operator). + +All that makes the generated library is not memory efficient, but very fast and convenient. + +AS OF NOW, THIS SOFTWARE IS NEARLY UNTESTED AND BOUND TO CONTAIN LOTS OF BUGS. + +Copyright © 2022 Matthias Quintern. +This software comes with no warranty. +This software is licensed under the GPL3 +""" + +from os import path, mkdir +# +# SETTINGS +# +# generate up to MAX_N (-> vecN, matNxN) +MAX_N = 4 +letters_ = { + 2: [ "x", "y" ], + 3: [ "x", "y", "z" ], + 4: [ "x", "y", "z", "w"], +} +directory = "math" +# +# \t or x-spaces +tab = "\t" +# float, double, long double... +float_type = "float" +# string or None +namespace = "gz" +typeShorts = { + "float": "f", + "double": "d", + "int": "i", + "unsigned int": "u", +} +include_static_asserts = True +include_template_instantiations = True +templateInstantiationIntTypes = [ # these must be usable with % + # "uint8_t" + "uint8_t", "uint16_t", "uint32_t", "uint64_t", + "int8_t", "int16_t", "int32_t", "int64_t", +] +templateInstantiationFloatTypes = [ + # "float", + "float", "double", "long double" +] +templateInstantiationTypes = templateInstantiationFloatTypes + templateInstantiationIntTypes + + +headerIncludes = [ "\"concepts.hpp\"", "\"../container/iterator.hpp\"" ] +sourceIncludes = ["", "", ""] + + +# shorthands +Number_N = ("Number", "N") +Class_T = [("Number", "T")] +constexpr = "constexpr" +inline = "inline" + + +def template(t:list[tuple[str, str]]): + if len(t) == 0: + return "" + s = "template<" + for temp in t: + s += temp[0] + " " + temp[1] + ", " + return s.removesuffix(", ") + ">\n" + + +class Member: + def __init__(self, class_:str, name:str, c_temp:list=[], m_temp:list=[], prefixes:list=[], ret_type:str="", args:str="", init:str="", const=False, body:list=[], comment:str=""): + """ + :param class: class name + :param name: member name + :param c_temp: list of templates of the class + :param m_temp: list of templates of the member + :param prefixes: things like constexpr, inline... + :param ret_type: return type of the member + :param args: arguments of the member + :param init: initializion of the member + :param const: True if the method does not change the object + :param body: method body + :param comment: a single-line docstring + + template + class class { + comment + template + prefixes ret_type name(args) const : init { + body + } + } + """ + self.class_ = class_ + self.name = name + self.prefixes = prefixes + self.c_temp = c_temp + self.m_temp = m_temp + self.ret_type = "" + if ret_type: self.ret_type = ret_type + " " + self.args = args + self.init = "" + if init: self.init = f"\n{tab} : {init} " + self.const = "" + if const: self.const = " const" + self.body = body + self.comment = "" + if comment: self.comment = "/// " + comment + "\n" + + def getArgsAsList(self): + args = [] + const = False + arglist = self.args.split(", ") + for i in range(len(arglist)): + if "const" in arglist[i]: + arglist[i] = arglist[i].replace("const ", "") + const = True + args.append(arglist[i].split(" ")) + if const: + args[-1][0] = "const " + args[-1][0] + return args + + def defintionH(self, depth:int=0): + """ + Return the defition to be used in a header + """ + s = "" + s += self.comment + s += template(self.m_temp) + for p in self.prefixes: + s += p + " " + s += self.ret_type + self.name + f"({self.args}){self.const};\n" + return transformString(s, depth) + + def source(self, depth:int=0): + """ + Return the defition + declarations for a source file + """ + s = "" + class_ = self.class_ + s += template(self.c_temp) + if len(self.c_temp) > 0: + class_ = self.class_ + "<" + for t in self.c_temp: + class_ += f"{t[1]}, " + class_ = class_.removesuffix(", ") + ">" + + s += template(self.m_temp) + for p in self.prefixes: + s += p + " " + s += f"{self.ret_type}{class_}::{self.name}({self.args}){self.const}{self.init}" + if self.body: + s += " {\n" + body = "" + for l in self.body: body += l + "\n" + s += transformString(body, depth) + s += "}\n" + else: + s += "{}" + return transformString(s, depth) + + def header(self, depth:int=0): + """ + Return the defition + declarations for a header file + """ + s = "" + s += self.comment + s += template(self.m_temp) + for p in self.prefixes: + s += p + " " + s += f"{self.ret_type}{self.name}({self.args}){self.const}{self.init}" + if self.body: + s += " {\n" + body = "" + for l in self.body: body += l + "\n" + s += transformString(body, depth+1) + s += "}\n" + else: + s += "{}\n" + return transformString(s, depth) + + def isConstructor(self): + return self.class_ == self.name + + def templateInstantiations(self): + """ + generate instantiations for all templates + right now only works with Number concepts + temp + temp + void Class::operator+(class) { + -> + temp<> void Class::operator+(class) + ... + """ + if len(self.m_temp) == 0: + return "" + class_ = self.class_ + if len(self.c_temp) > 0: + class_ = self.class_ + "<" + for t in self.c_temp: + class_ += f"{t[1]}, " + class_ = class_.removesuffix(", ") + ">" + + arglist = self.getArgsAsList() + args = "" + for arg in arglist: + args += arg[0] + ", " + args = args.removesuffix(", ") + name = self.name.removesuffix(" ") + if len(self.m_temp) > 0: + name += " <" + for type_ in self.m_temp: + name += type_[1] + ", " + name = name.removesuffix(", ") + ">" + + method = f"{self.ret_type}{class_}::{name}({args}){self.const}" + instantiations = [] + if len(self.c_temp) > 0 or len(self.m_temp) > 0: + if "%" in method: types = templateInstantiationIntTypes + else: types = templateInstantiationTypes + getTemplateInsts(method, instantiations, self.c_temp + self.m_temp, types, 0) + s = "" + for spec in instantiations: + s += spec + "\n" + return s + "\n"; + + +# +# HELPERS +# +def classname(row, col, template=""): + name = "" + if template and col == 1 and row == 1: + return template + elif col == 1: + name = "vec" + str(row) + elif row == 1: + name = "rvec" + str(col) + else: + name = "mat" + str(row) + "x" + str(col); + if template: + name += "<" + template + ">" + return name + + +def concept(row, col, template=""): + name = "" + if template and col == 1 and row == 1: + return template + elif col == 1: + name = "ColumnVector" + str(row) + elif row == 1: + name = "RowVector" + str(col) + else: + name = "Mat" + str(row) + "x" + str(col); + if template: + name += "<" + template + ">" + return name + + +def classnameFromConcept(concept:str): + name = "" + number = "" + if concept.startswith("ColumnVector"): + name = "vec" + number = concept.removeprefix("ColumnVector") + elif concept.startswith("RowVector"): + name = "rvec" + number = concept.removeprefix("RowVector") + elif concept.startswith("Mat"): + name = "mat" + number = concept.removeprefix("Mat") + return name + number + + +def varname(r, c, rows, cols): + """Get the name of the variable in row r and column c""" + if rows == 1: + if cols in letters_.keys(): + return letters_[cols][c] + else: + return f"x{r+1}" + elif cols == 1: + if rows in letters_.keys(): + return letters_[rows][r] + else: + return f"x{c+1}" + else: + return f"x{r+1}_{c+1}" + + +def varnameI(i, rows, cols): + """Get the name of the ith variable""" + if rows == 1: + if cols in letters_.keys(): + return letters_[cols][i%cols] + else: + return f"x{i+1}" + elif cols == 1: + if rows in letters_.keys(): + return letters_[rows][i%rows] + else: + return f"x{i+1}" + else: + return f"x{i%rows+1}_{i%cols+1}" + + + +def genstring(row: int, col: int, template: str, sep=", "): + """ + Generate a string from a template for each vector component + eg genstring(3, "@ + ") -> x + y + z + @C -> varname(1, c, 1, col) + @R -> varname(r, 1, row, 1) + @ -> varname(r, c, row, col) + @c -> c + @r -> r + + """ + s = "" + for r in range(row): + for c in range(col): + s += template.replace("@r", str(r)).replace("@c", str(c)).replace("@R", varname(r, c, row, 1)).replace("@C", varname(r, c, 1, col)).replace("@", varname(r, c, row, col)) + sep + + s.removesuffix(sep) + return s[0:len(s)-len(sep)] + + +def transformString(s: str, depth: int): + """Add tabs after all but the last line break and before the first char""" + return depth * tab + s.replace("\n", "\n" + depth * tab, s.count("\n") - 1) + + +def getPossiblities(i, a, depth=0): + """ + get all possibilites to get i by addition of numbers lower than i + eg i=3 -> 3, 2+1, 1+2, 1+1+1 + """ + if i == 0: + return + if i == 1: + a.append(str(1)) + return + for j in range(1, i): + b = [] + # print("\t" * depth + "gp: i="+str(i)+" j="+str(j)) + getPossiblities(i-j, b, depth+1) + + for poss in b: + # print("\t"*depth + f"{i}-{j} returned", b) + a.append(str(j) + poss) + a.append(str(i)) + + + +# +# GENERATORS +# +def genConstructors(row, col): + constructors = [] + class_ = classname(row, col) + + # default + constructors.append(Member(class_, class_, c_temp=Class_T, prefixes=[constexpr], init=genstring(row, col, "@(0)"), comment="Default constructor")) + + # from single scalar + constructors.append(Member(class_, class_, c_temp=Class_T, prefixes=[constexpr], m_temp=[["Number", "N"]], args="const N n", init=genstring(row, col, "@(static_cast(n))"), comment="Create from scalar, all components will have value n")) + + # generate a constructor for every possible vector initialization + if row == 1 or col == 1: + rows_or_cols = max(row, col) # vec and rvec share same variable names + size = max(row, col) + a = [] + getPossiblities(size, a) + for possiblity in a: + n_count = 1 # counts how many numbers appear + v_count = 1 # counts how many vectors appear + i = 0 # index the member variable to initialize + comment = "Create from " + templates = [] + args = "" + init = "" + + for nstr in possiblity: + n = int(nstr) + if n == 1: # if number + comment += "n " + templates.append(("Number", f"N{n_count}")) + args += f"N{n_count} n{n_count}, " + init += varnameI(i, row, col) + f"(static_cast(n{n_count})), " + n_count += 1 + i += 1 + else: # if vector + comment += f"vec{n} " + args += f"const V{v_count}& v{v_count}, " + templates.append((f"Vector{rows_or_cols}", f"V{v_count}")) + for j in range(n): + init += varnameI(i, rows_or_cols, 1) + "(static_cast(v" + str(v_count) + "." + varnameI(j, rows_or_cols, 1) + ")), " + i += 1 + v_count += 1 + args = args.removesuffix(", ") + init = init.removesuffix(", ") + constructors.append(Member(class_, class_, c_temp=Class_T, m_temp=templates, prefixes=[constexpr], args=args, init=init, comment=comment)) + # constructor for all matrices + else: + # from scalar + constructors.append(Member(class_, class_, c_temp=Class_T, m_temp=[ t.split(" ") for t in genstring(row, col, f"Number N@r_@c").split(", ")], prefixes=[constexpr], + args=genstring(row, col, "const N@r_@c @"), init=genstring(row, col, "@(static_cast(@))"), comment="Create from scalars")) + + # from row vec + constructors.append(Member(class_, class_, c_temp=Class_T, m_temp=[ t.split(" ") for t in genstring(row, 1, f"RowVector{col} V@r").split(", ")], prefixes=[constexpr], + args=genstring(row, 1, "const V@r& v@r"), init=genstring(row, col, "@(static_cast(v@r.@C))"), comment="Create from row vectors")) + + # from col vec + constructors.append(Member(class_, class_, c_temp=Class_T, m_temp=[ t. split(" ") for t in genstring(1, col, f"ColumnVector{row} V@c").split(", ")], prefixes=[constexpr], + args=genstring(1, col, "const V@c& v@c"), init=genstring(row, col, "@(static_cast(v@c.@R))"), comment="Create from column vectors")) + + return constructors + + +def genValues(row, col): + s = genstring(row, col, "T @;\n", "") + return s + +def genAssignment(row, col): + # TODO opt idea: memcpy for component wise assignment when types have same size + members = [] + members.append(Member(classname(row, col), "operator=", c_temp=Class_T, m_temp=[Number_N], prefixes=[constexpr], ret_type="void", + args=f"const {classname(row, col, 'N')}& other", body=genstring(row, col, f"\t@ = static_cast(other.@);\n", "").split("\n"), comment="Component-wise assignment")) + + members.append(Member(classname(row, col), "operator=", c_temp=Class_T, m_temp=[Number_N], prefixes=[constexpr], ret_type="void", + args="const N& other", body=genstring(row, col, f"\t@ = static_cast(other);\n", "").split("\n"), comment="Assignment from scalar")) + + return members + + + +ops = { + "+": "operator+", + "-": "operator-", + "%": "operator%", + "*": "compWiseMult", + "/": "compWiseDiv", +} +as_ops = { + "+=": "operator+=", + "-=": "operator-=", + "%=": "operator%=", + "*=": "compWiseAssMult", + "/=": "compWiseAssDiv", +} +def genArithmeticVectorial(row, col): + operators = [] + for op, opname in ops.items(): + body = "return " + classname(row, col, "T") + "(" + genstring(row, col, f"@ {op} static_cast(other.@)") + ");" + operators.append(Member(classname(row, col), opname, c_temp=Class_T, m_temp=[(concept(row, col), concept(row, col)[0])], prefixes=[constexpr], ret_type=classname(row, col, "T"), + args=f"const {concept(row, col)[0]}& other", const=True, body=[body], comment=f"Component-wise {op}")) + + for op, opname in as_ops.items(): + body = genstring(row, col, f"\t@ {op} static_cast(other.@);", "\n") + operators.append(Member(classname(row, col), opname, c_temp=Class_T, m_temp=[(concept(row, col), concept(row, col)[0])], prefixes=[constexpr], ret_type="void", + args=f"const {concept(row, col)[0]}& other", body=body.split("\n"), comment=f"Component-wise assignment {op}")) + return operators + +def genArithmeticScalar(row, col): + operators = [] + for op, opname in ops.items(): + body = "return " + classname(row, col, "T") + "(" + genstring(row, col, f"@ {op} static_cast(other)") + ");" + operators.append(Member(classname(row, col), opname, c_temp=Class_T, m_temp=[Number_N], prefixes=[constexpr], ret_type=classname(row, col, "T"), + args=f"const N& other", const=True, body=[body], comment=f"Component-wise {op} with scalar")) + for op, opname in as_ops.items(): + body = genstring(row, col, f"\t@ {op} static_cast(other);", "\n") + operators.append(Member(classname(row, col), opname, c_temp=Class_T, m_temp=[Number_N], prefixes=[constexpr], ret_type="void", + args=f"const N& other", body=body.split("\n"), comment=f"Component-wise assignment {op} from scalar")) + return operators + + +def genComparisonVectorial(row, col): + operators = [] + for op in ["==", "<", ">"]: + body = "return " + genstring(row, col, f"@ {op} other.@", " and ") + ";" + operators.append(Member(classname(row, col), f"operator{op}", c_temp=Class_T, m_temp=[(concept(row, col), concept(row, col)[0])], prefixes=[constexpr], ret_type="bool", + args=f"const {concept(row, col)[0]}& other", const=True, body=[body], comment=f"Component-wise comparison {op} (and)")) + for op, antiop in { "!=": "==", "<=": ">", ">=": "<" }.items(): + body = "return " + genstring(row, col, f"@ {antiop} other.@", " and ") + ";" + operators.append(Member(classname(row, col), f"operator{op}", c_temp=Class_T, m_temp=[(concept(row, col), concept(row, col)[0])], prefixes=[constexpr], ret_type="bool", + args=f"const {concept(row, col)[0]}& other", const=True, body=[body], comment=f"Component-wise comparison {op} (and)")) + return operators + +def genComparisonScalar(row, col): + operators = [] + for op in ["==", "<", ">"]: + body = "return " + genstring(row, col, f"@ {op} other", " and ") + ";" + operators.append(Member(classname(row, col), f"operator{op}", c_temp=Class_T, m_temp=[Number_N], prefixes=[constexpr], ret_type="bool", + args=f"const N& other", const=True, body=[body], comment=f"Component-wise comparison {op} (and)")) + for op, antiop in { "!=": "==", "<=": ">", ">=": "<" }.items(): + body = "return " + genstring(row, col, f"@ {antiop} other", " and ") + ";" + operators.append(Member(classname(row, col), f"operator{op}", c_temp=Class_T, m_temp=[Number_N], prefixes=[constexpr], ret_type="bool", + args=f"const N& other", const=True, body=[body], comment=f"Component-wise comparison {op} (and)")) + return operators + + + +def getDependecyClasses(row, col): + """ + Get all class names that row,col class depends on for matrix multplication and the .row() and .column() methods + The dependencies will be forward declared in the header and included in the source + """ + deps = [] + # for .row(), .column() + if row > 1 and col > 1: + deps.append(classname(row, 1)) + deps.append(classname(1, col)) + + # for matrix mult + for i in range(1, MAX_N + 1): + # results from matrix calculations + deps.append(classname(row, i)) + + # operands from matrix calculations + deps.append(classname(col, i)) + + deps = list(dict.fromkeys(deps)) + # remove vec1 + if classname(1, 1) in deps: + deps.remove(classname(1, 1)) + if classname(row, col) in deps: + deps.remove(classname(row, col)) + deps.sort() + return deps + + +def genArithmeticMatrix(row, col): + """ + Generate all matrix multiplications. + dimensions of result = mat(row, other.col) where col = other.row + """ + operators = [] + other_row = col + other_cols = [ i for i in range(1, MAX_N + 1) ] + for other_col in other_cols: + if (row == 1 and other_col == 1) or (other_row == 1 and other_col == 1): # if ret_type or arg is a scalar + continue + body = classname(row, other_col, "T") + " new_;\n" + for r in range(row): + for o_c in range(other_col): + body += "new_." + varname(r, o_c, row, other_col) + " = " + for i in range(col): + # print(f"genArithmeticMatrix: {row}x{col} * {other_row}x{other_col}", r, o_c, "->", c) + body += varname(r, i, row, col) + " * other." + varname(i, o_c, other_row, other_col) + " + " + body = body.removesuffix(" + ") + ";\n" + # if scalar, put ret_type creation and assignment in same line + body = body.replace("new_;\nnew_." + varname(0, 0, 1, 1) + " =", "new_ =") + "return new_;" + arg_concept = concept(other_row, other_col) + operators.append(Member(classname(row, col), "operator*", c_temp=Class_T, m_temp=[(arg_concept, arg_concept[0])], prefixes=[constexpr], ret_type=classname(row, other_col, "T"), const=True, + args=f"const {arg_concept[0]}& other", body=body.split("\n"), comment=f"Matrix multiplication with {classname(other_row, other_col)} -> {classname(row, other_col)}")) + + return operators + + +def genSubscription(row, col): + operators = [] + # [i] + operators.append(Member(classname(row, col), "operator[]", c_temp=Class_T, prefixes=[constexpr], ret_type="T", + args="std::size_t i", const=True, body=[f"return *(&{varname(0, 0, row, col)} + sizeof(T) * i);"], comment="Get the ith element. i starts at 0")) + + if row > 1 and col > 1: + # at(row, col) + body = f"return *(&{varname(0, 0, row, col)} + (row * {row} + col * {col}) * sizeof(T));" + operators.append(Member(classname(row, col), "at", c_temp=Class_T, prefixes=[constexpr], ret_type="T", + args="std::size_t row, std::size_t col", const=True, body=[body], comment="Get the element at row and col. row and col start at 0")) + + # row() + body = f"return {classname(1, col, 'T')}(" + genstring(1, col, f"*(&{varname(0, 0, row, col)} + ({row} * @c + i) * sizeof(T))") + ");" + operators.append(Member(classname(row, col), "row", c_temp=Class_T, prefixes=[constexpr], ret_type=classname(1, col, 'T'), + args="std::size_t i", const=True, body=[body], comment="Get the ith row as column vector. i starts at 0")) + + # column() + body = f"return {classname(row, 1, 'T')}(" + genstring(row, 1, f"*(&{varname(0, 0, row, col)} + ({col} * @r + i) * sizeof(T))") + ");" + operators.append(Member(classname(row, col), "column", c_temp=Class_T, prefixes=[constexpr], ret_type=classname(row, 1, 'T'), + args="std::size_t i", const=True, body=[body], comment="Get the ith column as row vector. i starts at 0")) + + return operators + + +def genIterator(row, col): + members = [] + its = [ + ("cbegin", "return Iterator(const_cast(&" + varname(0, 0, row, col) + "));", "Return an Iterator to the first element"), + ("cend", "return Iterator(const_cast(&" + varname(row-1, col-1, row, col) + " + sizeof(T)));", "Return an Iterator past the last element"), + ("begin", "return Iterator(const_cast(&" + varname(0, 0, row, col) + "));", "Return an Iterator to the first element"), + ("end", "return Iterator(const_cast(&" + varname(row-1, col-1, row, col) + " + sizeof(T)));", "Return an Iterator past the last element"), + ] + for name, body, comment in its: + members.append(Member(classname(row, col), name, c_temp=Class_T, prefixes=[constexpr], ret_type="Iterator", const=True, body=[body], comment=comment)) + return members + + +def genFunctional(row, col): + members = [] + # abs + members.append(Member(classname(row, col), "abs", c_temp=Class_T, prefixes=[constexpr, inline], ret_type=float_type, + const=True, body=["return std::sqrt(" + genstring(row, col, f"static_cast<{float_type}>(@ * @)", " + ") + ");"], comment="Returns the absolute value of the vector (sqrt of scalar product with itself)")) + + if (row == 2 and col == 1) or (col == 2 and row == 1): + # ratio + members.append(Member(classname(row, col), "ratio", c_temp=Class_T, prefixes=[constexpr, inline], ret_type=float_type, + const=True, body=["return static_cast<" + float_type + f">({varnameI(0, row, col)}) / {varnameI(1, row, col)};"], comment=f"Returns x/y")) + + # inverse ratio + members.append(Member(classname(row, col), "invereseRatio", c_temp=Class_T, prefixes=[constexpr, inline], ret_type=float_type, + const=True, body=["return static_cast<" + float_type + f">({varnameI(1, row, col)}) / {varnameI(0, row, col)};"], comment=f"Returns y/x")) + + + # min + members.append(Member(classname(row, col), "min", c_temp=Class_T, prefixes=[constexpr, inline], ret_type="T", const=True, + body=["return *std::min_element(cbegin(), cend());"], comment="Returns the min of the components")) + + # max + members.append(Member(classname(row, col), "max", c_temp=Class_T, prefixes=[constexpr, inline], ret_type="T", const=True, + body=["return *std::max_element(cbegin(), cend());"], comment="Returns the max of the components")) + + # dot + body = f"return " + genstring(row, col, "@ * static_cast(other.@)", " + ") + ";" + members.append(Member(classname(row, col), "dot", c_temp=Class_T, m_temp=[Number_N], prefixes=[constexpr, inline], ret_type="T", const=True, + args=f"const {classname(row, col, 'N')}& other", body=[body], comment="Scalar product (x1 * other.x1 + x2 * other.x2 ...)")) + return members + + +def genUtility(row, col): + s = "\n" + # s = "std::string to_string() const { return \"(\" + " + genstring(row, col, "std::to_string(@)", " + \", \" + ") + " + \")\"; };\n" + return s + + +def genUsing(row, col): + global typeShorts + s = "" + for t, c in typeShorts.items(): + s += f"using {classname(row, col)}{c} = {classname(row, col)}<{t}>;\n" + return s + + + +# +# TEMPLATE INSTANTIATION +# +def getTemplateInsts(method_name, instantiations, templates, types, depth=0): + """ method name must be like this: + constexpr ret_type Class::name(int, U) const;""" + if depth < len(templates): + if templates[depth][0] == "Number": + for n in types: + newmethod = method_name.replace( + f"<{templates[depth][1]}>", f"<{n}>").replace( + f"{templates[depth][1]} ", f"{n} ").replace( + f"{templates[depth][1]})", f"{n})").replace( + f"{templates[depth][1]},", f"{n},").replace( + f"{templates[depth][1]}& ", f"{n}& ").replace( + f"{templates[depth][1]}&)", f"{n}&)").replace( + f"{templates[depth][1]}&,", f"{n}&,") + getTemplateInsts(newmethod, instantiations, templates, types, depth+1) + else: + for n in types: + newmethod = method_name.replace( + f"<{templates[depth][1]}>", f"<{classnameFromConcept(templates[depth][0])}<{n}>>").replace( + f"{templates[depth][1]} ", f"{classnameFromConcept(templates[depth][0])}<{n}> ").replace( + f"{templates[depth][1]})", f"{classnameFromConcept(templates[depth][0])}<{n}> ").replace( + f"{templates[depth][1]},", f"{classnameFromConcept(templates[depth][0])}<{n}> ").replace( + f"{templates[depth][1]}& ", f"{classnameFromConcept(templates[depth][0])}<{n}>& ").replace( + f"{templates[depth][1]}&)", f"{classnameFromConcept(templates[depth][0])}<{n}>&)").replace( + f"{templates[depth][1]}&,", f"{classnameFromConcept(templates[depth][0])}<{n}>&,") + getTemplateInsts(newmethod, instantiations, templates, types, depth+1) + elif depth == len(templates) and len(templates)> 0: + instantiations.append("template " + method_name + ";") + + +# +# GENERATION +# +def generateFiles(row, col): + hd = 0 # header depth + sd = 0 # source and template depth + + # header + h = "#pragma once\n\n" + # source + s = "#include \"" + classname(row, col) + ".hpp\"\n\n" + # template instantiations + t = f"// template instantiations for {classname(row, col)}\n\n" + + dependencies = getDependecyClasses(row, col) + for dep in dependencies: + s += "#include \"" + dep + ".hpp\"\n" + s += "\n" + for i in headerIncludes: + h += "#include " + i + "\n" + h += "\n" + for i in sourceIncludes: + s += "#include " + i + "\n" + s += "\n" + if namespace: + h += "namespace " + namespace + " {\n\n" + s += "namespace " + namespace + " {\n\n" + t += "namespace " + namespace + " {\n\n" + hd += 1 + sd += 1 + + # template instantiations + if include_template_instantiations: + for typ in templateInstantiationTypes: + t += transformString("template class " + classname(row, col, typ) + ";\n", 0) + t += "\n" + + # forward declarations + h += transformString("// Forward declarations\n", hd) + for dep in dependencies: + h += transformString("template\nclass " + dep + ";\n", hd) + h += "\n" + + header, source, templates = generateMatrix(row, col, hd, sd) + h += header + "\n" + s += source + "\n" + t += templates + "\n" + + if include_static_asserts: + h += transformString("static_assert(" + concept(row, col, classname(row, col, "int")) +", \"" + classname(row, col, "int") + " does not satisfy the concept " + concept(row, col) + "\");\n", hd) + + if namespace: + hd -= 1 + sd -= 1 + h += transformString("} // namespace " + namespace + "\n", hd) + s += transformString("} // namespace " + namespace + "\n", hd) + t += transformString("} // namespace " + namespace + "\n", hd) + + for i in range(1, 5): + h = h.replace("\n" + i * tab + "\n", "\n\n") + s = s.replace("\n" + i * tab + "\n", "\n\n") + + return h, s, t + + +def append(header:str, source:str, templates:str, members:list[Member], hdepth:int, sdepth:int): + for m in members: + split = True + if m.isConstructor() or (len(m.body) < 2 and len(m.m_temp) > 1): + split = False + + if split: + header += m.defintionH(hdepth) + source += m.source(sdepth) + templates += m.templateInstantiations() + else: + header += m.header(hdepth) + return header, source, templates + + + + +def generateMatrix(r, c, hd, sd): + h = transformString( +"""/** + * @brief Class containing $ numbers + */ + """.replace("$", str(r*c)), hd) + h += transformString("""template +class $ { + public: +""".replace("$", classname(r, c)), hd) + + s = transformString("""// +// CLASS $ +// +""".replace("$", classname(r, c)), sd) + + + t = "" + + hd += 1 # header depth + # sd += 1 # source depth + + if classname(r, c).startswith("vec"): + h += transformString("/// just to satisfy concept ColumnVector\nusing isColumnVector = T;\n", hd + 1) + elif classname(r, c).startswith("rvec"): + h += transformString("/// just to satisfy concept RowVector\nusing isRowVector = T;\n", hd + 1) + + h += transformString("// Constructors\n", hd) + h, s, t = append(h, s, t, genConstructors(r, c), hd+1, sd) + + h += transformString("// Values\n", hd) + h += transformString(genValues(r, c), hd+1) + + h += transformString("// Assignment\n", hd) + h, s, t = append(h, s, t, genAssignment(r, c), hd+1, sd) + + h += transformString("// Arithmetic\n", hd-1) + h += transformString("// Vectorial\n", hd) + h, s, t = append(h, s, t, genArithmeticVectorial(r, c), hd+1, sd) + h += transformString("// Scalar\n", hd) + h, s, t = append(h, s, t, genArithmeticScalar(r, c),hd+1, sd) + h += transformString("// Matrix Multiplication\n", hd) + h, s, t = append(h, s, t, genArithmeticMatrix(r, c), hd+1, sd) + + h += transformString("// Comparison\n", hd-1) + h += transformString("// Vectorial\n", hd) + h, s, t = append(h, s, t, genComparisonVectorial(r, c),hd+1, sd) + h += transformString("// Scalar\n", hd) + h, s, t = append(h, s, t, genComparisonScalar(r, c), hd+1, sd) + + + h += transformString("// Functional\n", hd - 1) + h, s, t = append(h, s, t, genFunctional(r, c), hd+1, sd) + + h += transformString("// Utility\n", hd-1) + h, s, t = append(h, s, t, genSubscription(r, c), hd+1, sd) + h, s, t = append(h, s, t, genIterator(r, c), hd + 1, sd) + + h += transformString("}; // " + classname(r, c) + "\n\n", hd - 1) + h += transformString(genUsing(r, c), hd - 1) + h += "\n" + s += "\n" + t += "\n" + + return h, s, t + + +# +# CONCEPTS +# +def generateConceptsFile(row, col): + # Number + s = "#pragma once\n\n" + s += "#include \n\n" + s += "// Number\n" + s += "template\n" + s += "concept Number = std::integral or std::floating_point;\n\n" + + s += "// VectorX\n" + for i in range(2, max(row, col)): + s += "template\n" + # s += "concept Vector" + str(i) + " = requires(T t) { " + genstring(i, 1, "{ t.@ } -> Number; ", "") + "};\n\n" + s += "concept Vector" + str(i) + " = requires(T t) {\n" + genstring(i, 1, tab + "requires Number;\n", "") + "} and requires(T t) { sizeof(t." + varname(0, 0, i, 1) + ") == (sizeof(T) * " + str(i) + "); };\n\n" + s += "// RowVectorX\n" + for i in range(2, max(row, col)): + s += "template\n" + s += "concept RowVector" + str(i) + " = Vector" + str(i) + " and Number;\n\n" + s += "// ColumnVectorX\n" + for i in range(2, max(row, col)): + s += "template\n" + s += "concept ColumnVector" + str(i) + " = Vector" + str(i) + " and Number;\n\n" + s += "// Matrices\n" + for r in range(2, row): + for c in range(2, col): + s += "template\n" + # s += "concept " + concept(r, c) + " = requires(T t) { t -> template " + classname(r, c) + "; };\n" + s += "concept " + concept(r, c) + " = requires(T t) {\n" + genstring(r, c, tab + "requires Number;\n", "") + "};\n" # and requires(T t) { sizeof(t." + varname(0, 0, r, c) + ") == (sizeof(T) * " + str(r*c) + "); };\n\n" + return s + + +## +## ITERATORS +## +#def generateIteratorFile(): +# i = "#pragma once\n" +# if namespace: +# i += f"namespace {namespace} " + "{\n" + +# i += """ + +#template +#struct Iterator { +# public: +# using value_type = T; +# Iterator() : ptr(nullptr) {}; +# Iterator(T* ptr) : ptr(ptr) {}; +# T& operator*() const { return *ptr; }; +# Iterator& operator=(const Iterator& other) { +# ptr = other.ptr; +# return *this; +# }; +# Iterator& operator++() { ptr += sizeof(T); return *this; }; +# Iterator operator++(int) { auto copy = *this; ptr += sizeof(T); return copy; }; +# friend int operator-(Iterator lhs, Iterator rhs) { +# return lhs.ptr - rhs.ptr; +# }; +# bool operator==(const Iterator& other) const { return ptr == other.ptr; }; +# // bool operator!=(const Iterator& other) const { return ptr != other.ptr; }; +# private: +# T* ptr; +#}; // Iterator +#""" + +# if include_static_asserts: +# i += 'static_assert(std::forward_iterator>, "Iterator not a forward range.");' +# if namespace: +# i += "} // namespace " + namespace + "\n" +# return i + + + +# +# MAIN +# +def write_file(s, filename): + if path.exists(filename): + answer = "y" + # answer = input("File " + filename + "already exists. Overwrite? (y/n): ") + if answer not in "yY": + return + + with open(filename, "w") as file: + file.write(s) + + +if __name__ == "__main__": + if not path.isdir(directory): + mkdir(directory) + todo = [ [i, j] for i in range(1, MAX_N + 1) for j in range(1, MAX_N + 1)] + todo.remove([1, 1]) # remove [1, 1] + for r, c in todo: + filename = directory + "/" + classname(r, c) + header, source, templates = generateFiles(r, c) + if include_template_instantiations: + write_file(templates, filename + ".tpp") + source += "\n#include \"" + classname(r, c) + ".tpp\"" + write_file(header, filename + ".hpp") + write_file(source, filename + ".cpp") + + write_file(generateConceptsFile(MAX_N + 1, MAX_N + 1), directory + "/" + "concepts.hpp") + # write_file(generateIteratorFile(), directory + "/" + "iterator.hpp")