From 5c0ed32871f5d1be9621f59ffbe57ccf4c7756be Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 9 Jan 2024 05:00:02 +0000 Subject: [PATCH] [SymForce] Misc docs updates Fix formatting of exception classes Fixed some documentation of symbolic APIs, which was pretty limited and out of date Fixed dark mode code printing Reviewers: michael-f,philipp,nathan,hayk,brad,ryan-b,chao Topic: sf-docs GitOrigin-RevId: edab4f42152a60843b436c910e727e80b1157bb4 --- dev_requirements.txt | 8 +--- docs/static/css/custom.css | 14 ++++++ notebooks/tutorials/cameras_tutorial.ipynb | 6 --- notebooks/tutorials/codegen_tutorial.ipynb | 3 -- notebooks/tutorials/ops_tutorial.ipynb | 2 +- notebooks/tutorials/sympy_tutorial.ipynb | 46 +++++++++++++++++-- notebooks/tutorials/values_tutorial.ipynb | 2 - pyproject.toml | 5 ++ setup.py | 1 - symforce/__init__.py | 15 ++++-- symforce/notebook_util.py | 38 +++++++-------- symforce/values/values.py | 2 +- third_party/symenginepy/symengine/printing.py | 6 +-- 13 files changed, 96 insertions(+), 52 deletions(-) diff --git a/dev_requirements.txt b/dev_requirements.txt index 358d007b6..3cd0d2359 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -356,20 +356,14 @@ traitlets==5.11.2 # nbconvert # nbformat # nbsphinx -types-docutils==0.20.0.3 - # via types-pygments types-jinja2==2.11.9 # via symforce (setup.py) types-markupsafe==1.1.10 # via types-jinja2 -types-pygments==2.16.0.0 - # via symforce (setup.py) types-requests==2.31.0.8 # via symforce (setup.py) types-setuptools==68.2.0.0 - # via - # symforce (setup.py) - # types-pygments + # via symforce (setup.py) typing-extensions==4.8.0 # via # astroid diff --git a/docs/static/css/custom.css b/docs/static/css/custom.css index 2823e95e8..fd7e837d3 100644 --- a/docs/static/css/custom.css +++ b/docs/static/css/custom.css @@ -36,6 +36,8 @@ section#symforce-home h1 { * Font size tweaks */ +/* Python */ + /* h1 module names smaller */ section[id^="module-"] h1 { font-size: 1.5em; @@ -51,6 +53,18 @@ dl.py.class > dt.sig > span.sig-name { font-size: 1.8em; } +/* `exception` keywords larger */ +dl.py.exception > dt.sig > em.property { + font-size: 1.3em; +} + +/* exception names larger */ +dl.py.exception > dt.sig > span.sig-name { + font-size: 1.8em; +} + +/* C++ */ + /* h1 class names smaller */ section[id^="class-"] h1 { font-size: 1.5em; diff --git a/notebooks/tutorials/cameras_tutorial.ipynb b/notebooks/tutorials/cameras_tutorial.ipynb index 93ec71759..afb4d3ba2 100644 --- a/notebooks/tutorials/cameras_tutorial.ipynb +++ b/notebooks/tutorials/cameras_tutorial.ipynb @@ -20,12 +20,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Setup\n", - "import symforce\n", - "\n", - "symforce.set_symbolic_api(\"sympy\")\n", - "symforce.set_log_level(\"warning\")\n", - "\n", "import symforce.symbolic as sf\n", "from symforce.notebook_util import display" ] diff --git a/notebooks/tutorials/codegen_tutorial.ipynb b/notebooks/tutorials/codegen_tutorial.ipynb index 17beb861c..c7863d8bb 100644 --- a/notebooks/tutorials/codegen_tutorial.ipynb +++ b/notebooks/tutorials/codegen_tutorial.ipynb @@ -46,10 +46,8 @@ "\n", "import symforce.symbolic as sf\n", "from symforce import codegen\n", - "from symforce import ops\n", "from symforce.codegen import codegen_util\n", "from symforce.notebook_util import display\n", - "from symforce.notebook_util import display_code\n", "from symforce.notebook_util import display_code_file\n", "from symforce.values import Values" ] @@ -136,7 +134,6 @@ ")\n", "\n", "data = codegen_with_jacobians.generate_function()\n", - "from symforce.notebook_util import display_code_file\n", "\n", "display_code_file(data.generated_files[0], \"C++\")" ] diff --git a/notebooks/tutorials/ops_tutorial.ipynb b/notebooks/tutorials/ops_tutorial.ipynb index 37645c91b..394c761c5 100644 --- a/notebooks/tutorials/ops_tutorial.ipynb +++ b/notebooks/tutorials/ops_tutorial.ipynb @@ -15,7 +15,7 @@ "\n", "There are three core concepts, each of which is a superset of the previous. The core routines use these ops interfaces rather than calling methods on types directly. The [ops package](../api/symforce.ops.html) docs provide much more detail and each op is tested on each type, but examples are given here.\n", "\n", - "In the case of lists of objects or a Values object (see the [Values tutorial](../tutorials/values_tutorial.html)), we perform element-wise operations." + "In the case of different containers, such as lists, dataclasses, or Values objects (see the [Values tutorial](../tutorials/values_tutorial.html)), we perform operations recursively over their elements." ] }, { diff --git a/notebooks/tutorials/sympy_tutorial.ipynb b/notebooks/tutorials/sympy_tutorial.ipynb index b51ae5737..612c8d43e 100644 --- a/notebooks/tutorials/sympy_tutorial.ipynb +++ b/notebooks/tutorials/sympy_tutorial.ipynb @@ -11,21 +11,41 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "SymForce is built on the SymPy API for symbolic algebra. If you're not familiar with SymPy or symbolic computation, go through [their](https://docs.sympy.org/latest/tutorial/index.html) tutorial.\n", + "SymForce is built on the [SymPy](https://sympy.org) API for symbolic algebra. If you're not familiar with SymPy or symbolic computation, go through [their](https://docs.sympy.org/latest/tutorial/index.html) tutorial.\n", "Some basic usage is shown here." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, some optional configuration - see [Symbolic APIs](#Symbolic-APIs) below for more information:" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Configuration (optional)\n", "import symforce\n", "\n", - "symforce.set_symbolic_api(\"sympy\")\n", - "symforce.set_log_level(\"warning\")\n", + "symforce.set_symbolic_api(\"symengine\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Some imports that are useful for notebooks - you'll see these across many of our tutorials:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "from symforce.notebook_util import display\n", "from symforce.notebook_util import print_expression_tree" ] @@ -34,7 +54,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "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:" + "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. [symforce.symbolic](../api/symforce.symbolic.html) includes both the supported parts of the underlying symbolic API, as well as all of the types from [symforce.geo](../api/symforce.geo.html) and [symforce.cam](../api/symforce.cam.html). Let's define some algebraic symbols:" ] }, { @@ -122,6 +142,22 @@ "source": [ "display(sf.series(expr, y))" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Symbolic APIs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "SymForce supports two underling symbolic APIs. The [SymPy](https://sympy.org) API is pure Python, and the [SymEngine](https://symengine.org) API is a C++ implementation of much of SymPy. SymEngine is the default, and if you installed SymForce with `pip` or built it from source you'll have it already. SymEngine is much faster than SymPy by a factor of 100 or more. However, SymEngine doesn't implement everything SymPy does. SymForce abstracts over some of these differences, and adds support for some operations by converting between SymEngine and SymPy calls.\n", + "\n", + "For how to set the symbolic api, see the docs for [symforce.set_symbolic_api](../api/symforce.html#symforce.set_symbolic_api)." + ] } ], "metadata": { diff --git a/notebooks/tutorials/values_tutorial.ipynb b/notebooks/tutorials/values_tutorial.ipynb index fd9427074..6328d3bac 100644 --- a/notebooks/tutorials/values_tutorial.ipynb +++ b/notebooks/tutorials/values_tutorial.ipynb @@ -30,8 +30,6 @@ "\n", "import symforce.symbolic as sf\n", "from symforce.notebook_util import display\n", - "from symforce.notebook_util import display_code\n", - "from symforce.notebook_util import display_code_file\n", "from symforce.values import Values" ] }, diff --git a/pyproject.toml b/pyproject.toml index 8cc361c33..b233c0c1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,11 @@ line-length = 100 exclude = ["third_party", "build", ".eggs"] extend-include = ["*.ipynb"] +select = [ + "I", # isort + "RUF100", # unused-noqa +] + [tool.ruff.lint.isort] known-first-party = ["sym", "symforce"] force-single-line = true diff --git a/setup.py b/setup.py index 6bb4778fd..bccc99e01 100644 --- a/setup.py +++ b/setup.py @@ -453,7 +453,6 @@ def fixed_readme() -> str: # NOTE(brad): A transitive dependency of pylint. Added here only to pin the version. "lazy-object-proxy>=1.9.0", "types-jinja2", - "types-pygments", "types-requests", "types-setuptools", ], diff --git a/symforce/__init__.py b/symforce/__init__.py index 9c4ebb7d6..170421f8d 100644 --- a/symforce/__init__.py +++ b/symforce/__init__.py @@ -170,13 +170,18 @@ def _use_sympy() -> None: def set_symbolic_api(name: str) -> None: """ - 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. + Set the symbolic API for symforce - The default is symengine if available else sympy, but can be set by one of: + See the SymPy tutorial for information on the symbolic APIs that can be used: + https://symforce.org/tutorials/sympy_tutorial.html - 1) The SYMFORCE_SYMBOLIC_API environment variable + By default, SymForce will use the ``symengine`` API if it is available. If the symbolic API is + set to ``sympy`` it will use that. If ``symengine`` is not available and the symbolic API was + not set, it will emit a warning and use the ``sympy`` API. + + The symbolic API can be set by one of: + + 1) The ``SYMFORCE_SYMBOLIC_API`` environment variable 2) Calling this function before any other symforce imports Args: diff --git a/symforce/notebook_util.py b/symforce/notebook_util.py index a71fd61d4..d00e54456 100644 --- a/symforce/notebook_util.py +++ b/symforce/notebook_util.py @@ -8,7 +8,6 @@ """ import IPython -import pygments import sympy as sympy_py sympy_py.init_printing() @@ -17,11 +16,18 @@ import symforce.symbolic as sf from symforce import typing as T +if symforce.get_symbolic_api() == "symengine": + sf.sympy.init_printing() + def display(*args: T.Any) -> None: """ Display the given expressions in latex, or print if not an expression. """ + # TODO(aaron): This should all be unnecessary on new symengine. The problem is that our version + # of symengine does not define `DenseMatrixBase._repr_latex_`, so we need to convert symengine + # matrices to sympy + if symforce.get_symbolic_api() == "sympy": IPython.display.display(*args) return @@ -39,24 +45,15 @@ def display(*args: T.Any) -> None: IPython.display.display(*args) -def display_code(code: str, language: str) -> None: +def display_code(code: str, language: str = None) -> None: """ Display code with syntax highlighting. Args: - code (str): Source code - language (str): {python, c++, anything supported by pygments} + code: Source code + language: {python, c++, anything supported by pygments} """ - # types-pygments doesn't have the type for this - lexer = T.cast(T.Any, pygments).lexers.get_lexer_by_name(language) - - # And sometimes not this either - formatter = T.cast(T.Any, pygments).formatters.HtmlFormatter( # pylint: disable=no-member - noclasses=True - ) - html = pygments.highlight(code, lexer, formatter) - - IPython.display.display(IPython.display.HTML(html)) + IPython.display.display(IPython.display.Code(code, language=language)) def display_code_file(path: T.Openable, language: str) -> None: @@ -64,8 +61,8 @@ def display_code_file(path: T.Openable, language: str) -> None: Display code from a file path with syntax highlighting. Args: - path (T.Openable): Path to source file - language (str): {python, c++, anything supported by pygments} + path: Path to source file + language: {python, c++, anything supported by pygments} """ with open(path) as f: code = f.read() @@ -73,12 +70,17 @@ def display_code_file(path: T.Openable, language: str) -> None: display_code(code, language) -def print_expression_tree(expr: sf.Expr) -> None: +def print_expression_tree(expr: sf.Expr, assumptions: bool = False) -> None: """ Print a SymPy expression tree, ignoring node attributes + + Args: + expr: The expression to print + assumptions: Whether to include assumption information for nodes. See + ``sympy.printing.tree`` for more information. """ from sympy.printing.tree import tree - unfiltered_tree = tree(expr).split("\n") + unfiltered_tree = tree(expr, assumptions=assumptions).split("\n") filtered_tree = "\n".join(v for i, v in enumerate(unfiltered_tree) if "+-" in v or i == 0) print(filtered_tree) diff --git a/symforce/values/values.py b/symforce/values/values.py index dca833ec4..90c338fe8 100644 --- a/symforce/values/values.py +++ b/symforce/values/values.py @@ -144,7 +144,7 @@ def get_index_from_items(items: T.Iterable[T.Tuple[str, T.Any]]) -> T.Dict[str, arbitrary names for each element in ``my_list``) """ offset = 0 - index_dict = collections.OrderedDict() + index_dict = {} for name, value in items: entry_helper = lambda datatype=type( value diff --git a/third_party/symenginepy/symengine/printing.py b/third_party/symenginepy/symengine/printing.py index c6a06ef91..a605ee974 100644 --- a/third_party/symenginepy/symengine/printing.py +++ b/third_party/symenginepy/symengine/printing.py @@ -1,5 +1,5 @@ from symengine.lib.symengine_wrapper import ccode, sympify, Basic -import symengine.lib.symengine_wrapper +from symengine.lib import symengine_wrapper class CCodePrinter: @@ -29,6 +29,6 @@ def init_printing(pretty_print=True, use_latex=True): if pretty_print: if not use_latex: raise RuntimeError("Only latex is supported for pretty printing") - symengine.lib.symengine_wrapper.repr_latex[0] = True + symengine_wrapper.repr_latex[0] = True else: - symengine.lib.symengine_wrapper.repr_latex[0] = False + symengine_wrapper.repr_latex[0] = False