Reworked math lib generation script
Script can now generate matrices up to 9x9
This commit is contained in:
parent
82b5b2e282
commit
80743102a2
946
generate_math_lib.py
Normal file
946
generate_math_lib.py
Normal file
@ -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 = ["<cmath>", "<algorithm>", "<cstdint>"]
|
||||
|
||||
|
||||
# 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<c_temp>
|
||||
class class {
|
||||
comment
|
||||
template<m_temp>
|
||||
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<Number T>
|
||||
temp<Number N>
|
||||
void Class<T>::operator+(class<N>) {
|
||||
->
|
||||
temp<> void Class<float>::operator+(class<float>)
|
||||
...
|
||||
"""
|
||||
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<T>(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<T>(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<T>(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<T>(@))"), 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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(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<T*>(&" + varname(0, 0, row, col) + "));", "Return an Iterator to the first element"),
|
||||
("cend", "return Iterator(const_cast<T*>(&" + varname(row-1, col-1, row, col) + " + sizeof(T)));", "Return an Iterator past the last element"),
|
||||
("begin", "return Iterator(const_cast<T*>(&" + varname(0, 0, row, col) + "));", "Return an Iterator to the first element"),
|
||||
("end", "return Iterator(const_cast<T*>(&" + 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<T>", 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<T>(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<T>::name<U>(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<Number T>\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<Number T>
|
||||
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 <concepts>\n\n"
|
||||
s += "// Number\n"
|
||||
s += "template<typename T>\n"
|
||||
s += "concept Number = std::integral<T> or std::floating_point<T>;\n\n"
|
||||
|
||||
s += "// VectorX\n"
|
||||
for i in range(2, max(row, col)):
|
||||
s += "template<typename T>\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<decltype(t.@)>;\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<typename T>\n"
|
||||
s += "concept RowVector" + str(i) + " = Vector" + str(i) + "<T> and Number<typename T::isRowVector>;\n\n"
|
||||
s += "// ColumnVectorX\n"
|
||||
for i in range(2, max(row, col)):
|
||||
s += "template<typename T>\n"
|
||||
s += "concept ColumnVector" + str(i) + " = Vector" + str(i) + "<T> and Number<typename T::isColumnVector>;\n\n"
|
||||
s += "// Matrices\n"
|
||||
for r in range(2, row):
|
||||
for c in range(2, col):
|
||||
s += "template<typename T>\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<decltype(t.@)>;\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<typename T>
|
||||
#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<int>>, "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")
|
Loading…
Reference in New Issue
Block a user