From a56ade95acf47d52588f4bac1637c3277b10fc82 Mon Sep 17 00:00:00 2001 From: Hayk Martiros Date: Thu, 2 Jun 2022 12:12:43 +0200 Subject: [PATCH 1/2] Modularize backend-specific code Move almost all backend-specific code into a sub-package of symforce/codegen/backends, rather than being scattered around multiple core files. This includes the code printer, codegen config, and a bunch of switch statements around the codegen machinery. The goal is to centralize and make it cleaner to add new codegen backends. These changes were made in parallel with adding a javascript backend, which will come in a follow-on review. See codegen/backends/README.md for the steps to add a backend after this review. There are still several ways to improve code quality and reorganize backend-specific code, but this is a step forward. --- symforce/codegen/__init__.py | 5 +- symforce/codegen/backends/README.md | 14 +++ .../__init__.py} | 0 symforce/codegen/backends/cpp/README.md | 0 symforce/codegen/backends/cpp/__init__.py | 0 .../cpp}/cpp_code_printer.py | 4 +- symforce/codegen/backends/cpp/cpp_config.py | 74 ++++++++++++++ .../cpp/templates}/cam_package/CLASS.cc.jinja | 0 .../cpp/templates}/cam_package/CLASS.h.jinja | 0 .../cpp/templates}/cam_package/camera.h.jinja | 0 .../cam_package/posed_camera.h.jinja | 0 .../cpp/templates}/epsilon.cc.jinja | 0 .../cpp/templates}/epsilon.h.jinja | 0 .../cpp/templates}/function/FUNCTION.cc.jinja | 0 .../cpp/templates}/function/FUNCTION.h.jinja | 0 .../cpp/templates}/geo_package/CLASS.cc.jinja | 0 .../cpp/templates}/geo_package/CLASS.h.jinja | 0 .../geo_package/custom_methods/pose2.h.jinja | 0 .../geo_package/custom_methods/pose3.h.jinja | 0 .../geo_package/custom_methods/rot2.h.jinja | 0 .../geo_package/custom_methods/rot3.h.jinja | 0 .../geo_package/ops/group_ops.h.jinja | 0 .../geo_package/ops/lie_group_ops.h.jinja | 0 .../geo_package/ops/matrix/group_ops.cc.jinja | 0 .../geo_package/ops/matrix/group_ops.h.jinja | 0 .../ops/matrix/lie_group_ops.cc.jinja | 0 .../ops/matrix/lie_group_ops.h.jinja | 0 .../ops/matrix/storage_ops.cc.jinja | 0 .../ops/matrix/storage_ops.h.jinja | 0 .../geo_package/ops/scalar/group_ops.cc.jinja | 0 .../geo_package/ops/scalar/group_ops.h.jinja | 0 .../ops/scalar/lie_group_ops.cc.jinja | 0 .../ops/scalar/lie_group_ops.h.jinja | 0 .../ops/scalar/storage_ops.cc.jinja | 0 .../ops/scalar/storage_ops.h.jinja | 0 .../geo_package/ops/storage_ops.h.jinja | 0 .../cpp/templates}/keys.h.jinja | 0 .../templates}/ops/CLASS/group_ops.cc.jinja | 0 .../templates}/ops/CLASS/group_ops.h.jinja | 0 .../ops/CLASS/lie_group_ops.cc.jinja | 0 .../ops/CLASS/lie_group_ops.h.jinja | 0 .../templates}/ops/CLASS/storage_ops.cc.jinja | 0 .../templates}/ops/CLASS/storage_ops.h.jinja | 0 .../cam_function_codegen_cpp_test.cc.jinja | 0 .../tests/cam_package_cpp_test.cc.jinja | 0 .../tests/geo_package_cpp_test.cc.jinja | 0 .../cpp/templates}/type_ops.h.jinja | 0 .../cpp/templates}/typedefs.h.jinja | 0 .../cpp/templates}/util/util.jinja | 0 symforce/codegen/backends/python/README.md | 0 symforce/codegen/backends/python/__init__.py | 0 .../python}/python_code_printer.py | 4 +- .../codegen/backends/python/python_config.py | 58 +++++++++++ .../templates}/cam_package/CLASS.py.jinja | 0 .../cam_package/ops/CLASS/__init__.py.jinja | 0 .../cam_package/ops/CLASS/camera_ops.py.jinja | 0 .../templates}/function/FUNCTION.py.jinja | 0 .../templates}/function/__init__.py.jinja | 0 .../templates}/geo_package/CLASS.py.jinja | 0 .../templates}/geo_package/__init__.py.jinja | 0 .../geo_package/custom_methods/pose2.py.jinja | 0 .../geo_package/custom_methods/pose3.py.jinja | 0 .../geo_package/custom_methods/rot2.py.jinja | 0 .../geo_package/custom_methods/rot3.py.jinja | 0 .../templates}/ops/CLASS/__init__.py.jinja | 0 .../templates}/ops/CLASS/group_ops.py.jinja | 0 .../ops/CLASS/lie_group_ops.py.jinja | 0 .../python/templates/ops/__init__.py.jinja | 0 .../python/templates}/setup.py.jinja | 0 .../tests/cam_package_python_test.py.jinja | 0 .../tests/geo_package_python_test.py.jinja | 0 .../python/templates}/util/util.jinja | 0 symforce/codegen/cam_package_codegen.py | 9 +- symforce/codegen/codegen.py | 62 +++--------- symforce/codegen/codegen_config.py | 96 +++++++++---------- symforce/codegen/codegen_util.py | 45 +++------ symforce/codegen/geo_package_codegen.py | 20 ++-- symforce/codegen/printers/__init__.py | 7 -- symforce/codegen/sym_util_package_codegen.py | 22 ++--- symforce/codegen/template_util.py | 84 ++++++++++------ symforce/codegen/types_package_codegen.py | 2 +- symforce/codegen/values_codegen.py | 5 +- .../symforce_cpp_code_printer_codegen_test.py | 8 +- test/symforce_gen_codegen_test.py | 11 ++- test/symforce_lcm_codegen_test.py | 2 +- 85 files changed, 311 insertions(+), 221 deletions(-) create mode 100644 symforce/codegen/backends/README.md rename symforce/codegen/{python_templates/ops/__init__.py.jinja => backends/__init__.py} (100%) create mode 100644 symforce/codegen/backends/cpp/README.md create mode 100644 symforce/codegen/backends/cpp/__init__.py rename symforce/codegen/{printers => backends/cpp}/cpp_code_printer.py (98%) create mode 100644 symforce/codegen/backends/cpp/cpp_config.py rename symforce/codegen/{cpp_templates => backends/cpp/templates}/cam_package/CLASS.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/cam_package/CLASS.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/cam_package/camera.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/cam_package/posed_camera.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/epsilon.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/epsilon.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/function/FUNCTION.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/function/FUNCTION.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/CLASS.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/CLASS.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/custom_methods/pose2.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/custom_methods/pose3.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/custom_methods/rot2.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/custom_methods/rot3.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/group_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/lie_group_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/matrix/group_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/matrix/group_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/matrix/lie_group_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/matrix/lie_group_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/matrix/storage_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/matrix/storage_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/scalar/group_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/scalar/group_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/scalar/lie_group_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/scalar/lie_group_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/scalar/storage_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/scalar/storage_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/geo_package/ops/storage_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/keys.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/ops/CLASS/group_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/ops/CLASS/group_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/ops/CLASS/lie_group_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/ops/CLASS/lie_group_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/ops/CLASS/storage_ops.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/ops/CLASS/storage_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/tests/cam_function_codegen_cpp_test.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/tests/cam_package_cpp_test.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/tests/geo_package_cpp_test.cc.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/type_ops.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/typedefs.h.jinja (100%) rename symforce/codegen/{cpp_templates => backends/cpp/templates}/util/util.jinja (100%) create mode 100644 symforce/codegen/backends/python/README.md create mode 100644 symforce/codegen/backends/python/__init__.py rename symforce/codegen/{printers => backends/python}/python_code_printer.py (96%) create mode 100644 symforce/codegen/backends/python/python_config.py rename symforce/codegen/{python_templates => backends/python/templates}/cam_package/CLASS.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/cam_package/ops/CLASS/__init__.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/cam_package/ops/CLASS/camera_ops.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/function/FUNCTION.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/function/__init__.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/geo_package/CLASS.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/geo_package/__init__.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/geo_package/custom_methods/pose2.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/geo_package/custom_methods/pose3.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/geo_package/custom_methods/rot2.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/geo_package/custom_methods/rot3.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/ops/CLASS/__init__.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/ops/CLASS/group_ops.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/ops/CLASS/lie_group_ops.py.jinja (100%) create mode 100644 symforce/codegen/backends/python/templates/ops/__init__.py.jinja rename symforce/codegen/{python_templates => backends/python/templates}/setup.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/tests/cam_package_python_test.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/tests/geo_package_python_test.py.jinja (100%) rename symforce/codegen/{python_templates => backends/python/templates}/util/util.jinja (100%) diff --git a/symforce/codegen/__init__.py b/symforce/codegen/__init__.py index 526e7f6c7..3c5b22286 100644 --- a/symforce/codegen/__init__.py +++ b/symforce/codegen/__init__.py @@ -7,5 +7,8 @@ Package for executable code generation from symbolic expressions. """ -from .codegen_config import CodegenConfig, CppConfig, PythonConfig from .codegen import Codegen, LinearizationMode, GeneratedPaths +from .codegen_config import CodegenConfig + +from .backends.cpp.cpp_config import CppConfig +from .backends.python.python_config import PythonConfig diff --git a/symforce/codegen/backends/README.md b/symforce/codegen/backends/README.md new file mode 100644 index 000000000..7efa4bbf1 --- /dev/null +++ b/symforce/codegen/backends/README.md @@ -0,0 +1,14 @@ +# Code Generation Backends + +SymForce takes symbolic functions and generates runtime functions for multiple target backends. It is quite straightforward to add new backends. Before you do this, you should be familiar with [SymPy printing](https://docs.sympy.org/latest/modules/printing.html) for getting code from symbolic expressions, and with [Jinja templating](https://realpython.com/primer-on-jinja-templating/) for rendering output files. + +The minimal steps to support a new backend are: + + 1. Choose a name for your backend (for example 'julia') and create a corresponding package in `symforce/codegen/backends`. + 2. Implement a subclass of `sympy.CodePrinter` that emits backend math code while traversing symbolic expressions. Sometimes SymPy already contains the backend and the best pattern is to inherit from it and customize as needed. The best way to do this is by looking at existing backends as examples. + 3. Implement a subclass of `symforce.codegen.codegen_config.CodegenConfig`. This is the spec that users pass to the `Codegen` object to use your backend. Again, see existing examples. Optionally import your config in `symforce/codegen/__init__.py`. + 4. Create a `templates` directory containing jinja templates that are used to generate the actual output files. They specify the high level structure and APIs around the math code. Your codegen config has a `templates_to_render` method that should match your templates. A typical start is just one function template. + 5. Add your backend's extensions to `FileType` in `symforce/codegen/template_util.py`, filling out relevant methods there. + 6. Add tests to `test/symforce_codegen_test.py`. + +This will result in being able to generate functions for your backend that deal with scalars and arrays, but the `sym` geometry and camera classes. To implement those, follow the examples in `geo_package_codegen` and `cam_package_codegen`. diff --git a/symforce/codegen/python_templates/ops/__init__.py.jinja b/symforce/codegen/backends/__init__.py similarity index 100% rename from symforce/codegen/python_templates/ops/__init__.py.jinja rename to symforce/codegen/backends/__init__.py diff --git a/symforce/codegen/backends/cpp/README.md b/symforce/codegen/backends/cpp/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/symforce/codegen/backends/cpp/__init__.py b/symforce/codegen/backends/cpp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/symforce/codegen/printers/cpp_code_printer.py b/symforce/codegen/backends/cpp/cpp_code_printer.py similarity index 98% rename from symforce/codegen/printers/cpp_code_printer.py rename to symforce/codegen/backends/cpp/cpp_code_printer.py index 00c453172..035722a8e 100644 --- a/symforce/codegen/printers/cpp_code_printer.py +++ b/symforce/codegen/backends/cpp/cpp_code_printer.py @@ -3,14 +3,12 @@ # This source code is under the Apache 2.0 license found in the LICENSE file. # ---------------------------------------------------------------------------- +import sympy from sympy.printing.c import get_math_macros from sympy.printing.cxx import CXX11CodePrinter from symforce import typing as T -# Everything in this file is SymPy, not SymEngine (even when SymForce is on the SymEngine backend) -import sympy - class CppCodePrinter(CXX11CodePrinter): """ diff --git a/symforce/codegen/backends/cpp/cpp_config.py b/symforce/codegen/backends/cpp/cpp_config.py new file mode 100644 index 000000000..bf6d5f993 --- /dev/null +++ b/symforce/codegen/backends/cpp/cpp_config.py @@ -0,0 +1,74 @@ +# ---------------------------------------------------------------------------- +# SymForce - Copyright 2022, Skydio, Inc. +# This source code is under the Apache 2.0 license found in the LICENSE file. +# ---------------------------------------------------------------------------- +from dataclasses import dataclass +from pathlib import Path +from sympy.printing.codeprinter import CodePrinter + +from symforce import typing as T +from symforce.codegen.codegen_config import CodegenConfig + +CURRENT_DIR = Path(__file__).parent + + +@dataclass +class CppConfig(CodegenConfig): + """ + Code generation config for the C++ backend. + + Args: + doc_comment_line_prefix: Prefix applied to each line in a docstring + line_length: Maximum allowed line length in docstrings; used for formatting docstrings. + use_eigen_types: Use eigen_lcm types for vectors instead of lists + autoformat: Run a code formatter on the generated code + cse_optimizations: Optimizations argument to pass to sm.cse + support_complex: Generate code that can work with std::complex or with regular float types + force_no_inline: Mark generated functions as `__attribute__((noinline))` + zero_initialization_sparsity_threshold: Threshold between 0 and 1 for the sparsity below + which we'll initialize an output matrix to 0, so we + don't have to generate a line to set each zero + element to 0 individually + explicit_template_instantiation_types: Explicity instantiates templated functions in a `.cc` + file for each given type. This allows the generated function to be compiled in its own + translation unit. Useful for large functions which take a long time to compile. + """ + + doc_comment_line_prefix: str = " * " + line_length: int = 100 + use_eigen_types: bool = True + support_complex: bool = False + force_no_inline: bool = False + zero_initialization_sparsity_threshold: float = 0.5 + explicit_template_instantiation_types: T.Optional[T.Sequence[str]] = None + + @classmethod + def backend_name(cls) -> str: + return "cpp" + + @classmethod + def template_dir(cls) -> Path: + return CURRENT_DIR / "templates" + + def templates_to_render(self, generated_file_name: str) -> T.List[T.Tuple[str, str]]: + # Generate code into a header (since the code is templated) + templates = [("function/FUNCTION.h.jinja", f"{generated_file_name}.h")] + + # Generate a cc file only if we need explicit instantiation. + if self.explicit_template_instantiation_types is not None: + templates.append(("function/FUNCTION.cc.jinja", f"{generated_file_name}.cc")) + + return templates + + def printer(self) -> CodePrinter: + # NOTE(hayk): Is there any benefit to this being lazy? + from symforce.codegen.backends.cpp import cpp_code_printer + + if self.support_complex: + return cpp_code_printer.ComplexCppCodePrinter() + else: + return cpp_code_printer.CppCodePrinter() + + @staticmethod + def format_data_accessor(prefix: str, index: int) -> str: + return f"{prefix}.Data()[{index}]" diff --git a/symforce/codegen/cpp_templates/cam_package/CLASS.cc.jinja b/symforce/codegen/backends/cpp/templates/cam_package/CLASS.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/cam_package/CLASS.cc.jinja rename to symforce/codegen/backends/cpp/templates/cam_package/CLASS.cc.jinja diff --git a/symforce/codegen/cpp_templates/cam_package/CLASS.h.jinja b/symforce/codegen/backends/cpp/templates/cam_package/CLASS.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/cam_package/CLASS.h.jinja rename to symforce/codegen/backends/cpp/templates/cam_package/CLASS.h.jinja diff --git a/symforce/codegen/cpp_templates/cam_package/camera.h.jinja b/symforce/codegen/backends/cpp/templates/cam_package/camera.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/cam_package/camera.h.jinja rename to symforce/codegen/backends/cpp/templates/cam_package/camera.h.jinja diff --git a/symforce/codegen/cpp_templates/cam_package/posed_camera.h.jinja b/symforce/codegen/backends/cpp/templates/cam_package/posed_camera.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/cam_package/posed_camera.h.jinja rename to symforce/codegen/backends/cpp/templates/cam_package/posed_camera.h.jinja diff --git a/symforce/codegen/cpp_templates/epsilon.cc.jinja b/symforce/codegen/backends/cpp/templates/epsilon.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/epsilon.cc.jinja rename to symforce/codegen/backends/cpp/templates/epsilon.cc.jinja diff --git a/symforce/codegen/cpp_templates/epsilon.h.jinja b/symforce/codegen/backends/cpp/templates/epsilon.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/epsilon.h.jinja rename to symforce/codegen/backends/cpp/templates/epsilon.h.jinja diff --git a/symforce/codegen/cpp_templates/function/FUNCTION.cc.jinja b/symforce/codegen/backends/cpp/templates/function/FUNCTION.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/function/FUNCTION.cc.jinja rename to symforce/codegen/backends/cpp/templates/function/FUNCTION.cc.jinja diff --git a/symforce/codegen/cpp_templates/function/FUNCTION.h.jinja b/symforce/codegen/backends/cpp/templates/function/FUNCTION.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/function/FUNCTION.h.jinja rename to symforce/codegen/backends/cpp/templates/function/FUNCTION.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/CLASS.cc.jinja b/symforce/codegen/backends/cpp/templates/geo_package/CLASS.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/CLASS.cc.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/CLASS.cc.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/CLASS.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/CLASS.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/CLASS.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/CLASS.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/custom_methods/pose2.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/custom_methods/pose2.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/custom_methods/pose2.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/custom_methods/pose2.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/custom_methods/pose3.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/custom_methods/pose3.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/custom_methods/pose3.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/custom_methods/pose3.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/custom_methods/rot2.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/custom_methods/rot2.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/custom_methods/rot2.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/custom_methods/rot2.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/custom_methods/rot3.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/custom_methods/rot3.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/custom_methods/rot3.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/custom_methods/rot3.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/group_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/group_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/group_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/group_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/lie_group_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/lie_group_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/lie_group_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/lie_group_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/matrix/group_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/group_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/matrix/group_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/group_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/matrix/group_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/group_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/matrix/group_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/group_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/matrix/lie_group_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/lie_group_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/matrix/lie_group_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/lie_group_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/matrix/lie_group_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/lie_group_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/matrix/lie_group_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/lie_group_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/matrix/storage_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/storage_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/matrix/storage_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/storage_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/matrix/storage_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/storage_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/matrix/storage_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/matrix/storage_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/scalar/group_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/group_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/scalar/group_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/group_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/scalar/group_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/group_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/scalar/group_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/group_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/scalar/lie_group_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/lie_group_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/scalar/lie_group_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/lie_group_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/scalar/lie_group_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/lie_group_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/scalar/lie_group_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/lie_group_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/scalar/storage_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/storage_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/scalar/storage_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/storage_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/scalar/storage_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/storage_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/scalar/storage_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/scalar/storage_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/geo_package/ops/storage_ops.h.jinja b/symforce/codegen/backends/cpp/templates/geo_package/ops/storage_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/geo_package/ops/storage_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/geo_package/ops/storage_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/keys.h.jinja b/symforce/codegen/backends/cpp/templates/keys.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/keys.h.jinja rename to symforce/codegen/backends/cpp/templates/keys.h.jinja diff --git a/symforce/codegen/cpp_templates/ops/CLASS/group_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/ops/CLASS/group_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/ops/CLASS/group_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/ops/CLASS/group_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/ops/CLASS/group_ops.h.jinja b/symforce/codegen/backends/cpp/templates/ops/CLASS/group_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/ops/CLASS/group_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/ops/CLASS/group_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/ops/CLASS/lie_group_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/ops/CLASS/lie_group_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/ops/CLASS/lie_group_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/ops/CLASS/lie_group_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/ops/CLASS/lie_group_ops.h.jinja b/symforce/codegen/backends/cpp/templates/ops/CLASS/lie_group_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/ops/CLASS/lie_group_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/ops/CLASS/lie_group_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/ops/CLASS/storage_ops.cc.jinja b/symforce/codegen/backends/cpp/templates/ops/CLASS/storage_ops.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/ops/CLASS/storage_ops.cc.jinja rename to symforce/codegen/backends/cpp/templates/ops/CLASS/storage_ops.cc.jinja diff --git a/symforce/codegen/cpp_templates/ops/CLASS/storage_ops.h.jinja b/symforce/codegen/backends/cpp/templates/ops/CLASS/storage_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/ops/CLASS/storage_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/ops/CLASS/storage_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/tests/cam_function_codegen_cpp_test.cc.jinja b/symforce/codegen/backends/cpp/templates/tests/cam_function_codegen_cpp_test.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/tests/cam_function_codegen_cpp_test.cc.jinja rename to symforce/codegen/backends/cpp/templates/tests/cam_function_codegen_cpp_test.cc.jinja diff --git a/symforce/codegen/cpp_templates/tests/cam_package_cpp_test.cc.jinja b/symforce/codegen/backends/cpp/templates/tests/cam_package_cpp_test.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/tests/cam_package_cpp_test.cc.jinja rename to symforce/codegen/backends/cpp/templates/tests/cam_package_cpp_test.cc.jinja diff --git a/symforce/codegen/cpp_templates/tests/geo_package_cpp_test.cc.jinja b/symforce/codegen/backends/cpp/templates/tests/geo_package_cpp_test.cc.jinja similarity index 100% rename from symforce/codegen/cpp_templates/tests/geo_package_cpp_test.cc.jinja rename to symforce/codegen/backends/cpp/templates/tests/geo_package_cpp_test.cc.jinja diff --git a/symforce/codegen/cpp_templates/type_ops.h.jinja b/symforce/codegen/backends/cpp/templates/type_ops.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/type_ops.h.jinja rename to symforce/codegen/backends/cpp/templates/type_ops.h.jinja diff --git a/symforce/codegen/cpp_templates/typedefs.h.jinja b/symforce/codegen/backends/cpp/templates/typedefs.h.jinja similarity index 100% rename from symforce/codegen/cpp_templates/typedefs.h.jinja rename to symforce/codegen/backends/cpp/templates/typedefs.h.jinja diff --git a/symforce/codegen/cpp_templates/util/util.jinja b/symforce/codegen/backends/cpp/templates/util/util.jinja similarity index 100% rename from symforce/codegen/cpp_templates/util/util.jinja rename to symforce/codegen/backends/cpp/templates/util/util.jinja diff --git a/symforce/codegen/backends/python/README.md b/symforce/codegen/backends/python/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/symforce/codegen/backends/python/__init__.py b/symforce/codegen/backends/python/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/symforce/codegen/printers/python_code_printer.py b/symforce/codegen/backends/python/python_code_printer.py similarity index 96% rename from symforce/codegen/printers/python_code_printer.py rename to symforce/codegen/backends/python/python_code_printer.py index c6ae5223d..3c2d34822 100644 --- a/symforce/codegen/printers/python_code_printer.py +++ b/symforce/codegen/backends/python/python_code_printer.py @@ -3,10 +3,8 @@ # This source code is under the Apache 2.0 license found in the LICENSE file. # ---------------------------------------------------------------------------- -from sympy.printing.pycode import PythonCodePrinter as _PythonCodePrinter - -# Everything in this file is SymPy, not SymEngine (even when SymForce is on the SymEngine backend) import sympy +from sympy.printing.pycode import PythonCodePrinter as _PythonCodePrinter class PythonCodePrinter(_PythonCodePrinter): diff --git a/symforce/codegen/backends/python/python_config.py b/symforce/codegen/backends/python/python_config.py new file mode 100644 index 000000000..d2a805e06 --- /dev/null +++ b/symforce/codegen/backends/python/python_config.py @@ -0,0 +1,58 @@ +# ---------------------------------------------------------------------------- +# SymForce - Copyright 2022, Skydio, Inc. +# This source code is under the Apache 2.0 license found in the LICENSE file. +# ---------------------------------------------------------------------------- +from dataclasses import dataclass +from pathlib import Path +from sympy.printing.codeprinter import CodePrinter + +from symforce import typing as T +from symforce.codegen.codegen_config import CodegenConfig + + +CURRENT_DIR = Path(__file__).parent + + +@dataclass +class PythonConfig(CodegenConfig): + """ + Code generation config for the Python backend. + + Args: + doc_comment_line_prefix: Prefix applied to each line in a docstring + line_length: Maximum allowed line length in docstrings; used for formatting docstrings. + use_eigen_types: Use eigen_lcm types for vectors instead of lists + autoformat: Run a code formatter on the generated code + cse_optimizations: Optimizations argument to pass to sm.cse + use_numba: Add the `@numba.njit` decorator to generated functions. This will greatly + speed up functions by compiling them to machine code, but has large overhead + on the first call and some overhead on subsequent calls, so it should not be + used for small functions or functions that are only called a handfull of + times. + matrix_is_1D: geo.Matrix symbols get formatted as a 1D array + """ + + doc_comment_line_prefix: str = "" + line_length: int = 100 + use_eigen_types: bool = True + use_numba: bool = False + matrix_is_1d: bool = True + + @classmethod + def backend_name(cls) -> str: + return "python" + + @classmethod + def template_dir(cls) -> Path: + return CURRENT_DIR / "templates" + + def templates_to_render(self, generated_file_name: str) -> T.List[T.Tuple[str, str]]: + return [ + ("function/FUNCTION.py.jinja", f"{generated_file_name}.py"), + ("function/__init__.py.jinja", "__init__.py"), + ] + + def printer(self) -> CodePrinter: + from symforce.codegen.backends.python import python_code_printer + + return python_code_printer.PythonCodePrinter() diff --git a/symforce/codegen/python_templates/cam_package/CLASS.py.jinja b/symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja similarity index 100% rename from symforce/codegen/python_templates/cam_package/CLASS.py.jinja rename to symforce/codegen/backends/python/templates/cam_package/CLASS.py.jinja diff --git a/symforce/codegen/python_templates/cam_package/ops/CLASS/__init__.py.jinja b/symforce/codegen/backends/python/templates/cam_package/ops/CLASS/__init__.py.jinja similarity index 100% rename from symforce/codegen/python_templates/cam_package/ops/CLASS/__init__.py.jinja rename to symforce/codegen/backends/python/templates/cam_package/ops/CLASS/__init__.py.jinja diff --git a/symforce/codegen/python_templates/cam_package/ops/CLASS/camera_ops.py.jinja b/symforce/codegen/backends/python/templates/cam_package/ops/CLASS/camera_ops.py.jinja similarity index 100% rename from symforce/codegen/python_templates/cam_package/ops/CLASS/camera_ops.py.jinja rename to symforce/codegen/backends/python/templates/cam_package/ops/CLASS/camera_ops.py.jinja diff --git a/symforce/codegen/python_templates/function/FUNCTION.py.jinja b/symforce/codegen/backends/python/templates/function/FUNCTION.py.jinja similarity index 100% rename from symforce/codegen/python_templates/function/FUNCTION.py.jinja rename to symforce/codegen/backends/python/templates/function/FUNCTION.py.jinja diff --git a/symforce/codegen/python_templates/function/__init__.py.jinja b/symforce/codegen/backends/python/templates/function/__init__.py.jinja similarity index 100% rename from symforce/codegen/python_templates/function/__init__.py.jinja rename to symforce/codegen/backends/python/templates/function/__init__.py.jinja diff --git a/symforce/codegen/python_templates/geo_package/CLASS.py.jinja b/symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja similarity index 100% rename from symforce/codegen/python_templates/geo_package/CLASS.py.jinja rename to symforce/codegen/backends/python/templates/geo_package/CLASS.py.jinja diff --git a/symforce/codegen/python_templates/geo_package/__init__.py.jinja b/symforce/codegen/backends/python/templates/geo_package/__init__.py.jinja similarity index 100% rename from symforce/codegen/python_templates/geo_package/__init__.py.jinja rename to symforce/codegen/backends/python/templates/geo_package/__init__.py.jinja diff --git a/symforce/codegen/python_templates/geo_package/custom_methods/pose2.py.jinja b/symforce/codegen/backends/python/templates/geo_package/custom_methods/pose2.py.jinja similarity index 100% rename from symforce/codegen/python_templates/geo_package/custom_methods/pose2.py.jinja rename to symforce/codegen/backends/python/templates/geo_package/custom_methods/pose2.py.jinja diff --git a/symforce/codegen/python_templates/geo_package/custom_methods/pose3.py.jinja b/symforce/codegen/backends/python/templates/geo_package/custom_methods/pose3.py.jinja similarity index 100% rename from symforce/codegen/python_templates/geo_package/custom_methods/pose3.py.jinja rename to symforce/codegen/backends/python/templates/geo_package/custom_methods/pose3.py.jinja diff --git a/symforce/codegen/python_templates/geo_package/custom_methods/rot2.py.jinja b/symforce/codegen/backends/python/templates/geo_package/custom_methods/rot2.py.jinja similarity index 100% rename from symforce/codegen/python_templates/geo_package/custom_methods/rot2.py.jinja rename to symforce/codegen/backends/python/templates/geo_package/custom_methods/rot2.py.jinja diff --git a/symforce/codegen/python_templates/geo_package/custom_methods/rot3.py.jinja b/symforce/codegen/backends/python/templates/geo_package/custom_methods/rot3.py.jinja similarity index 100% rename from symforce/codegen/python_templates/geo_package/custom_methods/rot3.py.jinja rename to symforce/codegen/backends/python/templates/geo_package/custom_methods/rot3.py.jinja diff --git a/symforce/codegen/python_templates/ops/CLASS/__init__.py.jinja b/symforce/codegen/backends/python/templates/ops/CLASS/__init__.py.jinja similarity index 100% rename from symforce/codegen/python_templates/ops/CLASS/__init__.py.jinja rename to symforce/codegen/backends/python/templates/ops/CLASS/__init__.py.jinja diff --git a/symforce/codegen/python_templates/ops/CLASS/group_ops.py.jinja b/symforce/codegen/backends/python/templates/ops/CLASS/group_ops.py.jinja similarity index 100% rename from symforce/codegen/python_templates/ops/CLASS/group_ops.py.jinja rename to symforce/codegen/backends/python/templates/ops/CLASS/group_ops.py.jinja diff --git a/symforce/codegen/python_templates/ops/CLASS/lie_group_ops.py.jinja b/symforce/codegen/backends/python/templates/ops/CLASS/lie_group_ops.py.jinja similarity index 100% rename from symforce/codegen/python_templates/ops/CLASS/lie_group_ops.py.jinja rename to symforce/codegen/backends/python/templates/ops/CLASS/lie_group_ops.py.jinja diff --git a/symforce/codegen/backends/python/templates/ops/__init__.py.jinja b/symforce/codegen/backends/python/templates/ops/__init__.py.jinja new file mode 100644 index 000000000..e69de29bb diff --git a/symforce/codegen/python_templates/setup.py.jinja b/symforce/codegen/backends/python/templates/setup.py.jinja similarity index 100% rename from symforce/codegen/python_templates/setup.py.jinja rename to symforce/codegen/backends/python/templates/setup.py.jinja diff --git a/symforce/codegen/python_templates/tests/cam_package_python_test.py.jinja b/symforce/codegen/backends/python/templates/tests/cam_package_python_test.py.jinja similarity index 100% rename from symforce/codegen/python_templates/tests/cam_package_python_test.py.jinja rename to symforce/codegen/backends/python/templates/tests/cam_package_python_test.py.jinja diff --git a/symforce/codegen/python_templates/tests/geo_package_python_test.py.jinja b/symforce/codegen/backends/python/templates/tests/geo_package_python_test.py.jinja similarity index 100% rename from symforce/codegen/python_templates/tests/geo_package_python_test.py.jinja rename to symforce/codegen/backends/python/templates/tests/geo_package_python_test.py.jinja diff --git a/symforce/codegen/python_templates/util/util.jinja b/symforce/codegen/backends/python/templates/util/util.jinja similarity index 100% rename from symforce/codegen/python_templates/util/util.jinja rename to symforce/codegen/backends/python/templates/util/util.jinja diff --git a/symforce/codegen/cam_package_codegen.py b/symforce/codegen/cam_package_codegen.py index b2c94cda3..2c7c24b1c 100644 --- a/symforce/codegen/cam_package_codegen.py +++ b/symforce/codegen/cam_package_codegen.py @@ -257,7 +257,7 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: if isinstance(config, PythonConfig): logger.info(f'Creating Python package at: "{cam_package_dir}"') - template_dir = pathlib.Path(template_util.PYTHON_TEMPLATE_DIR) + template_dir = config.template_dir() # First generate the geo package as it's a dependency of the cam package from symforce.codegen import geo_package_codegen @@ -310,7 +310,7 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: elif isinstance(config, CppConfig): logger.info(f'Creating C++ cam package at: "{cam_package_dir}"') - template_dir = pathlib.Path(template_util.CPP_TEMPLATE_DIR, "cam_package") + template_dir = config.template_dir() / "cam_package" # First generate the geo package as it's a dependency of the cam package from symforce.codegen import geo_package_codegen @@ -331,9 +331,8 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: (".", "ops/CLASS/lie_group_ops.h"), (".", "ops/CLASS/lie_group_ops.cc"), ): - template_path = pathlib.Path( - template_util.CPP_TEMPLATE_DIR, base_dir, relative_path + ".jinja" - ) + template_path = config.template_dir() / base_dir / relative_path + ".jinja" + output_path = cam_package_dir / relative_path.replace( "CLASS", python_util.camelcase_to_snakecase(cls.__name__) ) diff --git a/symforce/codegen/codegen.py b/symforce/codegen/codegen.py index 1b02072c2..9814dd15c 100644 --- a/symforce/codegen/codegen.py +++ b/symforce/codegen/codegen.py @@ -23,13 +23,14 @@ from symforce import python_util from symforce import typing as T from symforce.values import Values +from symforce import codegen from symforce.codegen import template_util from symforce.codegen import codegen_util from symforce.codegen import codegen_config from symforce.codegen import types_package_codegen from symforce.type_helpers import symbolic_inputs -CURRENT_DIR = os.path.dirname(__file__) +CURRENT_DIR = Path(__file__).parent class LinearizationMode(enum.Enum): @@ -256,7 +257,7 @@ def common_data() -> T.Dict[str, T.Any]: data["DataBuffer"] = sm.DataBuffer data["Values"] = Values data["pathlib"] = pathlib - data["path_to_codegen"] = CURRENT_DIR + data["path_to_codegen"] = str(CURRENT_DIR) data["scalar_types"] = ("double", "float") data["camelcase_to_snakecase"] = python_util.camelcase_to_snakecase data["python_util"] = python_util @@ -393,56 +394,23 @@ def generate_function( self.namespace = namespace template_data = dict(self.common_data(), spec=self) + template_dir = self.config.template_dir() - # Generate the function - if isinstance(self.config, codegen_config.PythonConfig): - if skip_directory_nesting: - python_function_dir = output_dir - else: - python_function_dir = output_dir / "python" / "symforce" / namespace - - logger.info(f'Creating python function "{self.name}" at "{python_function_dir}"') - - templates.add( - Path(template_util.PYTHON_TEMPLATE_DIR) / "function" / "FUNCTION.py.jinja", - python_function_dir / f"{generated_file_name}.py", - template_data, - ) - templates.add( - Path(template_util.PYTHON_TEMPLATE_DIR) / "function" / "__init__.py.jinja", - python_function_dir / "__init__.py", - template_data, - ) - - out_function_dir = python_function_dir - elif isinstance(self.config, codegen_config.CppConfig): - if skip_directory_nesting: - cpp_function_dir = output_dir - else: - cpp_function_dir = output_dir / "cpp" / "symforce" / namespace - - logger.info( - f'Creating C++ function "{python_util.snakecase_to_camelcase(self.name)}" at "{cpp_function_dir}"' - ) + backend_name = self.config.backend_name() + if skip_directory_nesting: + out_function_dir = output_dir + else: + out_function_dir = output_dir / backend_name / "symforce" / namespace - templates.add( - Path(template_util.CPP_TEMPLATE_DIR) / "function" / "FUNCTION.h.jinja", - cpp_function_dir / f"{generated_file_name}.h", - template_data, - ) + logger.info(f'Creating {backend_name} function from "{self.name}" at "{out_function_dir}"') - if self.config.explicit_template_instantiation_types is not None: - templates.add( - Path(template_util.CPP_TEMPLATE_DIR) / "function" / "FUNCTION.cc.jinja", - cpp_function_dir / f"{generated_file_name}.cc", - template_data, - ) - - out_function_dir = cpp_function_dir - else: - raise NotImplementedError(f'Unknown config type: "{self.config}"') + # Get templates to render + for source, dest in self.config.templates_to_render(generated_file_name): + templates.add(template_dir / source, out_function_dir / dest, template_data) + # Render templates.render(autoformat=self.config.autoformat) + lcm_data = codegen_util.generate_lcm_types( lcm_type_dir=types_codegen_data["lcm_type_dir"], lcm_files=types_codegen_data["lcm_files"], diff --git a/symforce/codegen/codegen_config.py b/symforce/codegen/codegen_config.py index 9e03b3218..4c0e8a1e5 100644 --- a/symforce/codegen/codegen_config.py +++ b/symforce/codegen/codegen_config.py @@ -2,16 +2,20 @@ # SymForce - Copyright 2022, Skydio, Inc. # This source code is under the Apache 2.0 license found in the LICENSE file. # ---------------------------------------------------------------------------- - +from abc import abstractmethod from dataclasses import dataclass +from pathlib import Path +from sympy.printing.codeprinter import CodePrinter from symforce import typing as T +CURRENT_DIR = Path(__file__).parent + @dataclass class CodegenConfig: """ - Base class for language-specific arguments for code generation + Base class for backend-specific arguments for code generation. Args: doc_comment_line_prefix: Prefix applied to each line in a docstring, e.g. " * " for C++ @@ -20,6 +24,7 @@ class CodegenConfig: use_eigen_types: Use eigen_lcm types for vectors instead of lists autoformat: Run a code formatter on the generated code cse_optimizations: Optimizations argument to pass to sm.cse + matrix_is_1d: Whether geo.Matrix symbols get formatted as 1D """ doc_comment_line_prefix: str @@ -29,58 +34,45 @@ class CodegenConfig: cse_optimizations: T.Optional[ T.Union[T.Literal["basic"], T.Sequence[T.Tuple[T.Callable, T.Callable]]] ] = None + # TODO(hayk): Remove this parameter (by making everything 2D?) + matrix_is_1d: bool = False + @classmethod + @abstractmethod + def backend_name(cls) -> str: + """ + String name for the backend. This should match the directory name in codegen/backends + and will be used to namespace by backend in generated code. + """ + pass -@dataclass -class CppConfig(CodegenConfig): - """ - C++ Codegen configuration - - Args: - doc_comment_line_prefix: Prefix applied to each line in a docstring - line_length: Maximum allowed line length in docstrings; used for formatting docstrings. - use_eigen_types: Use eigen_lcm types for vectors instead of lists - autoformat: Run a code formatter on the generated code - cse_optimizations: Optimizations argument to pass to sm.cse - support_complex: Generate code that can work with std::complex or with regular float types - force_no_inline: Mark generated functions as `__attribute__((noinline))` - zero_initialization_sparsity_threshold: Threshold between 0 and 1 for the sparsity below - which we'll initialize an output matrix to 0, so we - don't have to generate a line to set each zero - element to 0 individually - explicit_template_instantiation_types: Explicity instantiates templated functions in a `.cc` - file for each given type. This allows the generated function to be compiled in its own - translation unit. Useful for large functions which take a long time to compile. - """ + @classmethod + @abstractmethod + def template_dir(cls) -> Path: + """ + Directory for jinja templates. + """ + pass - doc_comment_line_prefix: str = " * " - line_length: int = 100 - use_eigen_types: bool = True - support_complex: bool = False - force_no_inline: bool = False - zero_initialization_sparsity_threshold: float = 0.5 - explicit_template_instantiation_types: T.Optional[T.Sequence[str]] = None + @abstractmethod + def templates_to_render(self, generated_file_name: str) -> T.List[T.Tuple[str, str]]: + """ + Given a single symbolic function's filename, provide one or more Jinja templates to + render and the relative output paths where they should go. + """ + pass + @abstractmethod + def printer(self) -> CodePrinter: + """ + Return an instance of the code printer to use for this language. + """ + pass -@dataclass -class PythonConfig(CodegenConfig): - """ - Python Codegen configuration - - Args: - doc_comment_line_prefix: Prefix applied to each line in a docstring - line_length: Maximum allowed line length in docstrings; used for formatting docstrings. - use_eigen_types: Use eigen_lcm types for vectors instead of lists - autoformat: Run a code formatter on the generated code - cse_optimizations: Optimizations argument to pass to sm.cse - use_numba: Add the `@numba.njit` decorator to generated functions. This will greatly - speed up functions by compiling them to machine code, but has large overhead - on the first call and some overhead on subsequent calls, so it should not be - used for small functions or functions that are only called a handfull of - times. - """ - - doc_comment_line_prefix: str = "" - line_length: int = 100 - use_eigen_types: bool = True - use_numba: bool = False + # TODO(hayk): Move this into code printer. + @staticmethod + def format_data_accessor(prefix: str, index: int) -> str: + """ + Format data for accessing a data array in code. + """ + return f"{prefix}.data[{index}]" diff --git a/symforce/codegen/codegen_util.py b/symforce/codegen/codegen_util.py index 2c9d36964..2cdebfae2 100644 --- a/symforce/codegen/codegen_util.py +++ b/symforce/codegen/codegen_util.py @@ -23,7 +23,7 @@ from symforce.values import Values, IndexEntry from symforce import sympy as sm from symforce import typing as T -from symforce.codegen import printers, format_util +from symforce.codegen import format_util from symforce.codegen import codegen_config from symforce import python_util from symforce import _sympy_count_ops @@ -178,7 +178,7 @@ def count_ops(expr: sm.Expr) -> int: ) # Get printer - printer = get_code_printer(config) + printer = config.printer() # Print code intermediate_terms = [(str(var), printer.doprint(t)) for var, t in temps_formatted] @@ -341,20 +341,19 @@ def get_formatted_list( formatted_symbols = [sm.Symbol(key)] flattened_value = [value] elif issubclass(arg_cls, geo.Matrix): - if isinstance(config, codegen_config.PythonConfig): - # TODO(nathan): Not sure this works for 2D matrices + if config.matrix_is_1d: + # TODO(nathan): Not sure this works for 2D matrices. Get rid of this. formatted_symbols = [sm.Symbol(f"{key}[{j}]") for j in range(storage_dim)] - elif isinstance(config, codegen_config.CppConfig): - formatted_symbols = [] + else: # NOTE(brad): The order of the symbols must match the storage order of geo.Matrix # (as returned by geo.Matrix.to_storage). Hence, if there storage order were # changed to, say, row major, the below for loops would have to be swapped to # reflect that. + formatted_symbols = [] for j in range(value.shape[1]): for i in range(value.shape[0]): formatted_symbols.append(sm.Symbol(f"{key}({i}, {j})")) - else: - raise NotImplementedError() + flattened_value = ops.StorageOps.to_storage(value) elif issubclass(arg_cls, Values): @@ -466,12 +465,10 @@ def _get_scalar_keys_recursive( vec.extend(sm.Symbol(f"{prefix}[{i}]") for i in range(index_value.storage_dim)) else: # We have a geo/cam or other object that uses "data" to store a flat vector of scalars. - if isinstance(config, codegen_config.PythonConfig): - vec.extend(sm.Symbol(f"{prefix}.data[{i}]") for i in range(index_value.storage_dim)) - elif isinstance(config, codegen_config.CppConfig): - vec.extend(sm.Symbol(f"{prefix}.Data()[{i}]") for i in range(index_value.storage_dim)) - else: - raise NotImplementedError() + vec.extend( + sm.Symbol(config.format_data_accessor(prefix=prefix, index=i)) + for i in range(index_value.storage_dim) + ) assert len(vec) == len(set(vec)), "Non-unique keys:\n{}".format( [symbol for symbol in vec if vec.count(symbol) > 1] @@ -494,26 +491,6 @@ def get_formatted_sparse_list(sparse_outputs: Values) -> T.List[T.List[T.Scalar] return symbolic_args -def get_code_printer(config: codegen_config.CodegenConfig) -> "sm.CodePrinter": - """ - Pick a code printer for the given mode. - """ - # TODO(hayk): Consider symengine printer if this becomes slow. - - if isinstance(config, codegen_config.PythonConfig): - printer: sm.printing.codeprinter.CodePrinter = printers.PythonCodePrinter() - - elif isinstance(config, codegen_config.CppConfig): - if config.support_complex: - printer = printers.ComplexCppCodePrinter() - else: - printer = printers.CppCodePrinter() - else: - raise NotImplementedError(f"Unknown config type: {config}") - - return printer - - def _load_generated_package_internal(name: str, path: Path) -> T.Tuple[T.Any, T.List[str]]: """ Dynamically load generated package (or module). diff --git a/symforce/codegen/geo_package_codegen.py b/symforce/codegen/geo_package_codegen.py index 65abd8f5e..b3ba41ea6 100644 --- a/symforce/codegen/geo_package_codegen.py +++ b/symforce/codegen/geo_package_codegen.py @@ -145,7 +145,7 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: if isinstance(config, PythonConfig): logger.info(f'Creating Python package at: "{package_dir}"') - template_dir = Path(template_util.PYTHON_TEMPLATE_DIR) + template_dir = config.template_dir() # Build up templates for each type @@ -192,7 +192,7 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: sym_util_package_codegen.generate(config, output_dir=output_dir) logger.info(f'Creating C++ package at: "{package_dir}"') - template_dir = Path(template_util.CPP_TEMPLATE_DIR, "geo_package") + template_dir = config.template_dir() # Build up templates for each type for cls in DEFAULT_GEO_TYPES: @@ -210,14 +210,14 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: (".", "ops/CLASS/lie_group_ops.h"), (".", "ops/CLASS/lie_group_ops.cc"), ): - template_path = Path( - template_util.CPP_TEMPLATE_DIR, base_dir, relative_path + ".jinja" - ) + template_path = template_dir / base_dir / f"{relative_path}.jinja" output_path = package_dir / relative_path.replace("CLASS", cls.__name__.lower()) templates.add(template_path, output_path, data) # Render non geo type specific templates - for template_name in python_util.files_in_dir(str(template_dir / "ops"), relative=True): + for template_name in python_util.files_in_dir( + template_dir / "geo_package" / "ops", relative=True + ): if "CLASS" in template_name: continue @@ -225,15 +225,15 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: continue templates.add( - template_dir / "ops" / template_name, - package_dir / "ops" / template_name[: -len(".jinja")], + template_dir / "geo_package" / "ops" / template_name, + package_dir / "geo_package" / "ops" / template_name[: -len(".jinja")], dict(Codegen.common_data()), ) # Test example for name in ("geo_package_cpp_test.cc",): templates.add( - template_dir / ".." / "tests" / (name + ".jinja"), + template_dir / "tests" / (name + ".jinja"), Path(output_dir, "tests", name), dict( Codegen.common_data(), @@ -256,7 +256,7 @@ def generate(config: CodegenConfig, output_dir: str = None) -> str: # LCM type_t templates.add( - Path(template_util.LCM_TEMPLATE_DIR, "symforce_types.lcm.jinja"), + template_util.LCM_TEMPLATE_DIR / "symforce_types.lcm.jinja", package_dir / ".." / "lcmtypes" / "lcmtypes" / "symforce_types.lcm", lcm_types_codegen.lcm_symforce_types_data(), ) diff --git a/symforce/codegen/printers/__init__.py b/symforce/codegen/printers/__init__.py index 8140e53a1..e69de29bb 100644 --- a/symforce/codegen/printers/__init__.py +++ b/symforce/codegen/printers/__init__.py @@ -1,7 +0,0 @@ -# ---------------------------------------------------------------------------- -# SymForce - Copyright 2022, Skydio, Inc. -# This source code is under the Apache 2.0 license found in the LICENSE file. -# ---------------------------------------------------------------------------- - -from .cpp_code_printer import CppCodePrinter, ComplexCppCodePrinter -from .python_code_printer import PythonCodePrinter diff --git a/symforce/codegen/sym_util_package_codegen.py b/symforce/codegen/sym_util_package_codegen.py index 185f36282..552ab52ae 100644 --- a/symforce/codegen/sym_util_package_codegen.py +++ b/symforce/codegen/sym_util_package_codegen.py @@ -29,14 +29,15 @@ def generate(config: codegen.CodegenConfig, output_dir: str = None) -> str: templates = template_util.TemplateList() if isinstance(config, codegen.CppConfig): + template_dir = config.template_dir() templates.add( - template_path=os.path.join(template_util.CPP_TEMPLATE_DIR, "typedefs.h.jinja"), + template_path=template_dir / "typedefs.h.jinja", output_path=os.path.join(package_dir, "typedefs.h"), data={}, ) templates.add( - template_path=os.path.join(template_util.CPP_TEMPLATE_DIR, "type_ops.h.jinja"), + template_path=template_dir / "type_ops.h.jinja", output_path=os.path.join(package_dir, "type_ops.h"), data=dict( python_util=python_util, @@ -44,17 +45,12 @@ def generate(config: codegen.CodegenConfig, output_dir: str = None) -> str: ), ) - templates.add( - template_path=os.path.join(template_util.CPP_TEMPLATE_DIR, "epsilon.h.jinja"), - output_path=os.path.join(package_dir, "epsilon.h"), - data={}, - ) - - templates.add( - template_path=os.path.join(template_util.CPP_TEMPLATE_DIR, "epsilon.cc.jinja"), - output_path=os.path.join(package_dir, "epsilon.cc"), - data={}, - ) + for filename in ("epsilon.h", "epsilon.cc"): + templates.add( + template_path=template_dir / f"{filename}.jinja", + output_path=os.path.join(package_dir, filename), + data={}, + ) else: # sym/util is currently C++ only pass diff --git a/symforce/codegen/template_util.py b/symforce/codegen/template_util.py index 3e947d322..e417cd51d 100644 --- a/symforce/codegen/template_util.py +++ b/symforce/codegen/template_util.py @@ -18,10 +18,8 @@ from symforce import typing as T from symforce.codegen import format_util -CURRENT_DIR = os.path.dirname(__file__) -CPP_TEMPLATE_DIR = os.path.join(CURRENT_DIR, "cpp_templates") -PYTHON_TEMPLATE_DIR = os.path.join(CURRENT_DIR, "python_templates") -LCM_TEMPLATE_DIR = os.path.join(CURRENT_DIR, "lcm_templates") +CURRENT_DIR = Path(__file__).parent +LCM_TEMPLATE_DIR = CURRENT_DIR / "lcm_templates" class FileType(enum.Enum): @@ -61,6 +59,45 @@ def from_template_path(template_path: Path) -> FileType: ) return FileType.from_extension(parts[-2]) + def comment_prefix(self) -> str: + """ + Return the comment prefix for this file type. + """ + if self in (FileType.CPP, FileType.CUDA, FileType.LCM): + return "//" + elif self in (FileType.PYTHON, FileType.PYTHON_INTERFACE): + return "#" + else: + raise NotImplementedError(f"Unknown comment prefix for {self}") + + def autoformat( + self, file_contents: str, template_name: T.Openable, output_path: T.Openable = None + ) -> str: + """ + Format code of this file type. + """ + # TODO(hayk): Move up to language-specific config or printer. This is quite an awkward + # place for auto-format logic, but I thought it was better centralized here than down below + # hidden in a function. We might want to somehow pass the config through to render a + # template so we can move things into the backend code. (tag=centralize-language-diffs) + if self in (FileType.CPP, FileType.CUDA): + # Come up with a fake filename to give to the formatter just for formatting purposes, + # even if this isn't being written to disk + if output_path is not None: + format_cpp_filename = os.path.basename(output_path) + else: + format_cpp_filename = os.fspath(template_name).replace(".jinja", "") + + return format_util.format_cpp( + file_contents, filename=str(CURRENT_DIR / format_cpp_filename) + ) + elif self == FileType.PYTHON: + return format_util.format_py(file_contents) + elif self == FileType.PYTHON_INTERFACE: + return format_util.format_pyi(file_contents) + else: + raise NotImplementedError(f"Unknown autoformatter for {self}") + class RelEnvironment(jinja2.Environment): """ @@ -73,21 +110,16 @@ def join_path(self, template: T.Union[jinja2.Template, str], parent: str) -> str return os.path.normpath(os.path.join(os.path.dirname(parent), str(template))) -def add_preamble(source: str, name: Path, filetype: FileType) -> str: - prefix = ( - "//" - if filetype in (FileType.CPP, FileType.CUDA, FileType.LCM, FileType.TYPESCRIPT) - else "#" - ) +def add_preamble(source: str, name: Path, comment_prefix: str) -> str: dashes = "-" * 77 return ( textwrap.dedent( f""" - {prefix} {dashes} - {prefix} This file was autogenerated by symforce from template: - {prefix} {name} - {prefix} Do NOT modify by hand. - {prefix} {dashes} + {comment_prefix} {dashes} + {comment_prefix} This file was autogenerated by symforce from template: + {comment_prefix} {name} + {comment_prefix} Do NOT modify by hand. + {comment_prefix} {dashes} """ ).lstrip() @@ -143,24 +175,14 @@ def render_template( filetype = FileType.from_template_path(Path(template_name)) template = jinja_env(template_dir).get_template(os.fspath(template_name)) - rendered_str = add_preamble(str(template.render(**data)), template_name, filetype) + rendered_str = add_preamble( + str(template.render(**data)), template_name, comment_prefix=filetype.comment_prefix() + ) if autoformat: - if filetype in (FileType.CPP, FileType.CUDA): - # Come up with a fake filename to give to the formatter just for formatting purposes, even - # if this isn't being written to disk - if output_path is not None: - format_cpp_filename = os.path.basename(output_path) - else: - format_cpp_filename = os.fspath(template_name).replace(".jinja", "") - - rendered_str = format_util.format_cpp( - rendered_str, filename=os.path.join(CURRENT_DIR, format_cpp_filename) - ) - elif filetype == FileType.PYTHON: - rendered_str = format_util.format_py(rendered_str) - elif filetype == FileType.PYTHON_INTERFACE: - rendered_str = format_util.format_pyi(rendered_str) + rendered_str = filetype.autoformat( + file_contents=rendered_str, template_name=template_name, output_path=output_path + ) if output_path: directory = os.path.dirname(output_path) diff --git a/symforce/codegen/types_package_codegen.py b/symforce/codegen/types_package_codegen.py index f998bd8c1..ac21a7b3f 100644 --- a/symforce/codegen/types_package_codegen.py +++ b/symforce/codegen/types_package_codegen.py @@ -94,7 +94,7 @@ def generate_types( lcm_files = [] if len(types_to_generate) > 0: logger.info(f'Creating LCM type at: "{lcm_type_dir}"') - lcm_template = os.path.join(template_util.LCM_TEMPLATE_DIR, "types.lcm.jinja") + lcm_template = template_util.LCM_TEMPLATE_DIR / "types.lcm.jinja" # Type definition lcm_file_name = f"{file_name}.lcm" diff --git a/symforce/codegen/values_codegen.py b/symforce/codegen/values_codegen.py index 8fecf7c05..caa94b0a9 100644 --- a/symforce/codegen/values_codegen.py +++ b/symforce/codegen/values_codegen.py @@ -7,7 +7,7 @@ import re from symforce import typing as T -from symforce.codegen import Codegen, template_util +from symforce.codegen import Codegen, CppConfig, template_util from symforce.values import generated_key_selection from symforce.values.values import Values @@ -33,6 +33,7 @@ def generate_values_keys( skip_directory_nesting: Generate the output file directly into output_dir instead of adding the usual directory structure inside output_dir """ + config = CppConfig() if not isinstance(output_dir, Path): output_dir = Path(output_dir) @@ -50,7 +51,7 @@ def generate_values_keys( cpp_function_dir = output_dir / "cpp" / "symforce" / namespace template_util.render_template( - template_path=Path(template_util.CPP_TEMPLATE_DIR) / "keys.h.jinja", + template_path=config.template_dir() / "keys.h.jinja", data=dict(Codegen.common_data(), namespace=namespace, vars=vars_to_generate), output_path=cpp_function_dir / generated_file_name, ) diff --git a/test/symforce_cpp_code_printer_codegen_test.py b/test/symforce_cpp_code_printer_codegen_test.py index cb7266626..851a31f44 100644 --- a/test/symforce_cpp_code_printer_codegen_test.py +++ b/test/symforce_cpp_code_printer_codegen_test.py @@ -7,13 +7,9 @@ import symforce -from symforce import typing as T -from symforce.test_util import TestCase, sympy_only -from symforce.values import Values - from symforce import codegen -from symforce.codegen import codegen_util from symforce import sympy as sm +from symforce.test_util import TestCase, sympy_only SYMFORCE_DIR = os.path.dirname(os.path.dirname(__file__)) TEST_DATA_DIR = os.path.join( @@ -27,7 +23,7 @@ class SymforceCppCodePrinterTest(TestCase): """ def test_max_min(self) -> None: - printer = codegen_util.get_code_printer(codegen.CppConfig()) + printer = codegen.CppConfig().printer() a = sm.Symbol("a") b = sm.Symbol("b") diff --git a/test/symforce_gen_codegen_test.py b/test/symforce_gen_codegen_test.py index 06f21822b..6aaa586d9 100644 --- a/test/symforce_gen_codegen_test.py +++ b/test/symforce_gen_codegen_test.py @@ -23,7 +23,6 @@ from symforce.codegen import sym_util_package_codegen from symforce.codegen import template_util from symforce.test_util import TestCase, symengine_only -from symforce import path_util SYMFORCE_DIR = os.path.dirname(os.path.dirname(__file__)) TEST_DATA_DIR = os.path.join( @@ -87,9 +86,10 @@ def test_gen_package_codegen_python(self) -> None: """ output_dir = self.make_output_dir("sf_gen_codegen_test_") - cam_package_codegen.generate(config=codegen.PythonConfig(), output_dir=output_dir) + config = codegen.PythonConfig() + cam_package_codegen.generate(config=config, output_dir=output_dir) template_util.render_template( - template_path=os.path.join(template_util.PYTHON_TEMPLATE_DIR, "setup.py.jinja"), + template_path=config.template_dir() / "setup.py.jinja", output_path=os.path.join(output_dir, "setup.py"), data=dict( package_name="symforce-sym", @@ -140,6 +140,7 @@ def test_gen_package_codegen_cpp(self) -> None: """ Test C++ code generation """ + config = codegen.CppConfig() output_dir = self.make_output_dir("sf_gen_codegen_test_") # Prior factors, between factors, and SLAM factors for C++. @@ -147,11 +148,11 @@ def test_gen_package_codegen_cpp(self) -> None: slam_factors_codegen.generate(os.path.join(output_dir, "sym")) # Generate typedefs.h - sym_util_package_codegen.generate(config=codegen.CppConfig(), output_dir=output_dir) + sym_util_package_codegen.generate(config=config, output_dir=output_dir) # Generate cam package, geo package, and tests # This calls geo_package_codegen.generate internally - cam_package_codegen.generate(config=codegen.CppConfig(), output_dir=output_dir) + cam_package_codegen.generate(config=config, output_dir=output_dir) # Check against existing generated package (only on SymEngine) self.compare_or_update_directory( diff --git a/test/symforce_lcm_codegen_test.py b/test/symforce_lcm_codegen_test.py index f3bd969fb..69e902b78 100644 --- a/test/symforce_lcm_codegen_test.py +++ b/test/symforce_lcm_codegen_test.py @@ -17,7 +17,7 @@ def test_generate_lcm(self) -> None: output_dir = self.make_output_dir("sf_lcm_codegen_test") template_util.render_template( - os.path.join(template_util.LCM_TEMPLATE_DIR, "symforce_types.lcm.jinja"), + template_util.LCM_TEMPLATE_DIR / "symforce_types.lcm.jinja", data=lcm_symforce_types_data(), output_path=os.path.join(output_dir, "symforce_types.lcm"), ) From 587483414da3de1f25f69b5deae646c5f82d1e9f Mon Sep 17 00:00:00 2001 From: Hayk Martiros Date: Sat, 11 Jun 2022 13:04:17 -0700 Subject: [PATCH 2/2] Rename sympy/symengine switching from "backend" to "symbolic API" Because we are calling the codegen langauge targets "backends", it would be confusing to keep doing so with the SymPy / SymEngine switch. Backend matches LLVM terminology, so it makes sense there. We switch to using symbolic_api for the latter, which has some downsides in that we're setting the implementation and not the API, but it seems like the best choice of the options we've discussed. --- Makefile | 8 ++-- docs/development.rst | 6 +-- notebooks/storage_D_tangent.ipynb | 2 +- notebooks/symbolic_computation_speedups.ipynb | 2 +- notebooks/tangent_D_storage.ipynb | 2 +- notebooks/tutorials/cameras_tutorial.ipynb | 2 +- notebooks/tutorials/codegen_tutorial.ipynb | 2 +- notebooks/tutorials/geometry_tutorial.ipynb | 2 +- notebooks/tutorials/ops_tutorial.ipynb | 2 +- notebooks/tutorials/sympy_tutorial.ipynb | 4 +- notebooks/tutorials/values_tutorial.ipynb | 2 +- symforce/__init__.py | 48 +++++++++---------- symforce/codegen/codegen_util.py | 4 +- symforce/databuffer.py | 7 ++- .../robot_3d_localization.py | 4 +- symforce/initialization.py | 10 ++-- symforce/notebook_util.py | 2 +- symforce/ops/__init__.py | 4 +- symforce/test_util/epsilon_handling.py | 8 ++-- symforce/test_util/test_case.py | 28 +++++------ test/geo_matrix_test.py | 2 +- test/symforce_codegen_test.py | 2 +- .../symforce_cpp_code_printer_codegen_test.py | 2 +- test/symforce_databuffer_codegen_test.py | 2 +- test/symforce_gen_codegen_test.py | 4 +- test/symforce_gnc_codegen_test.py | 2 +- test/symforce_values_codegen_test.py | 2 +- 27 files changed, 80 insertions(+), 85 deletions(-) diff --git a/Makefile b/Makefile index 46486ddfa..834440022 100644 --- a/Makefile +++ b/Makefile @@ -86,16 +86,16 @@ TEST_CMD=-m unittest discover -s test/ -p *_test.py -v GEN_FILES=test/*codegen*.py test_symengine: - $(TEST_ENV) SYMFORCE_BACKEND=symengine $(PYTHON) $(TEST_CMD) + $(TEST_ENV) SYMFORCE_SYMBOLIC_API=symengine $(PYTHON) $(TEST_CMD) test_sympy: - $(TEST_ENV) SYMFORCE_BACKEND=sympy $(PYTHON) $(TEST_CMD) + $(TEST_ENV) SYMFORCE_SYMBOLIC_API=sympy $(PYTHON) $(TEST_CMD) test: test_symengine test_sympy # Generic target to run a SymEngine codegen test with --update update_%: - $(TEST_ENV) SYMFORCE_BACKEND=symengine $(PYTHON) test/$*.py --update + $(TEST_ENV) SYMFORCE_SYMBOLIC_API=symengine $(PYTHON) test/$*.py --update # All SymForce codegen tests, formatted as update_my_codegen_test targets GEN_FILES_UPDATE_TARGETS=$(shell \ @@ -106,7 +106,7 @@ test_update: $(GEN_FILES_UPDATE_TARGETS) # Generic target to run a SymPy codegen test with --update --run_slow_tests sympy_update_%: - $(TEST_ENV) SYMFORCE_BACKEND=sympy $(PYTHON) test/$*.py --update --run_slow_tests + $(TEST_ENV) SYMFORCE_SYMBOLIC_API=sympy $(PYTHON) test/$*.py --update --run_slow_tests # All SymForce codegen tests, formatted as sympy_update_my_codegen_test targets GEN_FILES_SYMPY_UPDATE_TARGETS=$(shell \ diff --git a/docs/development.rst b/docs/development.rst index bcc93cc94..70f5a6765 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -95,11 +95,11 @@ Much of the core functionality of SymForce is in generating code using the `Jinj For example template files, see ``symforce/codegen/cpp_templates``. ************************************************* -Symbolic Backends +Symbolic API ************************************************* -SymForce uses the `SymPy `_ API, but supports two backend implementations of it. The SymPy backend is pure Python, whereas the `SymEngine `_ backend is wrapped C++. It can be 100-200 times faster for many operations, but is less fully featured and requires a C++ build. +SymForce uses the `SymPy `_ API, but supports two implementations of it. The SymPy implementation is pure Python, whereas the `SymEngine `_ implementation is wrapped C++. It can be 100-200 times faster for many operations, but is less fully featured and requires a C++ build. -To set the backend, you can either use :func:`symforce.set_backend()` before any other imports, or use the ``SYMFORCE_BACKEND`` environment variable with the options ``sympy`` or ``symengine``. By default SymEngine will be used if found, otherwise SymPy. +To set the symbolic API, you can either use :func:`symforce.set_symbolic_api()` before any other imports, or use the ``SYMFORCE_SYMBOLIC_API`` environment variable with the options ``sympy`` or ``symengine``. By default SymEngine will be used if found, otherwise SymPy. ************************************************* Building wheels diff --git a/notebooks/storage_D_tangent.ipynb b/notebooks/storage_D_tangent.ipynb index 28ff6662a..2445d6678 100644 --- a/notebooks/storage_D_tangent.ipynb +++ b/notebooks/storage_D_tangent.ipynb @@ -8,7 +8,7 @@ "source": [ "import symforce\n", "\n", - "symforce.set_backend(\"sympy\")\n", + "symforce.set_symbolic_api(\"sympy\")\n", "symforce.set_log_level(\"warning\")\n", "\n", "from symforce import geo\n", diff --git a/notebooks/symbolic_computation_speedups.ipynb b/notebooks/symbolic_computation_speedups.ipynb index 0c950e5e8..f569b6818 100644 --- a/notebooks/symbolic_computation_speedups.ipynb +++ b/notebooks/symbolic_computation_speedups.ipynb @@ -23,7 +23,7 @@ "# Setup\n", "import symforce\n", "\n", - "symforce.set_backend(\"sympy\")\n", + "symforce.set_symbolic_api(\"sympy\")\n", "\n", "from symforce import geo\n", "from symforce import sympy as sm\n", diff --git a/notebooks/tangent_D_storage.ipynb b/notebooks/tangent_D_storage.ipynb index 1f3a61a94..cd62c8c67 100644 --- a/notebooks/tangent_D_storage.ipynb +++ b/notebooks/tangent_D_storage.ipynb @@ -8,7 +8,7 @@ "source": [ "import symforce\n", "\n", - "symforce.set_backend(\"sympy\")\n", + "symforce.set_symbolic_api(\"sympy\")\n", "symforce.set_log_level(\"warning\")\n", "\n", "from symforce import geo\n", diff --git a/notebooks/tutorials/cameras_tutorial.ipynb b/notebooks/tutorials/cameras_tutorial.ipynb index ab0b89732..1ffc07e4f 100644 --- a/notebooks/tutorials/cameras_tutorial.ipynb +++ b/notebooks/tutorials/cameras_tutorial.ipynb @@ -23,7 +23,7 @@ "# Setup\n", "import symforce\n", "\n", - "symforce.set_backend(\"sympy\")\n", + "symforce.set_symbolic_api(\"sympy\")\n", "symforce.set_log_level(\"warning\")\n", "\n", "from symforce.notebook_util import display\n", diff --git a/notebooks/tutorials/codegen_tutorial.ipynb b/notebooks/tutorials/codegen_tutorial.ipynb index 038a36a61..981961ff1 100644 --- a/notebooks/tutorials/codegen_tutorial.ipynb +++ b/notebooks/tutorials/codegen_tutorial.ipynb @@ -36,7 +36,7 @@ "import os\n", "import symforce\n", "\n", - "symforce.set_backend(\"symengine\")\n", + "symforce.set_symbolic_api(\"symengine\")\n", "symforce.set_log_level(\"warning\")\n", "\n", "from symforce import codegen\n", diff --git a/notebooks/tutorials/geometry_tutorial.ipynb b/notebooks/tutorials/geometry_tutorial.ipynb index e4145ee85..2db6efc9c 100644 --- a/notebooks/tutorials/geometry_tutorial.ipynb +++ b/notebooks/tutorials/geometry_tutorial.ipynb @@ -25,7 +25,7 @@ "# Setup\n", "import symforce\n", "\n", - "symforce.set_backend(\"sympy\")\n", + "symforce.set_symbolic_api(\"sympy\")\n", "symforce.set_log_level(\"warning\")\n", "\n", "from symforce.notebook_util import display\n", diff --git a/notebooks/tutorials/ops_tutorial.ipynb b/notebooks/tutorials/ops_tutorial.ipynb index fcb36d1fc..021b30ff4 100644 --- a/notebooks/tutorials/ops_tutorial.ipynb +++ b/notebooks/tutorials/ops_tutorial.ipynb @@ -27,7 +27,7 @@ "# Setup\n", "import symforce\n", "\n", - "symforce.set_backend(\"sympy\")\n", + "symforce.set_symbolic_api(\"sympy\")\n", "symforce.set_log_level(\"warning\")\n", "\n", "from symforce.notebook_util import display\n", diff --git a/notebooks/tutorials/sympy_tutorial.ipynb b/notebooks/tutorials/sympy_tutorial.ipynb index 6c434712e..1fc607cfb 100644 --- a/notebooks/tutorials/sympy_tutorial.ipynb +++ b/notebooks/tutorials/sympy_tutorial.ipynb @@ -24,7 +24,7 @@ "# Configuration (optional)\n", "import symforce\n", "\n", - "symforce.set_backend(\"sympy\")\n", + "symforce.set_symbolic_api(\"sympy\")\n", "symforce.set_log_level(\"warning\")\n", "from symforce.notebook_util import display, print_expression_tree" ] @@ -33,7 +33,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Always import the SymPy API through SymForce, because symforce can switch out the backend implementation of the API and adds a few minor but important augmentations. Let's define some algebraic symbols:" + "Always import the SymPy API through SymForce, because symforce can switch out the symbolic implementation of the API and adds a few minor but important augmentations. Let's define some algebraic symbols:" ] }, { diff --git a/notebooks/tutorials/values_tutorial.ipynb b/notebooks/tutorials/values_tutorial.ipynb index 9b2280855..da08a4c75 100644 --- a/notebooks/tutorials/values_tutorial.ipynb +++ b/notebooks/tutorials/values_tutorial.ipynb @@ -25,7 +25,7 @@ "# Setup\n", "import symforce\n", "\n", - "symforce.set_backend(\"sympy\")\n", + "symforce.set_symbolic_api(\"sympy\")\n", "symforce.set_log_level(\"warning\")\n", "\n", "from symforce import geo\n", diff --git a/symforce/__init__.py b/symforce/__init__.py index 99fb3221a..fc7f28f63 100644 --- a/symforce/__init__.py +++ b/symforce/__init__.py @@ -61,18 +61,18 @@ def set_log_level(log_level: str) -> None: set_log_level(os.environ.get("SYMFORCE_LOGLEVEL", "WARNING")) # ------------------------------------------------------------------------------------------------- -# Symbolic backend configuration +# Symbolic API configuration # ------------------------------------------------------------------------------------------------- sympy: T.Any = None from . import initialization -def _set_backend(sympy_module: ModuleType) -> None: +def _set_symbolic_api(sympy_module: ModuleType) -> None: # Make symforce-specific modifications to the sympy API initialization.modify_symbolic_api(sympy_module) - # Set this as the default backend + # Set this as the default symbolic API global sympy # pylint: disable=global-statement sympy = sympy_module @@ -143,13 +143,13 @@ def _use_symengine() -> None: logger.critical("Commanded to use symengine, but failed to import.") raise - _set_backend(symengine) + _set_symbolic_api(symengine) def _use_sympy() -> None: import sympy as sympy_py - _set_backend(sympy_py) + _set_symbolic_api(sympy_py) sympy_py.init_printing() @@ -171,54 +171,54 @@ def set_symengine_eval_on_sympify(eval_on_sympy: bool = True) -> None: logger.debug("set_symengine_fast_sympify has no effect when not using symengine") -def set_backend(backend: str) -> None: +def set_symbolic_api(name: str) -> None: """ - Set the symbolic backend for symforce. The sympy backend is the default and pure python, - whereas the symengine backend is C++ and requires building the symengine library. It can + Set the symbolic API for symforce. The sympy API is the default and pure python, + whereas the symengine API is C++ and requires building the symengine library. It can be 100-200 times faster for many operations, but is less fully featured. The default is symengine if available else sympy, but can be set by one of: - 1) The SYMFORCE_BACKEND environment variable + 1) The SYMFORCE_SYMBOLIC_API environment variable 2) Calling this function before any other symforce imports Args: - backend (str): {sympy, symengine} + name (str): {sympy, symengine} """ # TODO(hayk): Could do a better job of checking what's imported and raising an error # if this isn't the first thing imported/called from symforce. - if sympy and backend == sympy.__package__: - logger.debug(f'already on backend "{backend}"') + if sympy and name == sympy.__package__: + logger.debug(f'already on symbolic API "{name}"') return else: - logger.debug(f'backend: "{backend}"') + logger.debug(f'symbolic API: "{name}"') - if backend == "sympy": + if name == "sympy": _use_sympy() - elif backend == "symengine": + elif name == "symengine": _use_symengine() else: - raise NotImplementedError(f'Unknown backend: "{backend}"') + raise NotImplementedError(f'Unknown symbolic API: "{name}"') # Set default to symengine if available, else sympy -if "SYMFORCE_BACKEND" in os.environ: - set_backend(os.environ["SYMFORCE_BACKEND"]) +if "SYMFORCE_SYMBOLIC_API" in os.environ: + set_symbolic_api(os.environ["SYMFORCE_SYMBOLIC_API"]) else: try: symengine = _import_symengine_from_build() - logger.debug("No SYMFORCE_BACKEND set, found and using symengine.") - set_backend("symengine") + logger.debug("No SYMFORCE_SYMBOLIC_API set, found and using symengine.") + set_symbolic_api("symengine") except ImportError: - logger.debug("No SYMFORCE_BACKEND set, no symengine found, using sympy.") - set_backend("sympy") + logger.debug("No SYMFORCE_SYMBOLIC_API set, no symengine found, using sympy.") + set_symbolic_api("sympy") -def get_backend() -> str: +def get_symbolic_api() -> str: """ - Return the current backend as a string. + Return the current symbolic API as a string. Returns: str: diff --git a/symforce/codegen/codegen_util.py b/symforce/codegen/codegen_util.py index 2cdebfae2..5fd4f1cb8 100644 --- a/symforce/codegen/codegen_util.py +++ b/symforce/codegen/codegen_util.py @@ -240,8 +240,8 @@ def tmp_symbols() -> T.Iterable[str]: yield sm.Symbol(f"_tmp{i}") if cse_optimizations is not None: - if symforce.get_backend() == "symengine": - raise ValueError("cse_optimizations is not supported on the symengine backend") + if symforce.get_symbolic_api() == "symengine": + raise ValueError("cse_optimizations is not supported on symengine") temps, flat_simplified_outputs = sm.cse( flat_output_exprs, symbols=tmp_symbols(), optimizations=cse_optimizations diff --git a/symforce/databuffer.py b/symforce/databuffer.py index eedc27eff..8b5050984 100644 --- a/symforce/databuffer.py +++ b/symforce/databuffer.py @@ -11,10 +11,9 @@ class DataBuffer(sympy.MatrixSymbol): """ - Custom class for when using the sympy backend to make MatrixSymbol consistent with - symforce's custom 1-D Databuffer symengine - We want to force Databuffers to be 1-D since otherwise CSE will (rightfully) treat each index - as a separate expression. + Custom class to make sympy's MatrixSymbol consistent with symengine, where we have a custom + 1-D Databuffer. We want to force Databuffers to be 1-D since otherwise CSE will (rightfully) + treat each index as a separate expression. """ # HACK(harrison): needed to get around the flast that DataBuffer needs to be called from diff --git a/symforce/examples/robot_3d_localization/robot_3d_localization.py b/symforce/examples/robot_3d_localization/robot_3d_localization.py index 60253c866..c2bdad516 100644 --- a/symforce/examples/robot_3d_localization/robot_3d_localization.py +++ b/symforce/examples/robot_3d_localization/robot_3d_localization.py @@ -21,8 +21,8 @@ from symforce import sympy as sm from symforce import typing as T -if symforce.get_backend() != "symengine": - logger.warning("The 3D Localization example is very slow on the sympy backend") +if symforce.get_symbolic_api() != "symengine": + logger.warning("The 3D Localization example is very slow on sympy. Use symengine.") NUM_POSES = 5 NUM_LANDMARKS = 20 diff --git a/symforce/initialization.py b/symforce/initialization.py index 67a91722f..b2e3a3643 100644 --- a/symforce/initialization.py +++ b/symforce/initialization.py @@ -93,7 +93,7 @@ def new_symbol( def override_simplify(sympy_module: T.Type) -> None: """ - Override simplify so that we can use it with the symengine backend + Override simplify so that we can use it with symengine. Args: sympy_module (module): @@ -112,7 +112,7 @@ def simplify(*args: T.Any, **kwargs: T.Any) -> sympy.Basic: def override_limit(sympy_module: T.Type) -> None: """ - Override limit so that we can use it with the symengine backend + Override limit so that we can use it with symengine. Args: sympy_module (module): @@ -250,7 +250,7 @@ def override_subs(sympy_module: T.Type) -> None: self, _get_subs_dict(*args, **kwargs), **kwargs ) else: - raise NotImplementedError(f"Unknown backend: '{sympy_module.__name__}'") + raise NotImplementedError(f"Unknown symbolic API: '{sympy_module.__name__}'") def override_solve(sympy_module: T.Type) -> None: @@ -274,7 +274,7 @@ def solve(*args: T.Any, **kwargs: T.Any) -> T.List[T.Scalar]: return [] else: raise NotImplementedError( - f"sm.solve currently only supports FiniteSet and EmptySet results on the SymEngine backend, got {type(solution)} instead" + f"sm.solve currently only supports FiniteSet and EmptySet results on SymEngine, got {type(solution)} instead" ) sympy_module.solve = solve @@ -282,7 +282,7 @@ def solve(*args: T.Any, **kwargs: T.Any) -> T.List[T.Scalar]: # This one is fine as is return else: - raise NotImplementedError(f"Unknown backend: '{sympy_module.__name__}'") + raise NotImplementedError(f"Unknown symbolic API: '{sympy_module.__name__}'") def override_count_ops(sympy_module: T.Type) -> None: diff --git a/symforce/notebook_util.py b/symforce/notebook_util.py index d14841987..8db2d0960 100644 --- a/symforce/notebook_util.py +++ b/symforce/notebook_util.py @@ -30,7 +30,7 @@ def display(*args: T.Any) -> None: """ Display the given expressions in latex, or print if not an expression. """ - if symforce.get_backend() == "sympy": + if symforce.get_symbolic_api() == "sympy": IPython.display.display(*args) return diff --git a/symforce/ops/__init__.py b/symforce/ops/__init__.py index 427569036..d21059e49 100644 --- a/symforce/ops/__init__.py +++ b/symforce/ops/__init__.py @@ -34,8 +34,8 @@ class ScalarExpr(abc.ABC): """ Metaclass for scalar expressions - DataBuffer is a subclass of sm.Expr in both backends, but we do not want it to be registered - under ScalarLieGroupOps. + DataBuffer is a subclass of sm.Expr but we do not want it to be registered under + ScalarLieGroupOps. """ @abc.abstractmethod diff --git a/symforce/test_util/epsilon_handling.py b/symforce/test_util/epsilon_handling.py index 533005d37..a2febecf9 100644 --- a/symforce/test_util/epsilon_handling.py +++ b/symforce/test_util/epsilon_handling.py @@ -43,8 +43,8 @@ def is_value_with_epsilon_correct( """ # Converting between SymPy and SymEngine breaks substitution afterwards, so we require - # running with the SymPy backend - assert symforce.get_backend() == "sympy" + # running with SymPy. + assert symforce.get_symbolic_api() == "sympy" # Create symbols x = sm.Symbol("x", real=True) @@ -115,8 +115,8 @@ def is_derivative_with_epsilon_correct( """ # Converting between SymPy and SymEngine breaks substitution afterwards, so we require - # running with the SymPy backend - assert symforce.get_backend() == "sympy" + # running with SymPy. + assert symforce.get_symbolic_api() == "sympy" # Create symbols x = sm.Symbol("x", real=True) diff --git a/symforce/test_util/test_case.py b/symforce/test_util/test_case.py index c8cbe2f42..d998d7d1f 100644 --- a/symforce/test_util/test_case.py +++ b/symforce/test_util/test_case.py @@ -24,7 +24,7 @@ class TestCase(SymforceTestCaseMixin): """ # Set by the --run_slow_tests flag to indicate that we should run all tests even - # if we're on the SymPy backend + # if we're on SymPy. _RUN_SLOW_TESTS = False @staticmethod @@ -84,32 +84,29 @@ def compile_and_run_cpp( def sympy_only(func: T.Callable) -> T.Callable: """ - Decorator to mark a test to only run on the SymPy backend, and skip otherwise + Decorator to mark a test to only run on SymPy, and skip otherwise. """ - backend = symforce.get_backend() - if backend != "sympy": - return unittest.skip("This test only runs on the SymPy backend")(func) + if symforce.get_symbolic_api() != "sympy": + return unittest.skip("This test only runs on SymPy symbolic API.")(func) else: return func def symengine_only(func: T.Callable) -> T.Callable: """ - Decorator to mark a test to only run on the SymEngine backend, and skip otherwise + Decorator to mark a test to only run on the SymEngine, and skip otherwise. """ - backend = symforce.get_backend() - if backend != "symengine": - return unittest.skip("This test only runs on the SymEngine backend")(func) + if symforce.get_symbolic_api() != "symengine": + return unittest.skip("This test only runs on the SymEngine symbolic API")(func) else: return func def expected_failure_on_sympy(func: T.Callable) -> T.Callable: """ - Decorator to mark a test to be expected to fail only on the SymPy backend. + Decorator to mark a test to be expected to fail only on SymPy.. """ - backend = symforce.get_backend() - if backend == "sympy": + if symforce.get_symbolic_api() == "sympy": return unittest.expectedFailure(func) else: return func @@ -117,11 +114,10 @@ def expected_failure_on_sympy(func: T.Callable) -> T.Callable: def slow_on_sympy(func: T.Callable) -> T.Callable: """ - Decorator to mark a test as slow on the sympy backend. Will be skipped unless passed the + Decorator to mark a test as slow on sympy.. Will be skipped unless passed the --run_slow_tests flag """ - backend = symforce.get_backend() - if backend == "sympy" and not TestCase.should_run_slow_tests(): - return unittest.skip("This test is too slow on the SymPy backend")(func) + if symforce.get_symbolic_api() == "sympy" and not TestCase.should_run_slow_tests(): + return unittest.skip("This test is too slow on SymPy.")(func) else: return func diff --git a/test/geo_matrix_test.py b/test/geo_matrix_test.py index e72813651..607df756d 100644 --- a/test/geo_matrix_test.py +++ b/test/geo_matrix_test.py @@ -307,7 +307,7 @@ def test_clamp_norm(self) -> None: v_clamped = v.clamp_norm(10) self.assertStorageNear(v_clamped, v) - if symforce.get_backend() == "sympy": + if symforce.get_symbolic_api() == "sympy": with self.subTest("epsilon handling"): def scalar_clamp_norm(x: T.Scalar, epsilon: T.Scalar) -> T.Scalar: diff --git a/test/symforce_codegen_test.py b/test/symforce_codegen_test.py index a9b5e72e5..ecc9336cb 100644 --- a/test/symforce_codegen_test.py +++ b/test/symforce_codegen_test.py @@ -32,7 +32,7 @@ SYMFORCE_DIR = Path(__file__).parent.parent TEST_DATA_DIR = ( - SYMFORCE_DIR / "test" / "symforce_function_codegen_test_data" / symforce.get_backend() + SYMFORCE_DIR / "test" / "symforce_function_codegen_test_data" / symforce.get_symbolic_api() ) # Test function diff --git a/test/symforce_cpp_code_printer_codegen_test.py b/test/symforce_cpp_code_printer_codegen_test.py index 851a31f44..ccf88fef1 100644 --- a/test/symforce_cpp_code_printer_codegen_test.py +++ b/test/symforce_cpp_code_printer_codegen_test.py @@ -13,7 +13,7 @@ SYMFORCE_DIR = os.path.dirname(os.path.dirname(__file__)) TEST_DATA_DIR = os.path.join( - SYMFORCE_DIR, "test", "symforce_function_codegen_test_data", symforce.get_backend() + SYMFORCE_DIR, "test", "symforce_function_codegen_test_data", symforce.get_symbolic_api() ) diff --git a/test/symforce_databuffer_codegen_test.py b/test/symforce_databuffer_codegen_test.py index 0f2e7ccc5..e6d3651eb 100644 --- a/test/symforce_databuffer_codegen_test.py +++ b/test/symforce_databuffer_codegen_test.py @@ -19,7 +19,7 @@ CURRENT_DIR = Path(__file__).parent SYMFORCE_DIR = CURRENT_DIR.parent TEST_DATA_DIR = SYMFORCE_DIR.joinpath( - "test", "symforce_function_codegen_test_data", symforce.get_backend() + "test", "symforce_function_codegen_test_data", symforce.get_symbolic_api() ) diff --git a/test/symforce_gen_codegen_test.py b/test/symforce_gen_codegen_test.py index 6aaa586d9..c8ecd5298 100644 --- a/test/symforce_gen_codegen_test.py +++ b/test/symforce_gen_codegen_test.py @@ -26,7 +26,7 @@ SYMFORCE_DIR = os.path.dirname(os.path.dirname(__file__)) TEST_DATA_DIR = os.path.join( - SYMFORCE_DIR, "test", "symforce_function_codegen_test_data", symforce.get_backend() + SYMFORCE_DIR, "test", "symforce_function_codegen_test_data", symforce.get_symbolic_api() ) @@ -100,7 +100,7 @@ def test_gen_package_codegen_python(self) -> None: ) # Test against checked-in geo package (only on SymEngine) - if symforce.get_backend() == "symengine": + if symforce.get_symbolic_api() == "symengine": self.compare_or_update_directory( actual_dir=os.path.join(output_dir, "sym"), expected_dir=os.path.join(SYMFORCE_DIR, "gen", "python", "sym"), diff --git a/test/symforce_gnc_codegen_test.py b/test/symforce_gnc_codegen_test.py index 7dd58446b..cccc74500 100644 --- a/test/symforce_gnc_codegen_test.py +++ b/test/symforce_gnc_codegen_test.py @@ -14,7 +14,7 @@ SYMFORCE_DIR = os.path.dirname(os.path.dirname(__file__)) TEST_DATA_DIR = os.path.join( - SYMFORCE_DIR, "test", "symforce_function_codegen_test_data", symforce.get_backend() + SYMFORCE_DIR, "test", "symforce_function_codegen_test_data", symforce.get_symbolic_api() ) diff --git a/test/symforce_values_codegen_test.py b/test/symforce_values_codegen_test.py index 3321f765c..e4b4c1c1b 100644 --- a/test/symforce_values_codegen_test.py +++ b/test/symforce_values_codegen_test.py @@ -15,7 +15,7 @@ SYMFORCE_DIR = os.path.dirname(os.path.dirname(__file__)) TEST_DATA_DIR = os.path.join( - SYMFORCE_DIR, "test", "symforce_function_codegen_test_data", symforce.get_backend() + SYMFORCE_DIR, "test", "symforce_function_codegen_test_data", symforce.get_symbolic_api() )