From 663ecd78afd13acbfbc47be823325e9a4e191404 Mon Sep 17 00:00:00 2001 From: Toby Davis Date: Sat, 21 Oct 2023 17:33:31 +0100 Subject: [PATCH] More python bindings --- .../bindings/generators/arrayGenerator.py | 221 +++++++++++++++++- librapid/bindings/generators/main.py | 5 +- librapid/bindings/generators/module.py | 21 +- .../bindings/generators/shapeGenerator.py | 27 ++- 4 files changed, 260 insertions(+), 14 deletions(-) diff --git a/librapid/bindings/generators/arrayGenerator.py b/librapid/bindings/generators/arrayGenerator.py index 1cc1e2dd..ff3627a4 100644 --- a/librapid/bindings/generators/arrayGenerator.py +++ b/librapid/bindings/generators/arrayGenerator.py @@ -4,12 +4,219 @@ import module import file -constructors = [ - # Default constructor - function.Function( - name="__init__", - args=[] - ), +import itertools +# The set of Array types we support in Python +arrayTypes = [] -] +for scalar in [("int16_t", "Int16"), + ("int32_t", "Int32"), + ("int64_t", "Int64"), + ("float", "Float"), + ("double", "Double"), + ("lrc::Complex", "ComplexFloat"), + ("lrc::Complex", "ComplexDouble")]: + for backend in ["CPU", "OpenCL", "CUDA"]: + arrayTypes.append({ + "scalar": scalar[0], + "backend": backend, + "name": f"Array{scalar[1]}{backend}" + }) + + +def generateCppArrayType(config): + return f"lrc::Array<{config['scalar']}, lrc::backend::{config['backend']}>" + + +def generateCppArrayViewType(config): + return f"lrc::array::GeneralArrayView<{generateCppArrayType(config)}>" + + +def generateFunctionsForArray(config): + methods = [ + # Default constructor + function.Function( + name="__init__", + args=[] + ) + ] + + # Static fromData (n dimensions) + for n in range(1, 9): + cppType = "" + for j in range(n): + cppType += "std::vector<" + cppType += config['scalar'] + ">" * n + + methods.append( + function.Function( + name="fromData", + args=[ + argument.Argument( + name=f"array{n}D", + type=cppType, + const=True, + ref=True, + ) + ], + static=True, + op=f""" + return {generateCppArrayType(config)}::fromData(array{n}D); + """ + ) + ) + + methods += [ + # Shape + function.Function( + name="__init__", + args=[ + argument.Argument( + name="shape", + type="lrc::Shape", + const=True, + ref=True + ) + ] + ), + + # Shape and fill + function.Function( + name="__init__", + args=[ + argument.Argument( + name="shape", + type="lrc::Shape", + const=True, + ref=True + ), + argument.Argument( + name="val", + type=config['scalar'], + const=True, + ref=True + ) + ] + ), + + # Copy constructor + function.Function( + name="__init__", + args=[ + argument.Argument( + name="other", + type=generateCppArrayType(config), + const=True, + ref=True + ) + ] + ), + + # String representation + function.Function( + name="__str__", + args=[ + argument.Argument( + name="self", + type=generateCppArrayType(config), + const=True, + ref=True + ) + ], + op=""" + return fmt::format("{}", self); + """ + ), + + # String representation + function.Function( + name="__repr__", + args=[ + argument.Argument( + name="self", + type=generateCppArrayType(config), + const=True, + ref=True + ) + ], + op=f""" + return fmt::format("", self.shape()); + """ + ), + + # Format (__format__) + function.Function( + name="__format__", + args=[ + argument.Argument( + name="self", + type=generateCppArrayType(config), + const=True, + ref=True + ), + argument.Argument( + name="formatSpec", + type="std::string", + const=True, + ref=True + ) + ], + op=""" + std::string format = fmt::format("{{:{}}}", formatSpec); + return fmt::format(fmt::runtime(format), self); + """ + ) + ] + + return methods, [] + + +def generateArrayModule(config): + arrayClass = class_.Class( + name=config["name"], + type=generateCppArrayType(config) + ) + + methods, functions = generateFunctionsForArray(config) + arrayClass.functions.extend(methods) + + includeGuard = None + if config["backend"] == "CUDA": + includeGuard = "defined(LIBRAPID_HAS_CUDA)" + elif config["backend"] == "OpenCL": + includeGuard = "defined(LIBRAPID_HAS_OPENCL)" + + arrayModule = module.Module( + name=f"librapid.{config['name']}", + includeGuard=includeGuard + ) + arrayModule.addClass(arrayClass) + arrayModule.functions.extend(functions) + + return arrayModule + + +def writeArray(root, config): + fileType = file.File( + path=f"{root}/{config['name']}.cpp" + ) + + fileType.modules.append(generateArrayModule(config)) + + interfaceFunctions = fileType.write() + # Run clang-format if possible + try: + import subprocess + + subprocess.run(["clang-format", "-i", fileType.path]) + except Exception as e: + print("Unable to run clang-format:", e) + + return interfaceFunctions + + +def write(root): + interfaces = [] + for config in arrayTypes: + interfaces.extend(writeArray(root, config)) + return interfaces diff --git a/librapid/bindings/generators/main.py b/librapid/bindings/generators/main.py index 5773f879..6c623b46 100644 --- a/librapid/bindings/generators/main.py +++ b/librapid/bindings/generators/main.py @@ -2,6 +2,7 @@ import textwrap import shapeGenerator +import arrayGenerator outputDir = "../python/generated" @@ -19,6 +20,7 @@ namespace lrc = librapid; """).strip() + def main(): # Ensure the output directory exists if not os.path.exists(outputDir): @@ -26,7 +28,8 @@ def main(): interfaceFunctions = [] - interfaceFunctions += shapeGenerator.write(outputDir + "/shape.cpp") + interfaceFunctions += shapeGenerator.write(outputDir) + interfaceFunctions += arrayGenerator.write(outputDir) with open(f"{outputDir}/librapidPython.hpp", "w") as f: f.write(boilerplate) diff --git a/librapid/bindings/generators/module.py b/librapid/bindings/generators/module.py index c18b9d75..cc5c47e8 100644 --- a/librapid/bindings/generators/module.py +++ b/librapid/bindings/generators/module.py @@ -1,11 +1,13 @@ from class_ import Class +import textwrap class Module: - def __init__(self, name, parentModule=None, docstring=None): + def __init__(self, name, parentModule=None, docstring=None, includeGuard=None): self.name = name self.parent = parentModule - self.docstring = docstring if docstring is not None else f"Bindings for {name}" + self.docstring = docstring + self.includeGuard = includeGuard self.classes = [] self.functions = [] @@ -34,7 +36,8 @@ def genInterface(self): ret += f"module.def_submodule(\"{self.name}\", \"{self.parent.name}.{self.name}\") {{\n" moduleName = self.name - ret += f"{moduleName}.doc() = \"{self.docstring}\";\n\n" + if self.docstring is not None: + ret += f"{moduleName}.doc() = \"{self.docstring}\";\n\n" for class_ in self.classes: ret += class_.genInterface(moduleName) @@ -45,7 +48,17 @@ def genInterface(self): ret += "\n" ret += "}\n" - return ret + + if self.includeGuard is None: + return ret + else: + return textwrap.dedent(f""" + #if {self.includeGuard} + {ret} + #else + {self.genInterfaceDefinition()} {{}} + #endif + """) if __name__ == "__main__": diff --git a/librapid/bindings/generators/shapeGenerator.py b/librapid/bindings/generators/shapeGenerator.py index 72fb423b..703f8ccf 100644 --- a/librapid/bindings/generators/shapeGenerator.py +++ b/librapid/bindings/generators/shapeGenerator.py @@ -258,6 +258,29 @@ op=""" return fmt::format("_librapid.{}", self); """ + ), + + # Format (__format__) + function.Function( + name="__format__", + args=[ + argument.Argument( + name="self", + type="lrc::Shape", + const=True, + ref=True + ), + argument.Argument( + name="formatSpec", + type="std::string", + const=True, + ref=True + ) + ], + op=""" + std::string format = fmt::format("{{:{}}}", formatSpec); + return fmt::format(fmt::runtime(format), self); + """ ) ] @@ -275,9 +298,9 @@ moduleType.classes.append(classType) -def write(path): +def write(root): fileType = file.File( - path="../python/generated/shape.cpp" + path=f"{root}/shape.cpp" ) fileType.modules.append(moduleType)