Skip to content

Commit 006502b

Browse files
committed
[ci skip] update website docs
1 parent e19818a commit 006502b

2 files changed

Lines changed: 95 additions & 23 deletions

File tree

docs/python-c.mdx

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ The generated C/C++ bindings are in the auto-generated solver library.
7777
In particular
7878

7979
* The header files are at `the_optimizer/the_optimizer_bindings.{h,hpp}`
80-
* The static and dynamical library files are located in `the_optimizer/target/{debug,release}` (depending on whether it was a [*debug*] or [*release*] build)
80+
* The static and dynamic library files are located in `the_optimizer/target/{debug,release}` (depending on whether it was a [*debug*] or [*release*] build)
8181

8282
Note that `the_optimizer` is the name given to the optimizer in the Python codegen above.
8383

@@ -112,13 +112,16 @@ typedef struct exampleCache exampleCache;
112112

113113
typedef struct {
114114
exampleExitStatus exit_status;
115+
int error_code;
116+
char error_message[256];
115117
unsigned long num_outer_iterations;
116118
unsigned long num_inner_iterations;
117-
double last_problem_norm_fpr;
119+
double last_problem_norm_fpr;
118120
unsigned long long solve_time_ns;
119121
double penalty;
120122
double delta_y_norm_over_c;
121123
double f2_norm;
124+
double cost;
122125
const double *lagrange;
123126
} exampleSolverStatus;
124127

@@ -137,10 +140,61 @@ This is designed to follow a new-use-free pattern.
137140
138141
Function `{optimizer-name}_new` will allocate memory and setup a new solver instance and can be used to create as many solvers as necessary. Each solver instance can be used with `{optimizer-name}_solve` to solve the corresponding problem as many times as needed.
139142
140-
Parameter `u` is the starting guess and also the return of the decision variables and `params` is the array of static parameters. The size of `u` and `params` are `{optimizer-name}_NUM_DECISION_VARIABLES` and `{optimizer-name}_NUM_PARAMETERS` respectively.
143+
Parameter `u` is the starting guess and also the return of the decision variables and `params` is the array of static parameters. The size of `u` and `params` are `{optimizer-name}_NUM_DECISION_VARIABLES` and `{optimizer-name}_NUM_PARAMETERS` respectively. Arguments `y0` and `c0` are optional: pass `0` (or `NULL`) to use the default initial Lagrange multipliers and penalty parameter.
144+
145+
The returned `exampleSolverStatus` always contains a coarse solver outcome in
146+
`exit_status`. On success it also contains `error_code = 0` and an empty
147+
`error_message`. If the solver fails internally, the bindings return a
148+
structured error report with a nonzero `error_code` and a descriptive
149+
`error_message`.
141150
142151
Finally, when done with the solver, use `{optimizer-name}_free` to release the memory allocated by `{optimizer-name}_new`.
143152
153+
## Handling errors
154+
155+
The C bindings always return a value of type `exampleSolverStatus`. This means
156+
that solver calls do not report failure by returning `NULL` or by using a
157+
separate exception-like mechanism. Instead, callers should inspect both
158+
`exit_status` and `error_code`.
159+
160+
- `error_code = 0` means the solver call completed without an internal error
161+
- `error_code != 0` means the solver failed and `error_message` contains a
162+
descriptive explanation
163+
- `exit_status` gives the coarse outcome of the solve attempt, such as
164+
converged, reached the iteration limit, or failed because of a numerical
165+
issue
166+
167+
The recommended pattern is:
168+
169+
1. Call `{optimizer-name}_solve(...)`
170+
2. Check whether `status.error_code != 0`
171+
3. If so, report `status.error_message` and treat the call as failed
172+
4. Otherwise, inspect `status.exit_status` to determine whether the solver
173+
converged or returned the best available non-converged iterate
174+
175+
For example:
176+
177+
```c
178+
exampleSolverStatus status = example_solve(cache, u, p, 0, &initial_penalty);
179+
180+
if (status.error_code != 0) {
181+
fprintf(stderr, "Solver failed: [%d] %s\n",
182+
status.error_code, status.error_message);
183+
example_free(cache);
184+
return EXIT_FAILURE;
185+
}
186+
187+
if (status.exit_status != exampleConverged) {
188+
fprintf(stderr, "Warning: solver did not converge fully\n");
189+
}
190+
```
191+
192+
The generated C example follows exactly this pattern.
193+
194+
At the ABI level, callers are still responsible for passing valid pointers and
195+
correctly sized arrays for `u`, `params`, and optional arguments such as `y0`.
196+
Those are contract violations, not recoverable solver errors.
197+
144198

145199
## Using the bindings in an app
146200

@@ -155,26 +209,41 @@ The auto-generated example has the following form:
155209
/* File: the_optimizer/example_optimizer.c */
156210

