symforce


Namesymforce JSON
Version 0.9.0 PyPI version JSON
download
home_pagehttps://symforce.org
SummaryFast symbolic computation, code generation, and nonlinear optimization for robotics
upload_time2023-06-08 14:30:57
maintainer
docs_urlNone
author
requires_python>=3.8
licenseApache 2.0
keywords python computer-vision cpp robotics optimization structure-from-motion motion-planning code-generation slam autonomous-vehicles symbolic-computation
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            <!-- There's no good way to make an image that's different on light/dark mode that also links
    somewhere.  The closest thing is to add a #gh-light-mode-only to the target, which does this,
    but is kinda confusing -->
![SymForce](https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/symforce_banner.png#gh-light-mode-only)


| 📣 We are open for internships!  If you are interested in contributing to SymForce, visit the program page [here](https://github.com/symforce-org/symforce/discussions/242) to learn more.  |
|-----------------------------------------|

<p align="center">
<a href="https://github.com/symforce-org/symforce/actions/workflows/ci.yml?query=branch%3Amain"><img alt="CI status" src="https://github.com/symforce-org/symforce/actions/workflows/ci.yml/badge.svg" /></a>
<a href="https://symforce.org"><img alt="Documentation" src="https://img.shields.io/badge/api-docs-blue" /></a>
<a href="https://github.com/symforce-org/symforce"><img alt="Source Code" src="https://img.shields.io/badge/source-code-blue" /></a>
<a href="https://github.com/symforce-org/symforce/issues"><img alt="Issues" src="https://img.shields.io/badge/issue-tracker-blue" /></a>
<img alt="Python 3.8 | 3.9 | 3.10" src="https://img.shields.io/pypi/pyversions/symforce" />
<img alt="C++14" src="https://img.shields.io/badge/c++-14-blue" />
<a href="https://pypi.org/project/symforce/"><img alt="PyPI" src="https://img.shields.io/pypi/v/symforce" /></a>
<a href="https://github.com/symforce-org/symforce/tree/main/LICENSE"><img alt="Apache License" src="https://img.shields.io/pypi/l/symforce" /></a>
</p>

---

SymForce is a fast symbolic computation and code generation library for robotics applications like computer vision, state estimation, motion planning, and controls. It combines the development speed and flexibility of symbolic mathematics with the performance of autogenerated, highly optimized code in C++ or any target runtime language. SymForce contains three independently useful systems:

+ **Symbolic Toolkit** - builds on the SymPy API to provide rigorous geometric and camera types, lie group calculus, singularity handling, and tools to model complex problems

+ **Code Generator** - transforms symbolic expressions into blazing-fast, branchless code with clean APIs and minimal dependencies, with a template system to target any language

+ **Optimization Library** - a fast tangent-space optimization library based on factor graphs, with a highly optimized implementation for real-time robotics applications

SymForce automatically computes tangent space Jacobians, eliminating the need for any bug-prone handwritten derivatives. Generated functions can be directly used as factors in our nonlinear optimizer. This workflow enables faster runtime functions, faster development time, and fewer lines of handwritten code versus alternative methods.

SymForce is developed and maintained by [Skydio](https://skydio.com/). It is used in production to accelerate tasks like SLAM, bundle adjustment, calibration, and sparse nonlinear MPC for autonomous robots at scale.

<br/>

<img alt="SymForce" src="https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/symforce_diagram.png" width="700px"/>

<br/>

#### Features

 + Symbolic implementations of geometry and camera types with Lie group operations
 + Code generation of fast native runtime code from symbolic expressions, reducing duplication and minimizing bugs
 + Novel tools to compute fast and correct tangent-space jacobians for any expression, avoiding all handwritten derivatives
 + Strategies for flattening computation and leveraging sparsity that can yield 10x speedups over standard autodiff
 + A fast tangent-space optimization library in C++ and Python based on factor graphs
 + Rapid prototyping and analysis of complex problems with symbolic math, with a seamless workflow into production use
 + Embedded-friendly C++ generation of templated Eigen code with zero dynamic memory allocation
 + Highly performant, modular, tested, and extensible code

### Read the paper: <a href="https://arxiv.org/abs/2204.07889">https://arxiv.org/abs/2204.07889</a>

### And watch the video: <a href="https://youtu.be/QO_ltJRNj0o">https://youtu.be/QO_ltJRNj0o</a>

SymForce was published to [RSS 2022](https://roboticsconference.org/). Please cite it as follows:

```
@inproceedings{Martiros-RSS-22,
    author    = {Hayk Martiros AND Aaron Miller AND Nathan Bucki AND Bradley Solliday AND Ryan Kennedy AND Jack Zhu AND Tung Dang AND Dominic Pattison AND Harrison Zheng AND Teo Tomic AND Peter Henry AND Gareth Cross AND Josiah VanderMey AND Alvin Sun AND Samuel Wang AND Kristen Holtz},
    title     = {{SymForce: Symbolic Computation and Code Generation for Robotics}},
    booktitle = {Proceedings of Robotics: Science and Systems},
    year      = {2022},
    doi       = {10.15607/RSS.2022.XVIII.041}
}
```

# Install

Install with pip:

```bash
pip install symforce
```

Verify the installation in Python:
```python
>>> import symforce.symbolic as sf
>>> sf.Rot3()
```

This installs pre-compiled C++ components of SymForce on Linux and Mac using pip wheels, but does not include C++ headers. If you want to compile against C++ SymForce types (like `sym::Optimizer`), you currently need to <a href="#build-from-source">build from source</a>.

# Tutorial

Let's walk through a simple example of modeling and solving an optimization problem with SymForce. In this example a robot moves through a 2D plane and the goal is to estimate its pose at multiple time steps given noisy measurements.

The robot measures:

 * the distance it traveled from an odometry sensor
 * relative bearing angles to known landmarks in the scene

The robot's heading angle is defined counter-clockwise from the x-axis, and its relative bearing measurements are defined from the robot's forward direction:

<img alt="Robot 2D Localization Figure" src="https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/robot_2d_localization/problem_setup.png" width="350px"/>

## Explore the math

Import the SymForce symbolic API, which contains the augmented SymPy API, as well as geometry and camera types:
```python
import symforce.symbolic as sf
```

Create a symbolic 2D pose and landmark location. Using symbolic variables lets us explore and build up the math in a pure form.
```python
pose = sf.Pose2(
    t=sf.V2.symbolic("t"),
    R=sf.Rot2.symbolic("R")
)
landmark = sf.V2.symbolic("L")
```

Let's transform the landmark into the local frame of the robot.  We choose to represent poses as
`world_T_body`, meaning that to take a landmark in the world frame and get its position in the body
frame, we do:
```python
landmark_body = pose.inverse() * landmark
```
$$
\begin{bmatrix}
    R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0 \\
    -R_{im} L_0 + R_{re} L_1 + R_{im} t_0 + R_{re} t_1
\end{bmatrix}
$$

You can see that `sf.Rot2` is represented internally by a complex number (𝑅𝑟𝑒, 𝑅𝑖𝑚) and we can study how it rotates the landmark 𝐿.

For exploration purposes, let's take the jacobian of the body-frame landmark with respect to the tangent space of the `Pose2`, parameterized as (𝜃, 𝑥, 𝑦):

```python
landmark_body.jacobian(pose)
```

$$
\begin{bmatrix}
    -L_0 R_{im} + L_1 R_{re} + t_0 R_{im} - t_1 R_{re}, & -R_{re}, & -R_{im} \\
    -L_0 R_{re} - L_1 R_{im} + t_0 R_{re} + t_1 R_{im}, &  R_{im}, & -R_{re}
\end{bmatrix}
$$

Note that even though the orientation is stored as a complex number, the tangent space is a scalar angle and SymForce understands that.

Now compute the relative bearing angle:

```python
sf.atan2(landmark_body[1], landmark_body[0])
```

$$
atan_2(-R_{im} L_0 + R_{re} L_1 + R_{im} t_0 + R_{re} t_1, R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0)
$$

One important note is that `atan2` is singular at (0, 0). In SymForce we handle this by placing a symbol ϵ (epsilon) that preserves the value of an expression in the limit of ϵ → 0, but allows evaluating at runtime with a very small nonzero value. Functions with singularities accept an `epsilon` argument:

```python
sf.V3.symbolic("x").norm(epsilon=sf.epsilon())
```
$$
\sqrt{x_0^2 + x_1^2 + x_2^2 + \epsilon}
$$

See the [Epsilon Tutorial](https://symforce.org/tutorials/epsilon_tutorial.html) in the SymForce Docs for more information.

## Build an optimization problem

We will model this problem as a factor graph and solve it with nonlinear least-squares.

First, we need to tell SymForce to use a nonzero epsilon to prevent singularities.  This isn't necessary when playing around with symbolic expressions like we were above, but it's important now that we want to numerically evaluate some results.  For more information, check out the [Epsilon Tutorial](https://symforce.org/tutorials/epsilon_tutorial.html) - for now, all you need to do is this:

```python
import symforce
symforce.set_epsilon_to_symbol()
```

This needs to be done before other parts of symforce are imported - if you're following along in a
notebook you should add this at the top and restart the kernel.

Now that epsilon is set up, we will instantiate numerical [`Values`](https://symforce.org/api/symforce.values.values.html?highlight=values#module-symforce.values.values) for the problem, including an initial guess for our unknown poses (just set them to identity).

```python
import numpy as np
from symforce.values import Values

num_poses = 3
num_landmarks = 3

initial_values = Values(
    poses=[sf.Pose2.identity()] * num_poses,
    landmarks=[sf.V2(-2, 2), sf.V2(1, -3), sf.V2(5, 2)],
    distances=[1.7, 1.4],
    angles=np.deg2rad([[145, 335, 55], [185, 310, 70], [215, 310, 70]]).tolist(),
    epsilon=sf.numeric_epsilon,
)
```

Next, we can set up the factors connecting our variables.  The residual function comprises of two terms - one for the bearing measurements and one for the odometry measurements. Let's formalize the math we just defined for the bearing measurements into a symbolic residual function:

```python
def bearing_residual(
    pose: sf.Pose2, landmark: sf.V2, angle: sf.Scalar, epsilon: sf.Scalar
) -> sf.V1:
    t_body = pose.inverse() * landmark
    predicted_angle = sf.atan2(t_body[1], t_body[0], epsilon=epsilon)
    return sf.V1(sf.wrap_angle(predicted_angle - angle))
```

This function takes in a pose and landmark variable and returns the error between the predicted bearing angle and a measured value. Note that we call `sf.wrap_angle` on the angle difference to prevent wraparound effects.

The residual for distance traveled is even simpler:

```python
def odometry_residual(
    pose_a: sf.Pose2, pose_b: sf.Pose2, dist: sf.Scalar, epsilon: sf.Scalar
) -> sf.V1:
    return sf.V1((pose_b.t - pose_a.t).norm(epsilon=epsilon) - dist)
```

Now we can create [`Factor`](https://symforce.org/api/symforce.opt.factor.html?highlight=factor#module-symforce.opt.factor) objects from the residual functions and a set of keys. The keys are named strings for the function arguments, which will be accessed by name from a [`Values`](https://symforce.org/api/symforce.values.values.html) class we later instantiate with numerical quantities.

```python
from symforce.opt.factor import Factor

factors = []

# Bearing factors
for i in range(num_poses):
    for j in range(num_landmarks):
        factors.append(Factor(
            residual=bearing_residual,
            keys=[f"poses[{i}]", f"landmarks[{j}]", f"angles[{i}][{j}]", "epsilon"],
        ))

# Odometry factors
for i in range(num_poses - 1):
    factors.append(Factor(
        residual=odometry_residual,
        keys=[f"poses[{i}]", f"poses[{i + 1}]", f"distances[{i}]", "epsilon"],
    ))
```

Here is a visualization of the structure of this factor graph:

<img alt="Robot 2D Localization Factor Graph" src="https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/robot_2d_localization/factor_graph.png" width="600px"/>

## Solve the problem

Our goal is to find poses of the robot that minimize the residual of this factor graph, assuming the
landmark positions in the world are known. We create an
[`Optimizer`](https://symforce.org/api/symforce.opt.optimizer.html?highlight=optimizer#module-symforce.opt.optimizer)
with these factors and tell it to only optimize the pose keys (the rest are held constant):
```python
from symforce.opt.optimizer import Optimizer

optimizer = Optimizer(
    factors=factors,
    optimized_keys=[f"poses[{i}]" for i in range(num_poses)],
    # So that we save more information about each iteration, to visualize later:
    debug_stats=True,
)
```

Now run the optimization! This returns an [`Optimizer.Result`](https://symforce.org/api/symforce.opt.optimizer.html?highlight=optimizer#symforce.opt.optimizer.Optimizer.Result) object that contains the optimized values, error statistics, and per-iteration debug stats (if enabled).
```python
result = optimizer.optimize(initial_values)
```

We can check that the optimization succeeded, and look at the final error:
```python
assert result.status == Optimizer.Status.SUCCESS
print(result.error())
```

Let's visualize what the optimizer did. The orange circles represent the fixed landmarks, the blue
circles represent the robot, and the dotted lines represent the bearing measurements.

```python
from symforce.examples.robot_2d_localization.plotting import plot_solution
plot_solution(optimizer, result)
```
<img alt="Robot 2D Localization Solution" src="https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/robot_2d_localization/iterations.gif" width="600px"/>

All of the code for this example can also be found in `symforce/examples/robot_2d_localization`.

## Symbolic vs Numerical Types

SymForce provides `sym` packages with runtime code for geometry and camera types that are generated from its symbolic `geo` and `cam` packages. As such, there are multiple versions of a class like `Pose3` and it can be a common source of confusion.

The canonical symbolic class [`sf.Pose3`](https://symforce.org/api/symforce.symbolic.html#symforce.symbolic.Pose3) lives in the `symforce` package:
```python
sf.Pose3.identity()
```

The autogenerated Python runtime class [`sym.Pose3`](https://symforce.org/api-gen-py/sym.pose3.html?highlight=pose3#module-sym.pose3) lives in the `sym` package:
```python
import sym
sym.Pose3.identity()
```

The autogenerated C++ runtime class [`sym::Pose3`](https://symforce.org/api-gen-cpp/class/classsym_1_1Pose3.html) lives in the `sym::` namespace:
```C++
sym::Pose3<double>::Identity()
```

The matrix type for symbolic code is [`sf.Matrix`](https://symforce.org/api/symforce.symbolic.html#symforce.symbolic.Matrix), for generated Python is [`numpy.ndarray`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html), and for C++ is [`Eigen::Matrix`](https://eigen.tuxfamily.org/dox/group__TutorialMatrixClass.html).

The symbolic classes can also handle numerical values, but will be dramatically slower than the generated classes. The symbolic classes must be used when defining functions for codegen and optimization. Generated functions always accept the runtime types.

The `Codegen` or `Factor` objects require symbolic types and functions to do math and generate code. However, once code is generated, numerical types should be used when invoking generated functions and in the initial values when calling the `Optimizer`.

As a convenience, the Python `Optimizer` class can accept symbolic types in its `Values` and convert to numerical types before invoking generated functions. This is done in the tutorial example for simplicity.

## Generate runtime C++ code

Let's look under the hood to understand how that optimization worked. For each factor, SymForce introspects the form of the symbolic function, passes through symbolic inputs to build an output expression, automatically computes tangent-space jacobians of those output expressions w.r.t. the optimized variables, and generates fast runtime code for them.

The [`Codegen`](https://symforce.org/api/symforce.codegen.codegen.html?highlight=codegen#module-symforce.codegen.codegen) class is the central tool for generating runtime code from symbolic expressions. In this case, we pass it the bearing residual function and configure it to generate C++ code:
```python
from symforce.codegen import Codegen, CppConfig

codegen = Codegen.function(bearing_residual, config=CppConfig())
```

We can then create another `Codegen` object that computes a Gauss-Newton linearization from this Codegen object. It does this by introspecting and symbolically differentiating the given arguments:
```python
codegen_linearization = codegen.with_linearization(
    which_args=["pose"]
)
```

Generate a C++ function that computes the linearization wrt the pose argument:
```python
metadata = codegen_linearization.generate_function()
print(open(metadata.generated_files[0]).read())
```

This C++ code depends only on Eigen and computes the results in a single flat function that shares all common sub-expressions:

```c++
#pragma once

#include <Eigen/Dense>

#include <sym/pose2.h>

namespace sym {

/**
 * This function was autogenerated from a symbolic function. Do not modify by hand.
 *
 * Symbolic function: bearing_residual
 *
 * Args:
 *     pose: Pose2
 *     landmark: Matrix21
 *     angle: Scalar
 *     epsilon: Scalar
 *
 * Outputs:
 *     res: Matrix11
 *     jacobian: (1x3) jacobian of res wrt arg pose (3)
 *     hessian: (3x3) Gauss-Newton hessian for arg pose (3)
 *     rhs: (3x1) Gauss-Newton rhs for arg pose (3)
 */
template <typename Scalar>
void BearingFactor(const sym::Pose2<Scalar>& pose, const Eigen::Matrix<Scalar, 2, 1>& landmark,
                   const Scalar angle, const Scalar epsilon,
                   Eigen::Matrix<Scalar, 1, 1>* const res = nullptr,
                   Eigen::Matrix<Scalar, 1, 3>* const jacobian = nullptr,
                   Eigen::Matrix<Scalar, 3, 3>* const hessian = nullptr,
                   Eigen::Matrix<Scalar, 3, 1>* const rhs = nullptr) {
  // Total ops: 66

  // Input arrays
  const Eigen::Matrix<Scalar, 4, 1>& _pose = pose.Data();

  // Intermediate terms (24)
  const Scalar _tmp0 = _pose[1] * _pose[2];
  const Scalar _tmp1 = _pose[0] * _pose[3];
  const Scalar _tmp2 = _pose[0] * landmark(1, 0) - _pose[1] * landmark(0, 0);
  const Scalar _tmp3 = _tmp0 - _tmp1 + _tmp2;
  const Scalar _tmp4 = _pose[0] * _pose[2] + _pose[1] * _pose[3];
  const Scalar _tmp5 = _pose[1] * landmark(1, 0);
  const Scalar _tmp6 = _pose[0] * landmark(0, 0);
  const Scalar _tmp7 = -_tmp4 + _tmp5 + _tmp6;
  const Scalar _tmp8 = _tmp7 + epsilon * ((((_tmp7) > 0) - ((_tmp7) < 0)) + Scalar(0.5));
  const Scalar _tmp9 = -angle + std::atan2(_tmp3, _tmp8);
  const Scalar _tmp10 =
      _tmp9 - 2 * Scalar(M_PI) *
                  std::floor((Scalar(1) / Scalar(2)) * (_tmp9 + Scalar(M_PI)) / Scalar(M_PI));
  const Scalar _tmp11 = Scalar(1.0) / (_tmp8);
  const Scalar _tmp12 = std::pow(_tmp8, Scalar(2));
  const Scalar _tmp13 = _tmp3 / _tmp12;
  const Scalar _tmp14 = _tmp11 * (_tmp4 - _tmp5 - _tmp6) - _tmp13 * (_tmp0 - _tmp1 + _tmp2);
  const Scalar _tmp15 = _tmp12 + std::pow(_tmp3, Scalar(2));
  const Scalar _tmp16 = _tmp12 / _tmp15;
  const Scalar _tmp17 = _tmp14 * _tmp16;
  const Scalar _tmp18 = _pose[0] * _tmp13 + _pose[1] * _tmp11;
  const Scalar _tmp19 = _tmp16 * _tmp18;
  const Scalar _tmp20 = -_pose[0] * _tmp11 + _pose[1] * _tmp13;
  const Scalar _tmp21 = _tmp16 * _tmp20;
  const Scalar _tmp22 = std::pow(_tmp8, Scalar(4)) / std::pow(_tmp15, Scalar(2));
  const Scalar _tmp23 = _tmp18 * _tmp22;

  // Output terms (4)
  if (res != nullptr) {
    Eigen::Matrix<Scalar, 1, 1>& _res = (*res);

    _res(0, 0) = _tmp10;
  }

  if (jacobian != nullptr) {
    Eigen::Matrix<Scalar, 1, 3>& _jacobian = (*jacobian);

    _jacobian(0, 0) = _tmp17;
    _jacobian(0, 1) = _tmp19;
    _jacobian(0, 2) = _tmp21;
  }

  if (hessian != nullptr) {
    Eigen::Matrix<Scalar, 3, 3>& _hessian = (*hessian);

    _hessian(0, 0) = std::pow(_tmp14, Scalar(2)) * _tmp22;
    _hessian(0, 1) = 0;
    _hessian(0, 2) = 0;
    _hessian(1, 0) = _tmp14 * _tmp23;
    _hessian(1, 1) = std::pow(_tmp18, Scalar(2)) * _tmp22;
    _hessian(1, 2) = 0;
    _hessian(2, 0) = _tmp14 * _tmp20 * _tmp22;
    _hessian(2, 1) = _tmp20 * _tmp23;
    _hessian(2, 2) = std::pow(_tmp20, Scalar(2)) * _tmp22;
  }

  if (rhs != nullptr) {
    Eigen::Matrix<Scalar, 3, 1>& _rhs = (*rhs);

    _rhs(0, 0) = _tmp10 * _tmp17;
    _rhs(1, 0) = _tmp10 * _tmp19;
    _rhs(2, 0) = _tmp10 * _tmp21;
  }
}

}  // namespace sym
```

SymForce can also generate runtime Python code that depends only on `numpy`.

The code generation system is written with pluggable [jinja](https://palletsprojects.com/p/jinja/) templates to minimize the work to add new backend languages. Some of our top candidates to add are TypeScript, CUDA, and PyTorch.

## Optimize from C++

Now that we can generate C++ functions for each residual function, we can also run the optimization purely from C++ to get Python entirely out of the loop for production use.

```c++
const int num_poses = 3;
const int num_landmarks = 3;

std::vector<sym::Factor<double>> factors;

// Bearing factors
for (int i = 0; i < num_poses; ++i) {
    for (int j = 0; j < num_landmarks; ++j) {
        factors.push_back(sym::Factor<double>::Hessian(
            sym::BearingFactor<double>,
            {{'P', i}, {'L', j}, {'a', i, j}, {'e'}},  // keys
            {{'P', i}}  // keys to optimize
        ));
    }
}

// Odometry factors
for (int i = 0; i < num_poses - 1; ++i) {
    factors.push_back(sym::Factor<double>::Hessian(
        sym::OdometryFactor<double>,
        {{'P', i}, {'P', i + 1}, {'d', i}, {'e'}},  // keys
        {{'P', i}, {'P', i + 1}}  // keys to optimize
    ));
}

const auto params = sym::DefaultOptimizerParams();
sym::Optimizer<double> optimizer(
    params,
    factors,
    sym::kDefaultEpsilon<double>
);

sym::Values<double> values;
for (int i = 0; i < num_poses; ++i) {
    values.Set({'P', i}, sym::Pose2d::Identity());
}

// Set additional values
values.Set({'L', 0}, Eigen::Vector2d(-2, 2));
values.Set({'L', 1}, Eigen::Vector2d(1, -3));
values.Set({'L', 2}, Eigen::Vector2d(5, 2));
values.Set({'d', 0}, 1.7);
values.Set({'d', 1}, 1.4);
const std::array<std::array<double, 3>, 3> angles = {
    {{55, 245, -35}, {95, 220, -20}, {125, 220, -20}}
};
for (int i = 0; i < angles.size(); ++i) {
    for (int j = 0; j < angles[0].size(); ++j) {
        values.Set({'a', i, j}, angles[i][j] * M_PI / 180);
    }
}
values.Set('e', sym::kDefaultEpsilond);

// Optimize!
const auto stats = optimizer.Optimize(values);

std::cout << "Exit status: " << stats.status << std::endl;
std::cout << "Optimized values:" << values << std::endl;
```

This tutorial shows the central workflow in SymForce for creating symbolic expressions, generating code, and optimizing. This approach works well for a wide range of complex problems in robotics, computer vision, and applied science.

However, each piece may also be used independently. The optimization machinery can work with handwritten functions, and the symbolic math and code generation is useful outside of any optimization context.


<!-- $
<span style="color:blue">TODO: I wanted to show `sf.V1(sm.atan2(landmark_body[1], landmark_body[0])).jacobian(pose.R)`, but you have to call `sm.simplify` to get the expression to -1, otherwise it's more complicated. All this is also showing up extraneously in the generated code. Discuss what to show.</span>

\frac{
    (-\frac{
        (-R_{im} L_0 + R_{re} L_1 + R_{im} t_0 + R_{re} t_1)^2
    }{
        (R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0)^2
    } + \frac{

        }{

        })(R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0)^2
    }{
        (-R_{im} L_0 + R_{re} L_1 + R_{im} t_0 + R_{re} t_1)^2 +
        (R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0)^2
    }
$ -->

To learn more, visit the SymForce tutorials [here](https://symforce.org/#guides).

# Build from Source

For best results, you should build from the [latest tagged release](https://github.com/symforce-org/symforce/releases/latest).  You can also build from `main`, or from another branch, but everything is less guaranteed to work.

SymForce requires Python 3.8 or later. The build is currently tested on Linux and macOS, SymForce on Windows is untested (see [#145](https://github.com/symforce-org/symforce/issues/145)).  We strongly suggest creating a virtual python environment.

Install the `gmp` package with one of:
```bash
apt install libgmp-dev            # Ubuntu
brew install gmp                  # Mac
conda install -c conda-forge gmp  # Conda
```

SymForce contains both C++ and Python code. The C++ code is built using CMake.  You can build the package either by calling pip, or by calling CMake directly.  If building with `pip`, this will call CMake under the hood, and run the same CMake build for the C++ components.

If you encounter build issues, please file an [issue](https://github.com/symforce-org/symforce/issues).

## Build with pip

If you just want to build and install SymForce without repeatedly modifying the source, the recommended way to do this is with pip.  From the symforce directory:
```bash
pip install .
```

If you're modifying the SymForce Python sources, you can do an [editable install](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs) instead.  This will let you modify the Python components of SymForce without reinstalling.  If you're going to repeatedly modify the C++ sources, you should instead build with CMake directly as described <a href="#build-with-cmake">below</a>.  From the symforce directory:
```bash
pip install -e .
```

You should then [verify your installation](#verify-your-installation).

___Note:___ `pip install .` will not install pinned versions of SymForce's dependencies, it'll install any compatible versions.  It also won't install all packages required to run all of the SymForce tests and build all of the targets (e.g. building the docs or running the linters).  If you want all packages required for that, you should `pip install .[dev]` instead (or one of the other groups of extra requirements in our `setup.py`).  If you additionally want pinned versions of our dependencies, which are the exact versions guaranteed by CI to pass all of our tests, you can install them from `pip install -r dev_requirements.txt`.

_Note: Editable installs as root with the system python on Ubuntu (and other Debian derivatives) are broken on `setuptools<64.0.0`.  This is a [bug in Debian](https://ffy00.github.io/blog/02-python-debian-and-the-install-locations/), not something in SymForce that we can fix.  If this is your situation, either use a virtual environment, upgrade setuptools to a version `>=64.0.0`, or use a different installation method._

## Build with CMake

If you'll be modifying the C++ parts of SymForce, you should build with CMake directly instead - this method will not install
SymForce into your Python environment, so you'll need to add it to your PYTHONPATH separately.

Install python requirements:
```bash
pip install -r dev_requirements.txt
```

Build SymForce (requires C++14 or later):
```bash
mkdir build
cd build
cmake ..
make -j $(nproc)
```

You'll then need to add SymForce (along with `gen/python` and `third_party/skymarshal` within symforce and `lcmtypes/python2.7` within the build directory) to your PYTHONPATH in order to use them, for example:

```bash
export PYTHONPATH="$PYTHONPATH:/path/to/symforce:/path/to/symforce/build/lcmtypes/python2.7"
```

If you want to install SymForce to use its C++ libraries in another CMake project, you can do that with:
```bash
make install
```

SymForce does not currently integrate with CMake's `find_package` (see [#209](https://github.com/symforce-org/symforce/issues/209)), so if you do this you currently need to add its libraries as link dependencies in your CMake project manually.

## Verify your installation
```python
>>> import symforce
>>> symforce.get_symbolic_api()
'symengine'
>>> from symforce import cc_sym
```

If you see `'sympy'` here instead of `'symengine'`, or can't import `cc_sym`, your installation is probably broken and you should submit an [issue](https://github.com/symforce-org/symforce/issues).

# License

SymForce is released under the [Apache 2.0](https://spdx.org/licenses/Apache-2.0.html) license.

See the [LICENSE](https://github.com/symforce-org/symforce/blob/main/LICENSE) file for more information.

# Sponsors

SymForce is developed and maintained by [Skydio](https://skydio.com/). It is released as a free and open-source library for the robotics community.

<a href="http://skydio.com#gh-light-mode-only">
    <img alt="Skydio Logo" src="https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/skydio-logo-2.png" width="300px" />
</a>


# Contributing

While SymForce already powers tens of thousands of robots at Skydio, the public library is new and we are releasing it in beta stage. This is just the beginning, and we are excited for engagement from the community. Thank you for helping us develop SymForce! The best way to get started is to file [issues](https://github.com/symforce-org/symforce/issues) for bugs or desired features.

There are many features we're excited to add to SymForce and would love to see contributed by the community. Most are outlined in the issues, but some major desired contributions are:

- Add more backend languages, such as TypeScript and GLSL/HLSL, and improvements to the experimental CUDA and PyTorch backends
- Support for WebAssembly compilation
- More Lie group types, in particular Sim(3)
- Support for constraints in our optimizer
- Integration with [ISPC](https://ispc.github.io/)
- Windows and conda packages

            

Raw data

            {
    "_id": null,
    "home_page": "https://symforce.org",
    "name": "symforce",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "python,computer-vision,cpp,robotics,optimization,structure-from-motion,motion-planning,code-generation,slam,autonomous-vehicles,symbolic-computation",
    "author": "",
    "author_email": "\"Skydio, Inc.\" <hayk@skydio.com>",
    "download_url": "",
    "platform": null,
    "description": "<!-- There's no good way to make an image that's different on light/dark mode that also links\n    somewhere.  The closest thing is to add a #gh-light-mode-only to the target, which does this,\n    but is kinda confusing -->\n![SymForce](https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/symforce_banner.png#gh-light-mode-only)\n\n\n| \ud83d\udce3 We are open for internships!  If you are interested in contributing to SymForce, visit the program page [here](https://github.com/symforce-org/symforce/discussions/242) to learn more.  |\n|-----------------------------------------|\n\n<p align=\"center\">\n<a href=\"https://github.com/symforce-org/symforce/actions/workflows/ci.yml?query=branch%3Amain\"><img alt=\"CI status\" src=\"https://github.com/symforce-org/symforce/actions/workflows/ci.yml/badge.svg\" /></a>\n<a href=\"https://symforce.org\"><img alt=\"Documentation\" src=\"https://img.shields.io/badge/api-docs-blue\" /></a>\n<a href=\"https://github.com/symforce-org/symforce\"><img alt=\"Source Code\" src=\"https://img.shields.io/badge/source-code-blue\" /></a>\n<a href=\"https://github.com/symforce-org/symforce/issues\"><img alt=\"Issues\" src=\"https://img.shields.io/badge/issue-tracker-blue\" /></a>\n<img alt=\"Python 3.8 | 3.9 | 3.10\" src=\"https://img.shields.io/pypi/pyversions/symforce\" />\n<img alt=\"C++14\" src=\"https://img.shields.io/badge/c++-14-blue\" />\n<a href=\"https://pypi.org/project/symforce/\"><img alt=\"PyPI\" src=\"https://img.shields.io/pypi/v/symforce\" /></a>\n<a href=\"https://github.com/symforce-org/symforce/tree/main/LICENSE\"><img alt=\"Apache License\" src=\"https://img.shields.io/pypi/l/symforce\" /></a>\n</p>\n\n---\n\nSymForce is a fast symbolic computation and code generation library for robotics applications like computer vision, state estimation, motion planning, and controls. It combines the development speed and flexibility of symbolic mathematics with the performance of autogenerated, highly optimized code in C++ or any target runtime language. SymForce contains three independently useful systems:\n\n+ **Symbolic Toolkit** - builds on the SymPy API to provide rigorous geometric and camera types, lie group calculus, singularity handling, and tools to model complex problems\n\n+ **Code Generator** - transforms symbolic expressions into blazing-fast, branchless code with clean APIs and minimal dependencies, with a template system to target any language\n\n+ **Optimization Library** - a fast tangent-space optimization library based on factor graphs, with a highly optimized implementation for real-time robotics applications\n\nSymForce automatically computes tangent space Jacobians, eliminating the need for any bug-prone handwritten derivatives. Generated functions can be directly used as factors in our nonlinear optimizer. This workflow enables faster runtime functions, faster development time, and fewer lines of handwritten code versus alternative methods.\n\nSymForce is developed and maintained by [Skydio](https://skydio.com/). It is used in production to accelerate tasks like SLAM, bundle adjustment, calibration, and sparse nonlinear MPC for autonomous robots at scale.\n\n<br/>\n\n<img alt=\"SymForce\" src=\"https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/symforce_diagram.png\" width=\"700px\"/>\n\n<br/>\n\n#### Features\n\n + Symbolic implementations of geometry and camera types with Lie group operations\n + Code generation of fast native runtime code from symbolic expressions, reducing duplication and minimizing bugs\n + Novel tools to compute fast and correct tangent-space jacobians for any expression, avoiding all handwritten derivatives\n + Strategies for flattening computation and leveraging sparsity that can yield 10x speedups over standard autodiff\n + A fast tangent-space optimization library in C++ and Python based on factor graphs\n + Rapid prototyping and analysis of complex problems with symbolic math, with a seamless workflow into production use\n + Embedded-friendly C++ generation of templated Eigen code with zero dynamic memory allocation\n + Highly performant, modular, tested, and extensible code\n\n### Read the paper: <a href=\"https://arxiv.org/abs/2204.07889\">https://arxiv.org/abs/2204.07889</a>\n\n### And watch the video: <a href=\"https://youtu.be/QO_ltJRNj0o\">https://youtu.be/QO_ltJRNj0o</a>\n\nSymForce was published to [RSS 2022](https://roboticsconference.org/). Please cite it as follows:\n\n```\n@inproceedings{Martiros-RSS-22,\n    author    = {Hayk Martiros AND Aaron Miller AND Nathan Bucki AND Bradley Solliday AND Ryan Kennedy AND Jack Zhu AND Tung Dang AND Dominic Pattison AND Harrison Zheng AND Teo Tomic AND Peter Henry AND Gareth Cross AND Josiah VanderMey AND Alvin Sun AND Samuel Wang AND Kristen Holtz},\n    title     = {{SymForce: Symbolic Computation and Code Generation for Robotics}},\n    booktitle = {Proceedings of Robotics: Science and Systems},\n    year      = {2022},\n    doi       = {10.15607/RSS.2022.XVIII.041}\n}\n```\n\n# Install\n\nInstall with pip:\n\n```bash\npip install symforce\n```\n\nVerify the installation in Python:\n```python\n>>> import symforce.symbolic as sf\n>>> sf.Rot3()\n```\n\nThis installs pre-compiled C++ components of SymForce on Linux and Mac using pip wheels, but does not include C++ headers. If you want to compile against C++ SymForce types (like `sym::Optimizer`), you currently need to <a href=\"#build-from-source\">build from source</a>.\n\n# Tutorial\n\nLet's walk through a simple example of modeling and solving an optimization problem with SymForce. In this example a robot moves through a 2D plane and the goal is to estimate its pose at multiple time steps given noisy measurements.\n\nThe robot measures:\n\n * the distance it traveled from an odometry sensor\n * relative bearing angles to known landmarks in the scene\n\nThe robot's heading angle is defined counter-clockwise from the x-axis, and its relative bearing measurements are defined from the robot's forward direction:\n\n<img alt=\"Robot 2D Localization Figure\" src=\"https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/robot_2d_localization/problem_setup.png\" width=\"350px\"/>\n\n## Explore the math\n\nImport the SymForce symbolic API, which contains the augmented SymPy API, as well as geometry and camera types:\n```python\nimport symforce.symbolic as sf\n```\n\nCreate a symbolic 2D pose and landmark location. Using symbolic variables lets us explore and build up the math in a pure form.\n```python\npose = sf.Pose2(\n    t=sf.V2.symbolic(\"t\"),\n    R=sf.Rot2.symbolic(\"R\")\n)\nlandmark = sf.V2.symbolic(\"L\")\n```\n\nLet's transform the landmark into the local frame of the robot.  We choose to represent poses as\n`world_T_body`, meaning that to take a landmark in the world frame and get its position in the body\nframe, we do:\n```python\nlandmark_body = pose.inverse() * landmark\n```\n$$\n\\begin{bmatrix}\n    R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0 \\\\\n    -R_{im} L_0 + R_{re} L_1 + R_{im} t_0 + R_{re} t_1\n\\end{bmatrix}\n$$\n\nYou can see that `sf.Rot2` is represented internally by a complex number (\ud835\udc45\ud835\udc5f\ud835\udc52, \ud835\udc45\ud835\udc56\ud835\udc5a) and we can study how it rotates the landmark \ud835\udc3f.\n\nFor exploration purposes, let's take the jacobian of the body-frame landmark with respect to the tangent space of the `Pose2`, parameterized as (\ud835\udf03, \ud835\udc65, \ud835\udc66):\n\n```python\nlandmark_body.jacobian(pose)\n```\n\n$$\n\\begin{bmatrix}\n    -L_0 R_{im} + L_1 R_{re} + t_0 R_{im} - t_1 R_{re}, & -R_{re}, & -R_{im} \\\\\n    -L_0 R_{re} - L_1 R_{im} + t_0 R_{re} + t_1 R_{im}, &  R_{im}, & -R_{re}\n\\end{bmatrix}\n$$\n\nNote that even though the orientation is stored as a complex number, the tangent space is a scalar angle and SymForce understands that.\n\nNow compute the relative bearing angle:\n\n```python\nsf.atan2(landmark_body[1], landmark_body[0])\n```\n\n$$\natan_2(-R_{im} L_0 + R_{re} L_1 + R_{im} t_0 + R_{re} t_1, R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0)\n$$\n\nOne important note is that `atan2` is singular at (0, 0). In SymForce we handle this by placing a symbol \u03f5 (epsilon) that preserves the value of an expression in the limit of \u03f5 \u2192 0, but allows evaluating at runtime with a very small nonzero value. Functions with singularities accept an `epsilon` argument:\n\n```python\nsf.V3.symbolic(\"x\").norm(epsilon=sf.epsilon())\n```\n$$\n\\sqrt{x_0^2 + x_1^2 + x_2^2 + \\epsilon}\n$$\n\nSee the [Epsilon Tutorial](https://symforce.org/tutorials/epsilon_tutorial.html) in the SymForce Docs for more information.\n\n## Build an optimization problem\n\nWe will model this problem as a factor graph and solve it with nonlinear least-squares.\n\nFirst, we need to tell SymForce to use a nonzero epsilon to prevent singularities.  This isn't necessary when playing around with symbolic expressions like we were above, but it's important now that we want to numerically evaluate some results.  For more information, check out the [Epsilon Tutorial](https://symforce.org/tutorials/epsilon_tutorial.html) - for now, all you need to do is this:\n\n```python\nimport symforce\nsymforce.set_epsilon_to_symbol()\n```\n\nThis needs to be done before other parts of symforce are imported - if you're following along in a\nnotebook you should add this at the top and restart the kernel.\n\nNow that epsilon is set up, we will instantiate numerical [`Values`](https://symforce.org/api/symforce.values.values.html?highlight=values#module-symforce.values.values) for the problem, including an initial guess for our unknown poses (just set them to identity).\n\n```python\nimport numpy as np\nfrom symforce.values import Values\n\nnum_poses = 3\nnum_landmarks = 3\n\ninitial_values = Values(\n    poses=[sf.Pose2.identity()] * num_poses,\n    landmarks=[sf.V2(-2, 2), sf.V2(1, -3), sf.V2(5, 2)],\n    distances=[1.7, 1.4],\n    angles=np.deg2rad([[145, 335, 55], [185, 310, 70], [215, 310, 70]]).tolist(),\n    epsilon=sf.numeric_epsilon,\n)\n```\n\nNext, we can set up the factors connecting our variables.  The residual function comprises of two terms - one for the bearing measurements and one for the odometry measurements. Let's formalize the math we just defined for the bearing measurements into a symbolic residual function:\n\n```python\ndef bearing_residual(\n    pose: sf.Pose2, landmark: sf.V2, angle: sf.Scalar, epsilon: sf.Scalar\n) -> sf.V1:\n    t_body = pose.inverse() * landmark\n    predicted_angle = sf.atan2(t_body[1], t_body[0], epsilon=epsilon)\n    return sf.V1(sf.wrap_angle(predicted_angle - angle))\n```\n\nThis function takes in a pose and landmark variable and returns the error between the predicted bearing angle and a measured value. Note that we call `sf.wrap_angle` on the angle difference to prevent wraparound effects.\n\nThe residual for distance traveled is even simpler:\n\n```python\ndef odometry_residual(\n    pose_a: sf.Pose2, pose_b: sf.Pose2, dist: sf.Scalar, epsilon: sf.Scalar\n) -> sf.V1:\n    return sf.V1((pose_b.t - pose_a.t).norm(epsilon=epsilon) - dist)\n```\n\nNow we can create [`Factor`](https://symforce.org/api/symforce.opt.factor.html?highlight=factor#module-symforce.opt.factor) objects from the residual functions and a set of keys. The keys are named strings for the function arguments, which will be accessed by name from a [`Values`](https://symforce.org/api/symforce.values.values.html) class we later instantiate with numerical quantities.\n\n```python\nfrom symforce.opt.factor import Factor\n\nfactors = []\n\n# Bearing factors\nfor i in range(num_poses):\n    for j in range(num_landmarks):\n        factors.append(Factor(\n            residual=bearing_residual,\n            keys=[f\"poses[{i}]\", f\"landmarks[{j}]\", f\"angles[{i}][{j}]\", \"epsilon\"],\n        ))\n\n# Odometry factors\nfor i in range(num_poses - 1):\n    factors.append(Factor(\n        residual=odometry_residual,\n        keys=[f\"poses[{i}]\", f\"poses[{i + 1}]\", f\"distances[{i}]\", \"epsilon\"],\n    ))\n```\n\nHere is a visualization of the structure of this factor graph:\n\n<img alt=\"Robot 2D Localization Factor Graph\" src=\"https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/robot_2d_localization/factor_graph.png\" width=\"600px\"/>\n\n## Solve the problem\n\nOur goal is to find poses of the robot that minimize the residual of this factor graph, assuming the\nlandmark positions in the world are known. We create an\n[`Optimizer`](https://symforce.org/api/symforce.opt.optimizer.html?highlight=optimizer#module-symforce.opt.optimizer)\nwith these factors and tell it to only optimize the pose keys (the rest are held constant):\n```python\nfrom symforce.opt.optimizer import Optimizer\n\noptimizer = Optimizer(\n    factors=factors,\n    optimized_keys=[f\"poses[{i}]\" for i in range(num_poses)],\n    # So that we save more information about each iteration, to visualize later:\n    debug_stats=True,\n)\n```\n\nNow run the optimization! This returns an [`Optimizer.Result`](https://symforce.org/api/symforce.opt.optimizer.html?highlight=optimizer#symforce.opt.optimizer.Optimizer.Result) object that contains the optimized values, error statistics, and per-iteration debug stats (if enabled).\n```python\nresult = optimizer.optimize(initial_values)\n```\n\nWe can check that the optimization succeeded, and look at the final error:\n```python\nassert result.status == Optimizer.Status.SUCCESS\nprint(result.error())\n```\n\nLet's visualize what the optimizer did. The orange circles represent the fixed landmarks, the blue\ncircles represent the robot, and the dotted lines represent the bearing measurements.\n\n```python\nfrom symforce.examples.robot_2d_localization.plotting import plot_solution\nplot_solution(optimizer, result)\n```\n<img alt=\"Robot 2D Localization Solution\" src=\"https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/robot_2d_localization/iterations.gif\" width=\"600px\"/>\n\nAll of the code for this example can also be found in `symforce/examples/robot_2d_localization`.\n\n## Symbolic vs Numerical Types\n\nSymForce provides `sym` packages with runtime code for geometry and camera types that are generated from its symbolic `geo` and `cam` packages. As such, there are multiple versions of a class like `Pose3` and it can be a common source of confusion.\n\nThe canonical symbolic class [`sf.Pose3`](https://symforce.org/api/symforce.symbolic.html#symforce.symbolic.Pose3) lives in the `symforce` package:\n```python\nsf.Pose3.identity()\n```\n\nThe autogenerated Python runtime class [`sym.Pose3`](https://symforce.org/api-gen-py/sym.pose3.html?highlight=pose3#module-sym.pose3) lives in the `sym` package:\n```python\nimport sym\nsym.Pose3.identity()\n```\n\nThe autogenerated C++ runtime class [`sym::Pose3`](https://symforce.org/api-gen-cpp/class/classsym_1_1Pose3.html) lives in the `sym::` namespace:\n```C++\nsym::Pose3<double>::Identity()\n```\n\nThe matrix type for symbolic code is [`sf.Matrix`](https://symforce.org/api/symforce.symbolic.html#symforce.symbolic.Matrix), for generated Python is [`numpy.ndarray`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html), and for C++ is [`Eigen::Matrix`](https://eigen.tuxfamily.org/dox/group__TutorialMatrixClass.html).\n\nThe symbolic classes can also handle numerical values, but will be dramatically slower than the generated classes. The symbolic classes must be used when defining functions for codegen and optimization. Generated functions always accept the runtime types.\n\nThe `Codegen` or `Factor` objects require symbolic types and functions to do math and generate code. However, once code is generated, numerical types should be used when invoking generated functions and in the initial values when calling the `Optimizer`.\n\nAs a convenience, the Python `Optimizer` class can accept symbolic types in its `Values` and convert to numerical types before invoking generated functions. This is done in the tutorial example for simplicity.\n\n## Generate runtime C++ code\n\nLet's look under the hood to understand how that optimization worked. For each factor, SymForce introspects the form of the symbolic function, passes through symbolic inputs to build an output expression, automatically computes tangent-space jacobians of those output expressions w.r.t. the optimized variables, and generates fast runtime code for them.\n\nThe [`Codegen`](https://symforce.org/api/symforce.codegen.codegen.html?highlight=codegen#module-symforce.codegen.codegen) class is the central tool for generating runtime code from symbolic expressions. In this case, we pass it the bearing residual function and configure it to generate C++ code:\n```python\nfrom symforce.codegen import Codegen, CppConfig\n\ncodegen = Codegen.function(bearing_residual, config=CppConfig())\n```\n\nWe can then create another `Codegen` object that computes a Gauss-Newton linearization from this Codegen object. It does this by introspecting and symbolically differentiating the given arguments:\n```python\ncodegen_linearization = codegen.with_linearization(\n    which_args=[\"pose\"]\n)\n```\n\nGenerate a C++ function that computes the linearization wrt the pose argument:\n```python\nmetadata = codegen_linearization.generate_function()\nprint(open(metadata.generated_files[0]).read())\n```\n\nThis C++ code depends only on Eigen and computes the results in a single flat function that shares all common sub-expressions:\n\n```c++\n#pragma once\n\n#include <Eigen/Dense>\n\n#include <sym/pose2.h>\n\nnamespace sym {\n\n/**\n * This function was autogenerated from a symbolic function. Do not modify by hand.\n *\n * Symbolic function: bearing_residual\n *\n * Args:\n *     pose: Pose2\n *     landmark: Matrix21\n *     angle: Scalar\n *     epsilon: Scalar\n *\n * Outputs:\n *     res: Matrix11\n *     jacobian: (1x3) jacobian of res wrt arg pose (3)\n *     hessian: (3x3) Gauss-Newton hessian for arg pose (3)\n *     rhs: (3x1) Gauss-Newton rhs for arg pose (3)\n */\ntemplate <typename Scalar>\nvoid BearingFactor(const sym::Pose2<Scalar>& pose, const Eigen::Matrix<Scalar, 2, 1>& landmark,\n                   const Scalar angle, const Scalar epsilon,\n                   Eigen::Matrix<Scalar, 1, 1>* const res = nullptr,\n                   Eigen::Matrix<Scalar, 1, 3>* const jacobian = nullptr,\n                   Eigen::Matrix<Scalar, 3, 3>* const hessian = nullptr,\n                   Eigen::Matrix<Scalar, 3, 1>* const rhs = nullptr) {\n  // Total ops: 66\n\n  // Input arrays\n  const Eigen::Matrix<Scalar, 4, 1>& _pose = pose.Data();\n\n  // Intermediate terms (24)\n  const Scalar _tmp0 = _pose[1] * _pose[2];\n  const Scalar _tmp1 = _pose[0] * _pose[3];\n  const Scalar _tmp2 = _pose[0] * landmark(1, 0) - _pose[1] * landmark(0, 0);\n  const Scalar _tmp3 = _tmp0 - _tmp1 + _tmp2;\n  const Scalar _tmp4 = _pose[0] * _pose[2] + _pose[1] * _pose[3];\n  const Scalar _tmp5 = _pose[1] * landmark(1, 0);\n  const Scalar _tmp6 = _pose[0] * landmark(0, 0);\n  const Scalar _tmp7 = -_tmp4 + _tmp5 + _tmp6;\n  const Scalar _tmp8 = _tmp7 + epsilon * ((((_tmp7) > 0) - ((_tmp7) < 0)) + Scalar(0.5));\n  const Scalar _tmp9 = -angle + std::atan2(_tmp3, _tmp8);\n  const Scalar _tmp10 =\n      _tmp9 - 2 * Scalar(M_PI) *\n                  std::floor((Scalar(1) / Scalar(2)) * (_tmp9 + Scalar(M_PI)) / Scalar(M_PI));\n  const Scalar _tmp11 = Scalar(1.0) / (_tmp8);\n  const Scalar _tmp12 = std::pow(_tmp8, Scalar(2));\n  const Scalar _tmp13 = _tmp3 / _tmp12;\n  const Scalar _tmp14 = _tmp11 * (_tmp4 - _tmp5 - _tmp6) - _tmp13 * (_tmp0 - _tmp1 + _tmp2);\n  const Scalar _tmp15 = _tmp12 + std::pow(_tmp3, Scalar(2));\n  const Scalar _tmp16 = _tmp12 / _tmp15;\n  const Scalar _tmp17 = _tmp14 * _tmp16;\n  const Scalar _tmp18 = _pose[0] * _tmp13 + _pose[1] * _tmp11;\n  const Scalar _tmp19 = _tmp16 * _tmp18;\n  const Scalar _tmp20 = -_pose[0] * _tmp11 + _pose[1] * _tmp13;\n  const Scalar _tmp21 = _tmp16 * _tmp20;\n  const Scalar _tmp22 = std::pow(_tmp8, Scalar(4)) / std::pow(_tmp15, Scalar(2));\n  const Scalar _tmp23 = _tmp18 * _tmp22;\n\n  // Output terms (4)\n  if (res != nullptr) {\n    Eigen::Matrix<Scalar, 1, 1>& _res = (*res);\n\n    _res(0, 0) = _tmp10;\n  }\n\n  if (jacobian != nullptr) {\n    Eigen::Matrix<Scalar, 1, 3>& _jacobian = (*jacobian);\n\n    _jacobian(0, 0) = _tmp17;\n    _jacobian(0, 1) = _tmp19;\n    _jacobian(0, 2) = _tmp21;\n  }\n\n  if (hessian != nullptr) {\n    Eigen::Matrix<Scalar, 3, 3>& _hessian = (*hessian);\n\n    _hessian(0, 0) = std::pow(_tmp14, Scalar(2)) * _tmp22;\n    _hessian(0, 1) = 0;\n    _hessian(0, 2) = 0;\n    _hessian(1, 0) = _tmp14 * _tmp23;\n    _hessian(1, 1) = std::pow(_tmp18, Scalar(2)) * _tmp22;\n    _hessian(1, 2) = 0;\n    _hessian(2, 0) = _tmp14 * _tmp20 * _tmp22;\n    _hessian(2, 1) = _tmp20 * _tmp23;\n    _hessian(2, 2) = std::pow(_tmp20, Scalar(2)) * _tmp22;\n  }\n\n  if (rhs != nullptr) {\n    Eigen::Matrix<Scalar, 3, 1>& _rhs = (*rhs);\n\n    _rhs(0, 0) = _tmp10 * _tmp17;\n    _rhs(1, 0) = _tmp10 * _tmp19;\n    _rhs(2, 0) = _tmp10 * _tmp21;\n  }\n}\n\n}  // namespace sym\n```\n\nSymForce can also generate runtime Python code that depends only on `numpy`.\n\nThe code generation system is written with pluggable [jinja](https://palletsprojects.com/p/jinja/) templates to minimize the work to add new backend languages. Some of our top candidates to add are TypeScript, CUDA, and PyTorch.\n\n## Optimize from C++\n\nNow that we can generate C++ functions for each residual function, we can also run the optimization purely from C++ to get Python entirely out of the loop for production use.\n\n```c++\nconst int num_poses = 3;\nconst int num_landmarks = 3;\n\nstd::vector<sym::Factor<double>> factors;\n\n// Bearing factors\nfor (int i = 0; i < num_poses; ++i) {\n    for (int j = 0; j < num_landmarks; ++j) {\n        factors.push_back(sym::Factor<double>::Hessian(\n            sym::BearingFactor<double>,\n            {{'P', i}, {'L', j}, {'a', i, j}, {'e'}},  // keys\n            {{'P', i}}  // keys to optimize\n        ));\n    }\n}\n\n// Odometry factors\nfor (int i = 0; i < num_poses - 1; ++i) {\n    factors.push_back(sym::Factor<double>::Hessian(\n        sym::OdometryFactor<double>,\n        {{'P', i}, {'P', i + 1}, {'d', i}, {'e'}},  // keys\n        {{'P', i}, {'P', i + 1}}  // keys to optimize\n    ));\n}\n\nconst auto params = sym::DefaultOptimizerParams();\nsym::Optimizer<double> optimizer(\n    params,\n    factors,\n    sym::kDefaultEpsilon<double>\n);\n\nsym::Values<double> values;\nfor (int i = 0; i < num_poses; ++i) {\n    values.Set({'P', i}, sym::Pose2d::Identity());\n}\n\n// Set additional values\nvalues.Set({'L', 0}, Eigen::Vector2d(-2, 2));\nvalues.Set({'L', 1}, Eigen::Vector2d(1, -3));\nvalues.Set({'L', 2}, Eigen::Vector2d(5, 2));\nvalues.Set({'d', 0}, 1.7);\nvalues.Set({'d', 1}, 1.4);\nconst std::array<std::array<double, 3>, 3> angles = {\n    {{55, 245, -35}, {95, 220, -20}, {125, 220, -20}}\n};\nfor (int i = 0; i < angles.size(); ++i) {\n    for (int j = 0; j < angles[0].size(); ++j) {\n        values.Set({'a', i, j}, angles[i][j] * M_PI / 180);\n    }\n}\nvalues.Set('e', sym::kDefaultEpsilond);\n\n// Optimize!\nconst auto stats = optimizer.Optimize(values);\n\nstd::cout << \"Exit status: \" << stats.status << std::endl;\nstd::cout << \"Optimized values:\" << values << std::endl;\n```\n\nThis tutorial shows the central workflow in SymForce for creating symbolic expressions, generating code, and optimizing. This approach works well for a wide range of complex problems in robotics, computer vision, and applied science.\n\nHowever, each piece may also be used independently. The optimization machinery can work with handwritten functions, and the symbolic math and code generation is useful outside of any optimization context.\n\n\n<!-- $\n<span style=\"color:blue\">TODO: I wanted to show `sf.V1(sm.atan2(landmark_body[1], landmark_body[0])).jacobian(pose.R)`, but you have to call `sm.simplify` to get the expression to -1, otherwise it's more complicated. All this is also showing up extraneously in the generated code. Discuss what to show.</span>\n\n\\frac{\n    (-\\frac{\n        (-R_{im} L_0 + R_{re} L_1 + R_{im} t_0 + R_{re} t_1)^2\n    }{\n        (R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0)^2\n    } + \\frac{\n\n        }{\n\n        })(R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0)^2\n    }{\n        (-R_{im} L_0 + R_{re} L_1 + R_{im} t_0 + R_{re} t_1)^2 +\n        (R_{re} L_0 + R_{im} L_1 - R_{im} t_1 - R_{re} t_0)^2\n    }\n$ -->\n\nTo learn more, visit the SymForce tutorials [here](https://symforce.org/#guides).\n\n# Build from Source\n\nFor best results, you should build from the [latest tagged release](https://github.com/symforce-org/symforce/releases/latest).  You can also build from `main`, or from another branch, but everything is less guaranteed to work.\n\nSymForce requires Python 3.8 or later. The build is currently tested on Linux and macOS, SymForce on Windows is untested (see [#145](https://github.com/symforce-org/symforce/issues/145)).  We strongly suggest creating a virtual python environment.\n\nInstall the `gmp` package with one of:\n```bash\napt install libgmp-dev            # Ubuntu\nbrew install gmp                  # Mac\nconda install -c conda-forge gmp  # Conda\n```\n\nSymForce contains both C++ and Python code. The C++ code is built using CMake.  You can build the package either by calling pip, or by calling CMake directly.  If building with `pip`, this will call CMake under the hood, and run the same CMake build for the C++ components.\n\nIf you encounter build issues, please file an [issue](https://github.com/symforce-org/symforce/issues).\n\n## Build with pip\n\nIf you just want to build and install SymForce without repeatedly modifying the source, the recommended way to do this is with pip.  From the symforce directory:\n```bash\npip install .\n```\n\nIf you're modifying the SymForce Python sources, you can do an [editable install](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs) instead.  This will let you modify the Python components of SymForce without reinstalling.  If you're going to repeatedly modify the C++ sources, you should instead build with CMake directly as described <a href=\"#build-with-cmake\">below</a>.  From the symforce directory:\n```bash\npip install -e .\n```\n\nYou should then [verify your installation](#verify-your-installation).\n\n___Note:___ `pip install .` will not install pinned versions of SymForce's dependencies, it'll install any compatible versions.  It also won't install all packages required to run all of the SymForce tests and build all of the targets (e.g. building the docs or running the linters).  If you want all packages required for that, you should `pip install .[dev]` instead (or one of the other groups of extra requirements in our `setup.py`).  If you additionally want pinned versions of our dependencies, which are the exact versions guaranteed by CI to pass all of our tests, you can install them from `pip install -r dev_requirements.txt`.\n\n_Note: Editable installs as root with the system python on Ubuntu (and other Debian derivatives) are broken on `setuptools<64.0.0`.  This is a [bug in Debian](https://ffy00.github.io/blog/02-python-debian-and-the-install-locations/), not something in SymForce that we can fix.  If this is your situation, either use a virtual environment, upgrade setuptools to a version `>=64.0.0`, or use a different installation method._\n\n## Build with CMake\n\nIf you'll be modifying the C++ parts of SymForce, you should build with CMake directly instead - this method will not install\nSymForce into your Python environment, so you'll need to add it to your PYTHONPATH separately.\n\nInstall python requirements:\n```bash\npip install -r dev_requirements.txt\n```\n\nBuild SymForce (requires C++14 or later):\n```bash\nmkdir build\ncd build\ncmake ..\nmake -j $(nproc)\n```\n\nYou'll then need to add SymForce (along with `gen/python` and `third_party/skymarshal` within symforce and `lcmtypes/python2.7` within the build directory) to your PYTHONPATH in order to use them, for example:\n\n```bash\nexport PYTHONPATH=\"$PYTHONPATH:/path/to/symforce:/path/to/symforce/build/lcmtypes/python2.7\"\n```\n\nIf you want to install SymForce to use its C++ libraries in another CMake project, you can do that with:\n```bash\nmake install\n```\n\nSymForce does not currently integrate with CMake's `find_package` (see [#209](https://github.com/symforce-org/symforce/issues/209)), so if you do this you currently need to add its libraries as link dependencies in your CMake project manually.\n\n## Verify your installation\n```python\n>>> import symforce\n>>> symforce.get_symbolic_api()\n'symengine'\n>>> from symforce import cc_sym\n```\n\nIf you see `'sympy'` here instead of `'symengine'`, or can't import `cc_sym`, your installation is probably broken and you should submit an [issue](https://github.com/symforce-org/symforce/issues).\n\n# License\n\nSymForce is released under the [Apache 2.0](https://spdx.org/licenses/Apache-2.0.html) license.\n\nSee the [LICENSE](https://github.com/symforce-org/symforce/blob/main/LICENSE) file for more information.\n\n# Sponsors\n\nSymForce is developed and maintained by [Skydio](https://skydio.com/). It is released as a free and open-source library for the robotics community.\n\n<a href=\"http://skydio.com#gh-light-mode-only\">\n    <img alt=\"Skydio Logo\" src=\"https://raw.githubusercontent.com/symforce-org/symforce/84f76b6/docs/static/images/skydio-logo-2.png\" width=\"300px\" />\n</a>\n\n\n# Contributing\n\nWhile SymForce already powers tens of thousands of robots at Skydio, the public library is new and we are releasing it in beta stage. This is just the beginning, and we are excited for engagement from the community. Thank you for helping us develop SymForce! The best way to get started is to file [issues](https://github.com/symforce-org/symforce/issues) for bugs or desired features.\n\nThere are many features we're excited to add to SymForce and would love to see contributed by the community. Most are outlined in the issues, but some major desired contributions are:\n\n- Add more backend languages, such as TypeScript and GLSL/HLSL, and improvements to the experimental CUDA and PyTorch backends\n- Support for WebAssembly compilation\n- More Lie group types, in particular Sim(3)\n- Support for constraints in our optimizer\n- Integration with [ISPC](https://ispc.github.io/)\n- Windows and conda packages\n",
    "bugtrack_url": null,
    "license": "Apache 2.0",
    "summary": "Fast symbolic computation, code generation, and nonlinear optimization for robotics",
    "version": "0.9.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/symforce-org/symforce/issues",
        "Homepage": "https://symforce.org",
        "Source": "https://github.com/symforce-org/symforce"
    },
    "split_keywords": [
        "python",
        "computer-vision",
        "cpp",
        "robotics",
        "optimization",
        "structure-from-motion",
        "motion-planning",
        "code-generation",
        "slam",
        "autonomous-vehicles",
        "symbolic-computation"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c946a452727cc0f100dd7431b4ca4dc270461691bb5677eaffd4f3d517961e6e",
                "md5": "f221e15b33ca9c8ee8fb6e949845ceb8",
                "sha256": "b23ff3920eebcb54888a1c2f13050a6fa5662d963e567246bae9a8bf45366e4e"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp310-cp310-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "f221e15b33ca9c8ee8fb6e949845ceb8",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8",
            "size": 4064427,
            "upload_time": "2023-06-08T14:30:57",
            "upload_time_iso_8601": "2023-06-08T14:30:57.408266Z",
            "url": "https://files.pythonhosted.org/packages/c9/46/a452727cc0f100dd7431b4ca4dc270461691bb5677eaffd4f3d517961e6e/symforce-0.9.0-cp310-cp310-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b79acdb68537d960fc61293faa595b194c91b77bc028e5dccd6ef7f04e96e2d0",
                "md5": "1dbc4f666726f18fc0ee84d2ef6d2292",
                "sha256": "70b6250eae472d028a77951cc3cf29e629d7eb1d3dcebc51651c3de429abc64e"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp310-cp310-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "1dbc4f666726f18fc0ee84d2ef6d2292",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8",
            "size": 3537108,
            "upload_time": "2023-06-08T14:31:02",
            "upload_time_iso_8601": "2023-06-08T14:31:02.659228Z",
            "url": "https://files.pythonhosted.org/packages/b7/9a/cdb68537d960fc61293faa595b194c91b77bc028e5dccd6ef7f04e96e2d0/symforce-0.9.0-cp310-cp310-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f5335fad8b61d053c592cd5173eaefad1668665be32bb26824c4a9fd6af9c484",
                "md5": "f962f7629546d5cc3531607a05396c33",
                "sha256": "eabcb419110f66e69cb9f4c6c0a405e6eecc9c4479535dde75e5965c0ad06eca"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "f962f7629546d5cc3531607a05396c33",
            "packagetype": "bdist_wheel",
            "python_version": "cp310",
            "requires_python": ">=3.8",
            "size": 4491737,
            "upload_time": "2023-06-08T14:31:09",
            "upload_time_iso_8601": "2023-06-08T14:31:09.107976Z",
            "url": "https://files.pythonhosted.org/packages/f5/33/5fad8b61d053c592cd5173eaefad1668665be32bb26824c4a9fd6af9c484/symforce-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "11b28ea811f036dbf6185f76857f595679d1335d5b85bf636f24d9ceb39468a9",
                "md5": "8085790ac2a380bc2788c257e7dc9923",
                "sha256": "a9dcc6860dce542ea1eee9eb014657896c25ee2fb715f5ce1a3e385c2485b143"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp311-cp311-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "8085790ac2a380bc2788c257e7dc9923",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8",
            "size": 4051302,
            "upload_time": "2023-06-08T14:31:13",
            "upload_time_iso_8601": "2023-06-08T14:31:13.868768Z",
            "url": "https://files.pythonhosted.org/packages/11/b2/8ea811f036dbf6185f76857f595679d1335d5b85bf636f24d9ceb39468a9/symforce-0.9.0-cp311-cp311-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d0142f778474fb81a11441eaef3a244d5ade9e2220c743909b175341aa510171",
                "md5": "bbed86b3d34df353d405408ad2eaf67d",
                "sha256": "8944c4b71e5ce85b33b163ce1872d0d30fc3b8a67a6ea615dadba42ae4d254a6"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp311-cp311-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "bbed86b3d34df353d405408ad2eaf67d",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8",
            "size": 3524175,
            "upload_time": "2023-06-08T14:31:18",
            "upload_time_iso_8601": "2023-06-08T14:31:18.519229Z",
            "url": "https://files.pythonhosted.org/packages/d0/14/2f778474fb81a11441eaef3a244d5ade9e2220c743909b175341aa510171/symforce-0.9.0-cp311-cp311-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bec35b36a2b2bb2085f3b9f5cd4d4c7708f7ac52911b80ea849a847921d2e02e",
                "md5": "6485b3660480ccdfc60e6462bbe5c526",
                "sha256": "ea67d7cdec1753cc77fba12131413f7a53da90a0911bd0863f0c74256b9c6067"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "6485b3660480ccdfc60e6462bbe5c526",
            "packagetype": "bdist_wheel",
            "python_version": "cp311",
            "requires_python": ">=3.8",
            "size": 4443228,
            "upload_time": "2023-06-08T14:31:23",
            "upload_time_iso_8601": "2023-06-08T14:31:23.429793Z",
            "url": "https://files.pythonhosted.org/packages/be/c3/5b36a2b2bb2085f3b9f5cd4d4c7708f7ac52911b80ea849a847921d2e02e/symforce-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "808613bd4f6794abf6576b30d5055f67518a9a8feb050fdf30a60646ccf16589",
                "md5": "c2a221dd6953bc6c07f4cdb679309b8b",
                "sha256": "f39fdaf36cd100e0520c36649dc7ae6f1686b220b8d29c24a8efe2764ea9f5ed"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp38-cp38-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "c2a221dd6953bc6c07f4cdb679309b8b",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 4069681,
            "upload_time": "2023-06-08T14:30:27",
            "upload_time_iso_8601": "2023-06-08T14:30:27.808360Z",
            "url": "https://files.pythonhosted.org/packages/80/86/13bd4f6794abf6576b30d5055f67518a9a8feb050fdf30a60646ccf16589/symforce-0.9.0-cp38-cp38-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "966b201d7fcb036195ccd2d6fbed0d0ebf1eab6a2756bc750bb8c9f8ec316fb3",
                "md5": "866d43d5d32b84d1a24d6982c6de90bf",
                "sha256": "9501442b5948b7d06ec298ddcefe12a65f1a9ebe1d431f1727ef685458c9577b"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp38-cp38-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "866d43d5d32b84d1a24d6982c6de90bf",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 3537444,
            "upload_time": "2023-06-08T14:30:33",
            "upload_time_iso_8601": "2023-06-08T14:30:33.423989Z",
            "url": "https://files.pythonhosted.org/packages/96/6b/201d7fcb036195ccd2d6fbed0d0ebf1eab6a2756bc750bb8c9f8ec316fb3/symforce-0.9.0-cp38-cp38-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a42ab973006953d4266cbfb1a3bc063a976d1d6664ffa3dee3af576f82ff81d8",
                "md5": "bf09b4567ce1f6824ac7398265741f69",
                "sha256": "3de2114bb822978805c61ab872c10915f95934ce9438b76adad5e6ca1916566d"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "bf09b4567ce1f6824ac7398265741f69",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 4532329,
            "upload_time": "2023-06-08T14:30:38",
            "upload_time_iso_8601": "2023-06-08T14:30:38.498437Z",
            "url": "https://files.pythonhosted.org/packages/a4/2a/b973006953d4266cbfb1a3bc063a976d1d6664ffa3dee3af576f82ff81d8/symforce-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a74870bb317d78fd0a4b8e8b3081bd56139002723fca8434812e27c633d267c3",
                "md5": "14c484058fa4661a58dc15b4ee317f13",
                "sha256": "e9b3d23ef77d4f1762dce845ddc71de101fcb83f60d1a29eb6c058848f87f0a2"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp39-cp39-macosx_10_9_x86_64.whl",
            "has_sig": false,
            "md5_digest": "14c484058fa4661a58dc15b4ee317f13",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8",
            "size": 4097140,
            "upload_time": "2023-06-08T14:30:42",
            "upload_time_iso_8601": "2023-06-08T14:30:42.984433Z",
            "url": "https://files.pythonhosted.org/packages/a7/48/70bb317d78fd0a4b8e8b3081bd56139002723fca8434812e27c633d267c3/symforce-0.9.0-cp39-cp39-macosx_10_9_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "dcd191e0709021c226af8d3513cd0662e0f24929a3ac7b541a8367c6c28c2253",
                "md5": "ac9faf23947c06e2c04f246d64d59fb8",
                "sha256": "decd831690f2e77f807e93badb7e85d989480393707b4c6e246b34935944633a"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp39-cp39-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "ac9faf23947c06e2c04f246d64d59fb8",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8",
            "size": 3563166,
            "upload_time": "2023-06-08T14:30:47",
            "upload_time_iso_8601": "2023-06-08T14:30:47.930073Z",
            "url": "https://files.pythonhosted.org/packages/dc/d1/91e0709021c226af8d3513cd0662e0f24929a3ac7b541a8367c6c28c2253/symforce-0.9.0-cp39-cp39-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "888ecc3559e57611919435b5d7c58482e5127417d9a50b811002c3a83ed0cd0d",
                "md5": "cb6e2e8ad080eea08397a5fa20e8ce60",
                "sha256": "d1c98230e837df80c33577c2260d3a59c37fe81329763f63678aa1da8bb4e4b0"
            },
            "downloads": -1,
            "filename": "symforce-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "cb6e2e8ad080eea08397a5fa20e8ce60",
            "packagetype": "bdist_wheel",
            "python_version": "cp39",
            "requires_python": ">=3.8",
            "size": 4540064,
            "upload_time": "2023-06-08T14:30:52",
            "upload_time_iso_8601": "2023-06-08T14:30:52.425277Z",
            "url": "https://files.pythonhosted.org/packages/88/8e/cc3559e57611919435b5d7c58482e5127417d9a50b811002c3a83ed0cd0d/symforce-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-08 14:30:57",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "symforce-org",
    "github_project": "symforce",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "lcname": "symforce"
}
        
Elapsed time: 0.09768s