# tiny-solver-rs
> Warning! This project is still under development.
[](https://crates.io/crates/tiny-solver)
[](https://pypi.org/project/tiny-solver)
[](https://pypi.org/project/tiny-solver)
Inspired by [ceres-solver](https://github.com/ceres-solver/ceres-solver), [tiny-solver](https://github.com/keir/tinysolver), and [minisam](https://github.com/dongjing3309/minisam).
This is a general optimizer written in Rust, including bindings for Python. If you're familiar with ceres-solver or factor-graph optimizers, you'll find it very easy to use.
## Installation
### python
The python package can be installed directly from PyPI:
```sh
pip install tiny-solver
```
### rust
```sh
cargo add tiny-solver
```
## Current Features
- [x] Automatic Derivatives using [num-dual](https://github.com/itt-ustutt/num-dual)
- [x] ~~Sparse QR~~, Sparse Cholesky using [faer](https://github.com/sarah-ek/faer-rs)
- [x] GaussNewtonOptimizer
- [x] Multithreading jacobian
- [x] loss function (Huber)
- [x] Define factor in python
#### TODO
- [ ] LevenbergMarquardtOptimizer
- [ ] information matrix
## Benchmark
| dataset | tiny-solver | gtsam | minisam |
|---------|-------------|---------|----------|
| m3500 | 161.1ms | 130.7ms | 123.6 ms |
| | | | |
It's not extremely optimized, but it's easy to install and use.
## Usage
Rust
```rust
// define your own Cost/Factor struct
// impl residual function
// and the jacobian will be auto generated
struct CustomFactor {}
impl tiny_solver::factors::Factor for CustomFactor {
fn residual_func(
&self,
params: &[nalgebra::DVector<num_dual::DualDVec64>],
) -> nalgebra::DVector<num_dual::DualDVec64> {
let x = ¶ms[0][0];
let y = ¶ms[1][0];
let z = ¶ms[1][1];
na::dvector![x + y.clone().mul(2.0) + z.clone().mul(4.0), y.mul(z)]
}
}
fn main() {
// init logger, `export RUST_LOG=trace` to see more log
env_logger::init();
// init problem (factor graph)
let mut problem = tiny_solver::Problem::new();
// add residual blocks (factors)
// add residual x needs to be close to 3.0
problem.add_residual_block(
1,
vec![("x".to_string(), 1)],
Box::new(tiny_solver::factors::PriorFactor {
v: na::dvector![3.0],
}),
None,
);
// add custom residual for x and yz
problem.add_residual_block(
2,
vec![("x".to_string(), 1), ("yz".to_string(), 2)],
Box::new(CustomFactor {}),
None,
);
// the initial values for x is 0.7 and yz is [-30.2, 123.4]
let initial_values = HashMap::<String, na::DVector<f64>>::from([
("x".to_string(), na::dvector![0.7]),
("yz".to_string(), na::dvector![-30.2, 123.4]),
]);
// initialize optimizer
let optimizer = tiny_solver::GaussNewtonOptimizer {};
// optimize
let result = optimizer.optimize(&problem, &initial_values, None);
// result
for (k, v) in result {
println!("{}: {}", k, v);
}
}
```
Python
```py
import numpy as np
from tiny_solver import Problem, GaussNewtonOptimizer
from tiny_solver.factors import PriorFactor, PyFactor
# define custom cost function in python
# the trade off is the jacobian for the problem cannot be done in parallel
# because of gil
def cost(x: np.ndarray, yz: np.ndarray) -> np.ndarray:
r0 = x[0] + 2 * yz[0] + 4 * yz[1]
r1 = yz[0] * yz[0]
return np.array([r0, r1])
def main():
# initialize problem (factor graph)
problem = Problem()
# factor defined in python
custom_factor = PyFactor(cost)
problem.add_residual_block(
2,
[
("x", 1),
("yz", 2),
],
custom_factor,
None,
)
# prior factor import from rust
prior_factor = PriorFactor(np.array([3.0]))
problem.add_residual_block(1, [("x", 1)], prior_factor, None)
# initial values
init_values = {"x": np.array([0.7]), "yz": np.array([-30.2, 123.4])}
# optimizer
optimizer = GaussNewtonOptimizer()
result_values = optimizer.optimize(problem, init_values)
# result
for k, v in result_values.items():
print(f"{k}: {v}")
if __name__ == "__main__":
main()
```
## Example
### Basic example
```sh
cargo run -r --example small_problem
```
### M3500 dataset
<img src="docs/m3500_rs.png" width="300" alt="m3500 dataset rust result.">
```sh
git clone https://github.com/powei-lin/tiny-solver-rs.git
cd tiny-solver-rs
# run rust version
cargo run -r --example m3500_benchmar
# run python version
pip install tiny-solver matplotlib
python3 examples/python/m3500.py
```
Raw data
{
"_id": null,
"home_page": "https://github.com/powei-lin/tiny-solver-rs",
"name": "tiny-solver",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "factor-graph, ceres-solver, minisam",
"author": "Powei Lin <poweilin1994@gmail.com>",
"author_email": "Powei Lin <poweilin1994@gmail.com>",
"download_url": null,
"platform": null,
"description": "# tiny-solver-rs\n> Warning! This project is still under development.\n\n[](https://crates.io/crates/tiny-solver)\n[](https://pypi.org/project/tiny-solver)\n[](https://pypi.org/project/tiny-solver)\n\nInspired by [ceres-solver](https://github.com/ceres-solver/ceres-solver), [tiny-solver](https://github.com/keir/tinysolver), and [minisam](https://github.com/dongjing3309/minisam).\n\nThis is a general optimizer written in Rust, including bindings for Python. If you're familiar with ceres-solver or factor-graph optimizers, you'll find it very easy to use.\n\n## Installation\n### python\nThe python package can be installed directly from PyPI:\n```sh\npip install tiny-solver\n```\n### rust\n```sh\ncargo add tiny-solver\n```\n\n## Current Features\n\n- [x] Automatic Derivatives using [num-dual](https://github.com/itt-ustutt/num-dual)\n- [x] ~~Sparse QR~~, Sparse Cholesky using [faer](https://github.com/sarah-ek/faer-rs)\n- [x] GaussNewtonOptimizer\n- [x] Multithreading jacobian\n- [x] loss function (Huber)\n- [x] Define factor in python\n\n#### TODO\n- [ ] LevenbergMarquardtOptimizer\n- [ ] information matrix\n\n## Benchmark\n| dataset | tiny-solver | gtsam | minisam |\n|---------|-------------|---------|----------|\n| m3500 | 161.1ms | 130.7ms | 123.6 ms |\n| | | | |\n\nIt's not extremely optimized, but it's easy to install and use.\n\n## Usage\nRust \n```rust\n// define your own Cost/Factor struct\n// impl residual function\n// and the jacobian will be auto generated\nstruct CustomFactor {}\nimpl tiny_solver::factors::Factor for CustomFactor {\n fn residual_func(\n &self,\n params: &[nalgebra::DVector<num_dual::DualDVec64>],\n ) -> nalgebra::DVector<num_dual::DualDVec64> {\n let x = ¶ms[0][0];\n let y = ¶ms[1][0];\n let z = ¶ms[1][1];\n\n na::dvector![x + y.clone().mul(2.0) + z.clone().mul(4.0), y.mul(z)]\n }\n}\n\nfn main() {\n // init logger, `export RUST_LOG=trace` to see more log\n env_logger::init();\n\n // init problem (factor graph)\n let mut problem = tiny_solver::Problem::new();\n\n // add residual blocks (factors)\n // add residual x needs to be close to 3.0\n problem.add_residual_block(\n 1,\n vec![(\"x\".to_string(), 1)],\n Box::new(tiny_solver::factors::PriorFactor {\n v: na::dvector![3.0],\n }),\n None,\n );\n // add custom residual for x and yz\n problem.add_residual_block(\n 2,\n vec![(\"x\".to_string(), 1), (\"yz\".to_string(), 2)],\n Box::new(CustomFactor {}),\n None,\n );\n\n // the initial values for x is 0.7 and yz is [-30.2, 123.4]\n let initial_values = HashMap::<String, na::DVector<f64>>::from([\n (\"x\".to_string(), na::dvector![0.7]),\n (\"yz\".to_string(), na::dvector![-30.2, 123.4]),\n ]);\n\n // initialize optimizer\n let optimizer = tiny_solver::GaussNewtonOptimizer {};\n\n // optimize\n let result = optimizer.optimize(&problem, &initial_values, None);\n\n // result\n for (k, v) in result {\n println!(\"{}: {}\", k, v);\n }\n}\n```\nPython\n```py\nimport numpy as np\nfrom tiny_solver import Problem, GaussNewtonOptimizer\nfrom tiny_solver.factors import PriorFactor, PyFactor\n\n# define custom cost function in python\n# the trade off is the jacobian for the problem cannot be done in parallel\n# because of gil\ndef cost(x: np.ndarray, yz: np.ndarray) -> np.ndarray:\n r0 = x[0] + 2 * yz[0] + 4 * yz[1]\n r1 = yz[0] * yz[0]\n return np.array([r0, r1])\n\n\ndef main():\n\n # initialize problem (factor graph)\n problem = Problem()\n\n # factor defined in python\n custom_factor = PyFactor(cost)\n problem.add_residual_block(\n 2,\n [\n (\"x\", 1),\n (\"yz\", 2),\n ],\n custom_factor,\n None,\n )\n\n # prior factor import from rust\n prior_factor = PriorFactor(np.array([3.0]))\n problem.add_residual_block(1, [(\"x\", 1)], prior_factor, None)\n\n # initial values\n init_values = {\"x\": np.array([0.7]), \"yz\": np.array([-30.2, 123.4])}\n\n # optimizer\n optimizer = GaussNewtonOptimizer()\n result_values = optimizer.optimize(problem, init_values)\n\n # result\n for k, v in result_values.items():\n print(f\"{k}: {v}\")\n\n\nif __name__ == \"__main__\":\n main()\n```\n\n## Example\n### Basic example\n```sh\ncargo run -r --example small_problem\n```\n### M3500 dataset\n<img src=\"docs/m3500_rs.png\" width=\"300\" alt=\"m3500 dataset rust result.\">\n\n```sh\ngit clone https://github.com/powei-lin/tiny-solver-rs.git\ncd tiny-solver-rs\n\n# run rust version\ncargo run -r --example m3500_benchmar\n\n# run python version\npip install tiny-solver matplotlib\npython3 examples/python/m3500.py\n```\n",
"bugtrack_url": null,
"license": "MIT OR Apache-2.0",
"summary": "Factor graph solver",
"version": "0.7.0",
"project_urls": {
"Homepage": "https://github.com/powei-lin/tiny-solver-rs",
"Source Code": "https://github.com/powei-lin/tiny-solver-rs"
},
"split_keywords": [
"factor-graph",
" ceres-solver",
" minisam"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "431e387913676301a7efeb46af37ae62ef7139670e1095a3dcba037233613f58",
"md5": "970de8609b90da0fee68ceb20ef7a63e",
"sha256": "9898dae2bdf83eec9958c2caacef1c49d0f1fd3582cbcb27185170f9e194b91b"
},
"downloads": -1,
"filename": "tiny_solver-0.7.0-cp37-abi3-macosx_11_0_arm64.whl",
"has_sig": false,
"md5_digest": "970de8609b90da0fee68ceb20ef7a63e",
"packagetype": "bdist_wheel",
"python_version": "cp37",
"requires_python": ">=3.8",
"size": 2884042,
"upload_time": "2024-10-14T21:40:05",
"upload_time_iso_8601": "2024-10-14T21:40:05.247097Z",
"url": "https://files.pythonhosted.org/packages/43/1e/387913676301a7efeb46af37ae62ef7139670e1095a3dcba037233613f58/tiny_solver-0.7.0-cp37-abi3-macosx_11_0_arm64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "4c128f50cf53e86c46b02d20ae0e9dddcf376e5d3babb3362e2427656bff477a",
"md5": "0e0b684ea96a32b3839ae99a47e30c81",
"sha256": "a1c491a74978307b2f74da3685a610a5aa6ac63c1a219d22ba3c6f716cf4a973"
},
"downloads": -1,
"filename": "tiny_solver-0.7.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"has_sig": false,
"md5_digest": "0e0b684ea96a32b3839ae99a47e30c81",
"packagetype": "bdist_wheel",
"python_version": "cp37",
"requires_python": ">=3.8",
"size": 4207472,
"upload_time": "2024-10-14T21:44:07",
"upload_time_iso_8601": "2024-10-14T21:44:07.693564Z",
"url": "https://files.pythonhosted.org/packages/4c/12/8f50cf53e86c46b02d20ae0e9dddcf376e5d3babb3362e2427656bff477a/tiny_solver-0.7.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "a94ba1542c28dbaf84bb68dfb8e5272c806499f5a7c396be3b72a7a07161c8b6",
"md5": "2ca0e87f16794a3ee2fc3f503611d451",
"sha256": "bb3b4886e27eaa0dce79ab0f5c64577732f516b369737005aaa190918818ff2c"
},
"downloads": -1,
"filename": "tiny_solver-0.7.0-cp37-abi3-manylinux_2_35_x86_64.whl",
"has_sig": false,
"md5_digest": "2ca0e87f16794a3ee2fc3f503611d451",
"packagetype": "bdist_wheel",
"python_version": "cp37",
"requires_python": ">=3.8",
"size": 3617814,
"upload_time": "2024-10-14T21:44:10",
"upload_time_iso_8601": "2024-10-14T21:44:10.926972Z",
"url": "https://files.pythonhosted.org/packages/a9/4b/a1542c28dbaf84bb68dfb8e5272c806499f5a7c396be3b72a7a07161c8b6/tiny_solver-0.7.0-cp37-abi3-manylinux_2_35_x86_64.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-14 21:40:05",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "powei-lin",
"github_project": "tiny-solver-rs",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "tiny-solver"
}