157211
#include <stdio.h>
212+
#include <stdlib.h>
158213
#include "example_bindings.h"
159214

160-
int main() {
161-
double p[EXAMPLE_NUM_PARAMETERS] = {1.0, 10.0}; // parameter
215+
int main(void) {
216+
double p[EXAMPLE_NUM_PARAMETERS] = {0}; // parameter
162217
double u[EXAMPLE_NUM_DECISION_VARIABLES] = {0}; // initial guess
218+
double initial_penalty = 15.0;
163219

164220
exampleCache *cache = example_new();
165-
exampleSolverStatus status = example_solve(cache, u, p);
166-
example_free(cache);
167-
168-
for (int i = 0; i < EXAMPLE_NUM_DECISION_VARIABLES; ++i) {
169-
printf("u[%d] = %g\n", i, u[i]);
221+
if (cache == NULL) {
222+
fprintf(stderr, "Could not allocate solver cache\n");
223+
return EXIT_FAILURE;
170224
}
171225

226+
exampleSolverStatus status = example_solve(cache, u, p, 0, &initial_penalty);
227+
172228
printf("exit status = %d\n", status.exit_status);
229+
printf("error code = %d\n", status.error_code);
230+
printf("error message = %s\n", status.error_message);
173231
printf("iterations = %lu\n", status.num_inner_iterations);
174232
printf("outer iterations = %lu\n", status.num_outer_iterations);
175233
printf("solve time = %f ms\n", (double)status.solve_time_ns / 1e6);
176234

177-
return 0;
235+
if (status.error_code != 0) {
236+
example_free(cache);
237+
return EXIT_FAILURE;
238+
}
239+
240+
for (int i = 0; i < EXAMPLE_NUM_DECISION_VARIABLES; ++i) {
241+
printf("u[%d] = %g\n", i, u[i]);
242+
}
243+
244+
example_free(cache);
245+
246+
return EXIT_SUCCESS;
178247
}
179248
```
180249
@@ -185,19 +254,18 @@ int main() {
185254
To compile your C program you need to link to the auto-generated
186255
C bindings (see [next section](#compile-your-own-code)).
187256
However, OpEn generates automatically a `CMakeLists.txt` file
188-
to facilitate the compilation/linking procedure. To build the
189-
auto-generated example run
257+
to facilitate the compilation/linking procedure. A typical build is:
190258
191259
```bash
192-
cmake .
193-
make
260+
cmake -S . -B build
261+
cmake --build build
194262
```
195263

196-
once you build your optimizer you can run the executable (`optimizer`)
197-
with
264+
Once you build your optimizer you can run the executable (`optimizer`)
265+
with:
198266

199267
```bash
200-
make run
268+
cmake --build build --target run
201269
```
202270

203271
#### Compile your own code
@@ -260,14 +328,18 @@ LD_LIBRARY_PATH=./target/release ./optimizer
260328
The output looks like this:
261329

262330
```text
331+
exit status = 0
332+
error code = 0
333+
error message =
334+
iterations = 69
335+
outer iterations = 5
336+
solve time = 0.140401 ms
263337
u[0] = 0.654738
264338
u[1] = 0.982045
265339
u[2] = 0.98416
266340
u[3] = 0.984188
267341
u[4] = 0.969986
268-
exit status = 0
269-
iterations = 69
270-
outer iterations = 5
271-
solve time = 0.140401 ms
272342
```
273343

344+
If `error_code` is nonzero, the solver failed to produce a valid result and
345+
`error_message` contains the propagated reason from the generated Rust solver.

open-codegen/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Note: This is the Changelog file of `opengen` - the Python interface of OpEn
2121
- Extended `RosConfiguration` so it can be used for both ROS and ROS2 package generation
2222
- Breaking change: the direct interface (Python bindings) now has an API which mirrors that of the TCP interface: the method `solve` returns either a solution or an error object. Website documentation is updated. New unit tests are implemented. Note that `solver.run()` does not return the solution object directly, but rather works in the same way as the TCP interface: it returns a response object (instance of `SolverResponse`), on which the method `.get()` returns either a `SolverStatus` or `SolverError`.
2323
- Added helpful `__repr__` methods to generated Python binding response/status/error objects, TCP solver response/error objects, and `GeneratedOptimizer` for easier inspection and debugging
24-
- Updated generated TCP server and C interface templates to work with the richer Rust solver error model and expose better failure information to clients
24+
- Updated generated TCP server and C interface templates to work with the richer Rust solver error model and expose better failure information to clients. Updated auto-generated `CMakeLists.txt` file. Tighter unit tests.
2525
- ROS2 generated packages now publish detailed `error_code` and `error_message` fields, plus `STATUS_INVALID_REQUEST`, so invalid requests and solver failures are reported explicitly instead of being silently ignored
2626

2727

0 commit comments

Comments
 (0)