947 lines
36 KiB
Python
947 lines
36 KiB
Python
|
#!/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")
|