@@ -4,132 +4,110 @@ title: Single and double precision
44description : OpEn with f32 and f64 number types
55---
66
7+ import Tabs from ' @theme/Tabs' ;
8+ import TabItem from ' @theme/TabItem' ;
9+
710:::note Info
8- The functionality presented here was introduced in OpEn version [ ` 0.12.0 ` ] ( https://pypi.org/project/opengen/#history ) .
9- The new API is fully backward-compatible with previous versions of OpEn.
11+ The functionality presented here was introduced in OpEn version [ ` 0.12.0 ` ] ( https://crates.io/crates/optimization_engine/0.12.0-alpha.1 ) .
12+ The new API is fully backward-compatible with previous versions of OpEn
13+ with ` f64 ` being the default scalar type.
1014:::
1115
1216## Overview
1317
14- OpEn's Rust API supports both ` f64 ` and ` f32 ` .
15-
16- Most public Rust types are generic over a scalar type ` T ` with ` T: num::Float ` , and in most places the default type is ` f64 ` . This means:
17-
18- - if you do nothing special, you will usually get ` f64 `
19- - if you want single precision, you can explicitly use ` f32 `
20- - all quantities involved in one solver instance should use the same scalar type
21-
22- In particular, this applies to:
23-
24- - cost and gradient functions
25- - constraints
26- - ` Problem `
27- - caches such as ` PANOCCache ` , ` FBSCache ` , and ` AlmCache `
28- - optimizers such as ` PANOCOptimizer ` , ` FBSOptimizer ` , and ` AlmOptimizer `
29- - solver status types such as ` SolverStatus<T> ` and ` AlmOptimizerStatus<T> `
30-
31- ## When to use ` f64 ` and when to use ` f32 `
32-
33- ### ` f64 `
34-
35- Use ` f64 ` when you want maximum numerical robustness and accuracy. This is the safest default for:
18+ OpEn's Rust API now supports both ` f64 ` and ` f32 ` . Note that with ` f32 `
19+ you may encounter issues with convergence, especially if you are solving
20+ particularly ill-conditioned problems. On the other hand, ` f32 ` is sometimes
21+ the preferred type for embedded applications and can lead to lower
22+ solve times.
3623
37- - desktop applications
38- - difficult nonlinear problems
39- - problems with tight tolerances
40- - problems that are sensitive to conditioning
24+ When using ` f32 ` : (i) make sure the problem is properly scaled,
25+ and (ii) you may want to opt for less demanding tolerances.
4126
42- ### ` f32 `
27+ ## PANOC example
4328
44- Use ` f32 ` when memory footprint and throughput matter more than ultimate accuracy. This is often useful for:
29+ Below you can see two examples of using the solver with single and double
30+ precision arithmetic.
4531
46- - embedded applications
47- - high-rate MPC loops
48- - applications where moderate tolerances are acceptable
32+ <Tabs >
4933
50- In general, ` f32 ` may require:
5134
52- - slightly looser tolerances
53- - more careful scaling of the problem
54- - fewer expectations about extremely small residuals
55-
56- ## The default: ` f64 `
57-
58- If your functions, constants, and vectors use ` f64 ` , you can often omit the scalar type completely.
35+ <TabItem value = " using-f32" label = " Single precision" >
5936
6037``` rust
6138use optimization_engine :: {constraints, panoc :: PANOCCache , Problem , SolverError };
6239use optimization_engine :: panoc :: PANOCOptimizer ;
6340
64- let tolerance = 1e - 6 ;
41+ let tolerance = 1e - 4_ f32 ;
6542let lbfgs_memory = 10 ;
66- let radius = 1.0 ;
43+ let radius = 1.0_ f32 ;
6744
6845let bounds = constraints :: Ball2 :: new (None , radius );
6946
70- let df = | u : & [f64 ], grad : & mut [f64 ]| -> Result <(), SolverError > {
71- grad [0 ] = u [0 ] + u [1 ] + 1.0 ;
72- grad [1 ] = u [0 ] + 2.0 * u [1 ] - 1.0 ;
47+ let df = | u : & [f32 ], grad : & mut [f32 ]| -> Result <(), SolverError > {
48+ grad [0 ] = u [0 ] + u [1 ] + 1.0_ f32 ;
49+ grad [1 ] = u [0 ] + 2.0_ f32 * u [1 ] - 1.0_ f32 ;
7350 Ok (())
7451};
7552
76- let f = | u : & [f64 ], cost : & mut f64 | -> Result <(), SolverError > {
77- * cost = 0.5 * (u [0 ] * u [0 ] + u [1 ] * u [1 ]);
53+ let f = | u : & [f32 ], cost : & mut f32 | -> Result <(), SolverError > {
54+ * cost = 0.5_ f32 * (u [0 ] * u [0 ] + u [1 ] * u [1 ]);
7855 Ok (())
7956};
8057
8158let problem = Problem :: new (& bounds , df , f );
82- let mut cache = PANOCCache :: new (2 , tolerance , lbfgs_memory );
59+ let mut cache = PANOCCache :: < f32 > :: new (2 , tolerance , lbfgs_memory );
8360let mut optimizer = PANOCOptimizer :: new (problem , & mut cache );
8461
85- let mut u = [0.0 , 0.0 ];
62+ let mut u = [0.0_ f32 , 0.0_ f32 ];
8663let status = optimizer . solve (& mut u ). unwrap ();
8764assert! (status . has_converged ());
8865```
66+ </TabItem >
8967
90- Because all literals and function signatures above are ` f64 ` , the compiler infers ` T = f64 ` .
91-
92- ## Using ` f32 `
93-
94- To use single precision, make the scalar type explicit throughout the problem definition.
68+ <TabItem value = " default-f64" label = " Double precision" default >
9569
9670``` rust
9771use optimization_engine :: {constraints, panoc :: PANOCCache , Problem , SolverError };
9872use optimization_engine :: panoc :: PANOCOptimizer ;
9973
100- let tolerance = 1e - 4_ f32 ;
74+ let tolerance = 1e - 6 ;
10175let lbfgs_memory = 10 ;
102- let radius = 1.0_ f32 ;
76+ let radius = 1.0 ;
10377
10478let bounds = constraints :: Ball2 :: new (None , radius );
10579
106- let df = | u : & [f32 ], grad : & mut [f32 ]| -> Result <(), SolverError > {
107- grad [0 ] = u [0 ] + u [1 ] + 1.0_ f32 ;
108- grad [1 ] = u [0 ] + 2.0_ f32 * u [1 ] - 1.0_ f32 ;
80+ let df = | u : & [f64 ], grad : & mut [f64 ]| -> Result <(), SolverError > {
81+ grad [0 ] = u [0 ] + u [1 ] + 1.0 ;
82+ grad [1 ] = u [0 ] + 2.0 * u [1 ] - 1.0 ;
10983 Ok (())
11084};
11185
112- let f = | u : & [f32 ], cost : & mut f32 | -> Result <(), SolverError > {
113- * cost = 0.5_ f32 * (u [0 ] * u [0 ] + u [1 ] * u [1 ]);
86+ let f = | u : & [f64 ], cost : & mut f64 | -> Result <(), SolverError > {
87+ * cost = 0.5 * (u [0 ] * u [0 ] + u [1 ] * u [1 ]);
11488 Ok (())
11589};
11690
11791let problem = Problem :: new (& bounds , df , f );
118- let mut cache = PANOCCache :: < f32 > :: new (2 , tolerance , lbfgs_memory );
92+ let mut cache = PANOCCache :: new (2 , tolerance , lbfgs_memory );
11993let mut optimizer = PANOCOptimizer :: new (problem , & mut cache );
12094
121- let mut u = [0.0_ f32 , 0.0_ f32 ];
95+ let mut u = [0.0 , 0.0 ];
12296let status = optimizer . solve (& mut u ). unwrap ();
12397assert! (status . has_converged ());
12498```
99+ </TabItem >
100+
101+ </Tabs >
125102
126- The key idea is that the same scalar type must be used consistently in :
103+ To use single precision, make sure that the following are all using ` f32 ` :
127104
128105- the initial guess ` u `
129106- the closures for the cost and gradient
130107- the constraints
131108- the cache
132109- any tolerances and numerical constants
110+ - You are explicitly using ` PANOCCache::<f32> ` as in the above example
133111
134112## Example with FBS
135113
@@ -175,7 +153,7 @@ For example, if you use:
175153
176154then the whole ALM solve runs in single precision.
177155
178- If instead you use plain ` f64 ` literals and ` &[f64] ` closures, the solver runs in double precision.
156+ If instead you use plain ` f64 ` literals and ` &[f64] ` closures, the solver runs in double precision. This is the default behaviour.
179157
180158## Type inference tips
181159
@@ -188,28 +166,11 @@ Good ways to make `f32` intent clear are:
188166- annotate caches explicitly, for example ` PANOCCache::<f32>::new(...) `
189167- annotate closure arguments, for example ` |u: &[f32], grad: &mut [f32]| `
190168
191- ## Important rule: do not mix ` f32 ` and ` f64 `
192-
193- The following combinations are problematic:
169+ :::warning Important rule: do not mix ` f32 ` and ` f64 `
170+ For example, the following combinations are problematic:
194171
195172- ` u: &[f32] ` with a cost function writing to ` &mut f64 `
196173- ` Ball2::new(None, 1.0_f64) ` together with ` PANOCCache::<f32> `
197- - ` tolerance = 1e-6 ` in one place and ` 1e-6_f32 ` elsewhere if inference becomes ambiguous
198174
199175Choose one scalar type per optimization problem and use it everywhere.
200-
201- ## Choosing tolerances
202-
203- When moving from ` f64 ` to ` f32 ` , it is often a good idea to relax tolerances.
204-
205- Typical starting points are:
206-
207- - ` f64 ` : ` 1e-6 ` , ` 1e-8 ` , or smaller if needed
208- - ` f32 ` : ` 1e-4 ` or ` 1e-5 `
209-
210- The right choice depends on:
211-
212- - scaling of the problem
213- - conditioning
214- - solver settings
215- - whether the problem is solved repeatedly in real time
176+ :::
0 commit comments