elastiqp 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {elastiqp-0.1.0 → elastiqp-0.2.0}/.github/workflows/wheels.yml +14 -4
- {elastiqp-0.1.0 → elastiqp-0.2.0}/CMakeLists.txt +46 -2
- {elastiqp-0.1.0 → elastiqp-0.2.0}/PKG-INFO +31 -14
- {elastiqp-0.1.0 → elastiqp-0.2.0}/README.md +30 -13
- {elastiqp-0.1.0 → elastiqp-0.2.0}/bindings/bindings.cc +12 -68
- {elastiqp-0.1.0 → elastiqp-0.2.0}/bindings/jax_ffi.cc +20 -33
- elastiqp-0.2.0/cmake/elastiqpConfig.cmake.in +18 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/cmake/elastiqp_install.cmake +8 -1
- {elastiqp-0.1.0 → elastiqp-0.2.0}/examples/basic_usage.py +1 -2
- {elastiqp-0.1.0 → elastiqp-0.2.0}/examples/jax_usage.py +1 -5
- {elastiqp-0.1.0 → elastiqp-0.2.0}/examples/torch_usage.py +1 -5
- {elastiqp-0.1.0 → elastiqp-0.2.0}/include/elastiqp/common.hpp +26 -2
- {elastiqp-0.1.0 → elastiqp-0.2.0}/include/elastiqp/das.hpp +401 -68
- elastiqp-0.2.0/include/elastiqp/elastiqp_c.h +233 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/include/elastiqp/ipm.hpp +91 -140
- {elastiqp-0.1.0 → elastiqp-0.2.0}/include/elastiqp/kkt_vjp.hpp +7 -1
- {elastiqp-0.1.0 → elastiqp-0.2.0}/include/elastiqp/pdal.hpp +56 -119
- {elastiqp-0.1.0 → elastiqp-0.2.0}/package.xml +1 -1
- {elastiqp-0.1.0 → elastiqp-0.2.0}/python/elastiqp/__init__.py +11 -2
- {elastiqp-0.1.0 → elastiqp-0.2.0}/python/elastiqp/jax.py +76 -124
- {elastiqp-0.1.0 → elastiqp-0.2.0}/python/elastiqp/torch.py +20 -39
- elastiqp-0.2.0/src/elastiqp_c.cc +534 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/support/drift_traj.hpp +1 -1
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/support/test_util.hpp +5 -2
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_bindings.py +35 -17
- elastiqp-0.2.0/tests/test_c_api.c +425 -0
- elastiqp-0.2.0/tests/test_c_parity.cc +234 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_das.cc +298 -0
- elastiqp-0.2.0/tests/test_das_alloc.cc +397 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_ipm.cc +7 -122
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_jax_ffi.py +130 -94
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_pdal.cc +18 -85
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_solvers.cc +3 -3
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_torch.py +105 -37
- elastiqp-0.1.0/cmake/elastiqpConfig.cmake.in +0 -8
- {elastiqp-0.1.0 → elastiqp-0.2.0}/.clang-format +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/.gitignore +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/LICENSE +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/NOTICE +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/examples/constraint_conflict_demo.py +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/include/elastiqp/elastiqp.hpp +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/pyproject.toml +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/data/warmstart_dual_overshoot.npz +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/data/warmstart_stale_duals.npz +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/support/problem_gen.hpp +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_bcl_creep.cc +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_gap_creep.cc +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_lp.cc +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/tests/test_ruiz.cc +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/third_party/xla_ffi/README.md +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/third_party/xla_ffi/xla/ffi/api/api.h +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/third_party/xla_ffi/xla/ffi/api/c_api.h +0 -0
- {elastiqp-0.1.0 → elastiqp-0.2.0}/third_party/xla_ffi/xla/ffi/api/ffi.h +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
# Build manylinux
|
|
1
|
+
# Build manylinux, macOS, and Windows wheels and an sdist, then publish to PyPI on a version tag.
|
|
2
2
|
#
|
|
3
|
-
# Manual runs (workflow_dispatch) build everything and publish to TestPyPI
|
|
3
|
+
# Manual runs (workflow_dispatch) build everything, and publish to TestPyPI
|
|
4
|
+
# only if the "publish" box is ticked.
|
|
4
5
|
# Tag pushes matching v* publish to PyPI.
|
|
5
6
|
#
|
|
6
7
|
# Both publish jobs use PyPI trusted publishing (no API token): register this
|
|
@@ -17,6 +18,11 @@ on:
|
|
|
17
18
|
- "pyproject.toml"
|
|
18
19
|
- "CMakeLists.txt"
|
|
19
20
|
workflow_dispatch:
|
|
21
|
+
inputs:
|
|
22
|
+
publish:
|
|
23
|
+
description: "Publish to TestPyPI"
|
|
24
|
+
type: boolean
|
|
25
|
+
default: false
|
|
20
26
|
|
|
21
27
|
jobs:
|
|
22
28
|
build_wheels:
|
|
@@ -25,7 +31,7 @@ jobs:
|
|
|
25
31
|
strategy:
|
|
26
32
|
fail-fast: false
|
|
27
33
|
matrix:
|
|
28
|
-
os: [ubuntu-latest, ubuntu-24.04-arm, macos-14]
|
|
34
|
+
os: [ubuntu-latest, ubuntu-24.04-arm, macos-14, windows-latest]
|
|
29
35
|
steps:
|
|
30
36
|
- uses: actions/checkout@v5
|
|
31
37
|
|
|
@@ -33,6 +39,7 @@ jobs:
|
|
|
33
39
|
env:
|
|
34
40
|
CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*"
|
|
35
41
|
CIBW_SKIP: "*-musllinux_* *_i686"
|
|
42
|
+
CIBW_ARCHS_WINDOWS: AMD64
|
|
36
43
|
# AlmaLinux 8 based image: glibc 2.28, broad compatibility.
|
|
37
44
|
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
|
|
38
45
|
CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
|
|
@@ -40,6 +47,9 @@ jobs:
|
|
|
40
47
|
CIBW_BEFORE_ALL_LINUX: dnf install -y epel-release && dnf install -y eigen3-devel
|
|
41
48
|
CIBW_BEFORE_ALL_MACOS: brew install eigen
|
|
42
49
|
CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=11.0
|
|
50
|
+
# vcpkg is preinstalled at C:\vcpkg on GitHub's Windows runners.
|
|
51
|
+
CIBW_BEFORE_ALL_WINDOWS: vcpkg install eigen3:x64-windows
|
|
52
|
+
CIBW_ENVIRONMENT_WINDOWS: CMAKE_PREFIX_PATH=C:/vcpkg/installed/x64-windows
|
|
43
53
|
# Smoke-test each wheel from a clean venv (no ELASTIQP_BUILD_DIR set,
|
|
44
54
|
# so the test imports the installed elastiqp._core).
|
|
45
55
|
CIBW_TEST_COMMAND: python {project}/tests/test_bindings.py
|
|
@@ -67,7 +77,7 @@ jobs:
|
|
|
67
77
|
|
|
68
78
|
publish_testpypi:
|
|
69
79
|
name: publish to TestPyPI
|
|
70
|
-
if: github.event_name == 'workflow_dispatch'
|
|
80
|
+
if: github.event_name == 'workflow_dispatch' && inputs.publish
|
|
71
81
|
needs: [build_wheels, build_sdist]
|
|
72
82
|
runs-on: ubuntu-latest
|
|
73
83
|
environment: testpypi
|
|
@@ -18,8 +18,10 @@ else()
|
|
|
18
18
|
endif()
|
|
19
19
|
option(ELASTIQP_BUILD_TESTS "Build the elastiqp test suite" ${_elastiqp_top_level})
|
|
20
20
|
option(ELASTIQP_BUILD_PYTHON "Build the Python bindings and JAX FFI" ${_elastiqp_top_level})
|
|
21
|
+
option(ELASTIQP_BUILD_C "Build the C interface library (elastiqp_c)" ${_elastiqp_top_level})
|
|
21
22
|
if(SKBUILD) # pip wheel build: bindings only, no install/export
|
|
22
23
|
set(ELASTIQP_BUILD_TESTS OFF)
|
|
24
|
+
set(ELASTIQP_BUILD_C OFF)
|
|
23
25
|
set(ELASTIQP_BUILD_PYTHON ON)
|
|
24
26
|
endif()
|
|
25
27
|
|
|
@@ -34,6 +36,27 @@ target_include_directories(elastiqp INTERFACE
|
|
|
34
36
|
target_link_libraries(elastiqp INTERFACE Eigen3::Eigen)
|
|
35
37
|
target_compile_features(elastiqp INTERFACE cxx_std_17)
|
|
36
38
|
|
|
39
|
+
# C interface library
|
|
40
|
+
if(ELASTIQP_BUILD_C)
|
|
41
|
+
enable_language(C)
|
|
42
|
+
add_library(elastiqp_c src/elastiqp_c.cc)
|
|
43
|
+
add_library(elastiqp::elastiqp_c ALIAS elastiqp_c)
|
|
44
|
+
target_link_libraries(elastiqp_c PRIVATE elastiqp::elastiqp)
|
|
45
|
+
target_include_directories(elastiqp_c PUBLIC
|
|
46
|
+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
|
47
|
+
$<INSTALL_INTERFACE:include>)
|
|
48
|
+
target_compile_definitions(elastiqp_c PRIVATE ELASTIQP_C_BUILDING)
|
|
49
|
+
if(BUILD_SHARED_LIBS)
|
|
50
|
+
target_compile_definitions(elastiqp_c PUBLIC ELASTIQP_C_SHARED)
|
|
51
|
+
endif()
|
|
52
|
+
set_target_properties(elastiqp_c PROPERTIES
|
|
53
|
+
CXX_VISIBILITY_PRESET hidden
|
|
54
|
+
VISIBILITY_INLINES_HIDDEN ON
|
|
55
|
+
POSITION_INDEPENDENT_CODE ON
|
|
56
|
+
VERSION ${PROJECT_VERSION}
|
|
57
|
+
SOVERSION ${PROJECT_VERSION_MAJOR})
|
|
58
|
+
endif()
|
|
59
|
+
|
|
37
60
|
# Headers for tests (never installed)
|
|
38
61
|
add_library(elastiqp_testing INTERFACE)
|
|
39
62
|
add_library(elastiqp::testing ALIAS elastiqp_testing)
|
|
@@ -55,6 +78,9 @@ if(ELASTIQP_BUILD_TESTS)
|
|
|
55
78
|
elastiqp_test(test_das)
|
|
56
79
|
add_test(NAME elastiqp.das COMMAND test_das)
|
|
57
80
|
|
|
81
|
+
elastiqp_test(test_das_alloc)
|
|
82
|
+
add_test(NAME elastiqp.das_alloc COMMAND test_das_alloc)
|
|
83
|
+
|
|
58
84
|
elastiqp_test(test_pdal)
|
|
59
85
|
add_test(NAME elastiqp.pdal COMMAND test_pdal)
|
|
60
86
|
|
|
@@ -72,16 +98,34 @@ if(ELASTIQP_BUILD_TESTS)
|
|
|
72
98
|
|
|
73
99
|
elastiqp_test(test_lp)
|
|
74
100
|
add_test(NAME elastiqp.lp COMMAND test_lp)
|
|
101
|
+
|
|
102
|
+
if(ELASTIQP_BUILD_C)
|
|
103
|
+
add_executable(test_c_api tests/test_c_api.c)
|
|
104
|
+
target_link_libraries(test_c_api PRIVATE elastiqp::elastiqp_c)
|
|
105
|
+
if(NOT MSVC)
|
|
106
|
+
target_link_libraries(test_c_api PRIVATE m)
|
|
107
|
+
endif()
|
|
108
|
+
add_test(NAME elastiqp.c_api COMMAND test_c_api)
|
|
109
|
+
|
|
110
|
+
elastiqp_test(test_c_parity)
|
|
111
|
+
target_link_libraries(test_c_parity PRIVATE elastiqp::elastiqp_c)
|
|
112
|
+
add_test(NAME elastiqp.c_parity COMMAND test_c_parity)
|
|
113
|
+
endif()
|
|
75
114
|
endif()
|
|
76
115
|
|
|
77
116
|
# Python bindings and JAX FFI
|
|
78
117
|
if(ELASTIQP_BUILD_PYTHON)
|
|
79
118
|
add_library(elastiqp_jax_ffi SHARED bindings/jax_ffi.cc)
|
|
80
|
-
target_include_directories(elastiqp_jax_ffi PRIVATE
|
|
119
|
+
target_include_directories(elastiqp_jax_ffi SYSTEM PRIVATE
|
|
81
120
|
${CMAKE_CURRENT_SOURCE_DIR}/third_party/xla_ffi)
|
|
82
121
|
target_link_libraries(elastiqp_jax_ffi PRIVATE elastiqp::elastiqp)
|
|
122
|
+
# The XLA FFI handler symbols carry no dllexport; export them for ctypes.
|
|
123
|
+
set_target_properties(elastiqp_jax_ffi PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
|
83
124
|
if(SKBUILD)
|
|
84
|
-
|
|
125
|
+
# A Windows DLL is a RUNTIME artifact, not a LIBRARY one.
|
|
126
|
+
install(TARGETS elastiqp_jax_ffi
|
|
127
|
+
LIBRARY DESTINATION elastiqp
|
|
128
|
+
RUNTIME DESTINATION elastiqp)
|
|
85
129
|
endif()
|
|
86
130
|
|
|
87
131
|
find_package(Python 3.8 COMPONENTS Interpreter Development.Module QUIET)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: elastiqp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: An always-feasible QP solver for constrained robot control
|
|
5
5
|
Author-Email: Daniel Morton <danielpmorton@gmail.com>
|
|
6
6
|
License: Copyright 2026 Daniel Morton
|
|
@@ -213,14 +213,14 @@ Provides-Extra: all
|
|
|
213
213
|
Requires-Dist: elastiqp[dev,examples,jax,torch]; extra == "all"
|
|
214
214
|
Description-Content-Type: text/markdown
|
|
215
215
|
|
|
216
|
+
<div align="center">
|
|
217
|
+
<img src="https://github.com/user-attachments/assets/12b43719-2b23-478d-9aaa-5481911741b4" alt="logo" width="400" height="73"></img>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
216
220
|
# ElastiQP
|
|
217
221
|
|
|
218
222
|
[](https://arxiv.org/abs/2609.19080)
|
|
219
223
|
|
|
220
|
-
> [!WARNING]
|
|
221
|
-
> Currently under construction, apologies for any rough edges
|
|
222
|
-
|
|
223
|
-
|
|
224
224
|
An always-feasible QP solver for constrained robot control.
|
|
225
225
|
|
|
226
226
|
ElastiQP solves the following problem:
|
|
@@ -281,9 +281,6 @@ If you've installed with CMake, you can also `find_package(elastiqp)`
|
|
|
281
281
|
|
|
282
282
|
#### From PyPI
|
|
283
283
|
|
|
284
|
-
> [!WARNING]
|
|
285
|
-
> The code is not yet available on PyPI but I will upload a copy shortly
|
|
286
|
-
|
|
287
284
|
```
|
|
288
285
|
pip install elastiqp
|
|
289
286
|
```
|
|
@@ -312,7 +309,7 @@ Note: if using UV, you can directly replace the above `pip` commands with `uv pi
|
|
|
312
309
|
elastiqp::Solution sol = elastiqp::Solve(Q, q, A, b, G, h, penalty);
|
|
313
310
|
// or without equalities: elastiqp::Solve(Q, q, G, h, penalty)
|
|
314
311
|
|
|
315
|
-
//
|
|
312
|
+
// Repeated solves
|
|
316
313
|
elastiqp::Solver solver;
|
|
317
314
|
solver.setup(Q, q, A, b, G, h, penalty);
|
|
318
315
|
while (running) {
|
|
@@ -342,9 +339,6 @@ sol = solver.solve()
|
|
|
342
339
|
|
|
343
340
|
### JAX / PyTorch
|
|
344
341
|
|
|
345
|
-
> [!WARNING]
|
|
346
|
-
> Differentiability support is still in beta, and is not implemented in the default (active set) backend
|
|
347
|
-
|
|
348
342
|
```python
|
|
349
343
|
import jax
|
|
350
344
|
jax.config.update("jax_enable_x64", True)
|
|
@@ -353,9 +347,9 @@ import elastiqp.jax
|
|
|
353
347
|
# Same API as Python, just with elastiqp.jax
|
|
354
348
|
sol = elastiqp.jax.solve(Q, q, G, h, penalty, A=A, b=b)
|
|
355
349
|
|
|
356
|
-
# Compatible with jax.grad and vjp
|
|
350
|
+
# Compatible with jax.grad and vjp
|
|
357
351
|
def loss(q_):
|
|
358
|
-
sol = elastiqp.jax.solve(Q, q_, G, h, penalty, A=A, b=b
|
|
352
|
+
sol = elastiqp.jax.solve(Q, q_, G, h, penalty, A=A, b=b)
|
|
359
353
|
return jnp.sum(sol.x**2)
|
|
360
354
|
|
|
361
355
|
grad_q = jax.grad(loss)(q)
|
|
@@ -379,6 +373,29 @@ _, xs = jax.lax.scan(step, init, qs)
|
|
|
379
373
|
|
|
380
374
|
For runnable Python/JAX/PyTorch examples, see the `examples` folder
|
|
381
375
|
|
|
376
|
+
### C
|
|
377
|
+
|
|
378
|
+
A new experimental C wrapper has recently been added for compatibility with libraries like [CasADi](https://github.com/casadi/casadi)
|
|
379
|
+
|
|
380
|
+
```c
|
|
381
|
+
#include "elastiqp/elastiqp_c.h"
|
|
382
|
+
|
|
383
|
+
ElastiQPProblem qp = {.n = n, .m = m, .p = p, .Q = Q, .q = q, .A = A, .b = b,
|
|
384
|
+
.G = G, .h = h, .penalty = penalty};
|
|
385
|
+
ElastiQPSolution res = {.x = x, .z = z};
|
|
386
|
+
elastiqp_solve_once(&res, &qp, NULL); // NULL: default settings
|
|
387
|
+
|
|
388
|
+
// Repeated solves
|
|
389
|
+
ElastiQPWorkspace* work;
|
|
390
|
+
elastiqp_setup(&work, &qp, NULL);
|
|
391
|
+
while (running) {
|
|
392
|
+
elastiqp_set_q(work, q_k); elastiqp_set_h(work, h_k); elastiqp_set_b(work, b_k);
|
|
393
|
+
elastiqp_set_G(work, G_k);
|
|
394
|
+
if (elastiqp_solve(&res, work) != ELASTIQP_SOLVED) { /* ... */ }
|
|
395
|
+
}
|
|
396
|
+
elastiqp_free(work);
|
|
397
|
+
```
|
|
398
|
+
|
|
382
399
|
## Benchmarks
|
|
383
400
|
|
|
384
401
|
> [!WARNING]
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://github.com/user-attachments/assets/12b43719-2b23-478d-9aaa-5481911741b4" alt="logo" width="400" height="73"></img>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
1
5
|
# ElastiQP
|
|
2
6
|
|
|
3
7
|
[](https://arxiv.org/abs/2609.19080)
|
|
4
8
|
|
|
5
|
-
> [!WARNING]
|
|
6
|
-
> Currently under construction, apologies for any rough edges
|
|
7
|
-
|
|
8
|
-
|
|
9
9
|
An always-feasible QP solver for constrained robot control.
|
|
10
10
|
|
|
11
11
|
ElastiQP solves the following problem:
|
|
@@ -66,9 +66,6 @@ If you've installed with CMake, you can also `find_package(elastiqp)`
|
|
|
66
66
|
|
|
67
67
|
#### From PyPI
|
|
68
68
|
|
|
69
|
-
> [!WARNING]
|
|
70
|
-
> The code is not yet available on PyPI but I will upload a copy shortly
|
|
71
|
-
|
|
72
69
|
```
|
|
73
70
|
pip install elastiqp
|
|
74
71
|
```
|
|
@@ -97,7 +94,7 @@ Note: if using UV, you can directly replace the above `pip` commands with `uv pi
|
|
|
97
94
|
elastiqp::Solution sol = elastiqp::Solve(Q, q, A, b, G, h, penalty);
|
|
98
95
|
// or without equalities: elastiqp::Solve(Q, q, G, h, penalty)
|
|
99
96
|
|
|
100
|
-
//
|
|
97
|
+
// Repeated solves
|
|
101
98
|
elastiqp::Solver solver;
|
|
102
99
|
solver.setup(Q, q, A, b, G, h, penalty);
|
|
103
100
|
while (running) {
|
|
@@ -127,9 +124,6 @@ sol = solver.solve()
|
|
|
127
124
|
|
|
128
125
|
### JAX / PyTorch
|
|
129
126
|
|
|
130
|
-
> [!WARNING]
|
|
131
|
-
> Differentiability support is still in beta, and is not implemented in the default (active set) backend
|
|
132
|
-
|
|
133
127
|
```python
|
|
134
128
|
import jax
|
|
135
129
|
jax.config.update("jax_enable_x64", True)
|
|
@@ -138,9 +132,9 @@ import elastiqp.jax
|
|
|
138
132
|
# Same API as Python, just with elastiqp.jax
|
|
139
133
|
sol = elastiqp.jax.solve(Q, q, G, h, penalty, A=A, b=b)
|
|
140
134
|
|
|
141
|
-
# Compatible with jax.grad and vjp
|
|
135
|
+
# Compatible with jax.grad and vjp
|
|
142
136
|
def loss(q_):
|
|
143
|
-
sol = elastiqp.jax.solve(Q, q_, G, h, penalty, A=A, b=b
|
|
137
|
+
sol = elastiqp.jax.solve(Q, q_, G, h, penalty, A=A, b=b)
|
|
144
138
|
return jnp.sum(sol.x**2)
|
|
145
139
|
|
|
146
140
|
grad_q = jax.grad(loss)(q)
|
|
@@ -164,6 +158,29 @@ _, xs = jax.lax.scan(step, init, qs)
|
|
|
164
158
|
|
|
165
159
|
For runnable Python/JAX/PyTorch examples, see the `examples` folder
|
|
166
160
|
|
|
161
|
+
### C
|
|
162
|
+
|
|
163
|
+
A new experimental C wrapper has recently been added for compatibility with libraries like [CasADi](https://github.com/casadi/casadi)
|
|
164
|
+
|
|
165
|
+
```c
|
|
166
|
+
#include "elastiqp/elastiqp_c.h"
|
|
167
|
+
|
|
168
|
+
ElastiQPProblem qp = {.n = n, .m = m, .p = p, .Q = Q, .q = q, .A = A, .b = b,
|
|
169
|
+
.G = G, .h = h, .penalty = penalty};
|
|
170
|
+
ElastiQPSolution res = {.x = x, .z = z};
|
|
171
|
+
elastiqp_solve_once(&res, &qp, NULL); // NULL: default settings
|
|
172
|
+
|
|
173
|
+
// Repeated solves
|
|
174
|
+
ElastiQPWorkspace* work;
|
|
175
|
+
elastiqp_setup(&work, &qp, NULL);
|
|
176
|
+
while (running) {
|
|
177
|
+
elastiqp_set_q(work, q_k); elastiqp_set_h(work, h_k); elastiqp_set_b(work, b_k);
|
|
178
|
+
elastiqp_set_G(work, G_k);
|
|
179
|
+
if (elastiqp_solve(&res, work) != ELASTIQP_SOLVED) { /* ... */ }
|
|
180
|
+
}
|
|
181
|
+
elastiqp_free(work);
|
|
182
|
+
```
|
|
183
|
+
|
|
167
184
|
## Benchmarks
|
|
168
185
|
|
|
169
186
|
> [!WARNING]
|
|
@@ -43,20 +43,14 @@ inline void apply_options(elastiqp::pdal::Settings& s,
|
|
|
43
43
|
std::optional<double> eps,
|
|
44
44
|
std::optional<int> max_iter,
|
|
45
45
|
std::optional<bool> ruiz) {
|
|
46
|
-
if (eps)
|
|
47
|
-
s.eps_abs = *eps;
|
|
48
|
-
s.eps_duality_gap_abs = *eps;
|
|
49
|
-
}
|
|
46
|
+
if (eps) s.eps_abs = *eps;
|
|
50
47
|
if (max_iter) s.max_outer_iter = *max_iter;
|
|
51
48
|
if (ruiz) s.ruiz = *ruiz;
|
|
52
49
|
}
|
|
53
50
|
inline void apply_options(elastiqp::ipm::Settings& s, std::optional<double> eps,
|
|
54
51
|
std::optional<int> max_iter,
|
|
55
52
|
std::optional<bool> ruiz) {
|
|
56
|
-
if (eps)
|
|
57
|
-
s.eps_abs = *eps;
|
|
58
|
-
s.eps_duality_gap_abs = *eps;
|
|
59
|
-
}
|
|
53
|
+
if (eps) s.eps_abs = *eps;
|
|
60
54
|
if (max_iter) s.max_iter = *max_iter;
|
|
61
55
|
if (ruiz) s.ruiz = *ruiz;
|
|
62
56
|
}
|
|
@@ -305,7 +299,9 @@ NB_MODULE(_core, m) {
|
|
|
305
299
|
.def_rw("check_eq_consistency", &S::check_eq_consistency)
|
|
306
300
|
.def_rw("progress_tol", &S::progress_tol)
|
|
307
301
|
.def_rw("cycle_tol", &S::cycle_tol)
|
|
308
|
-
.def_rw("refactor_tol", &S::refactor_tol)
|
|
302
|
+
.def_rw("refactor_tol", &S::refactor_tol)
|
|
303
|
+
.def_rw("relax_reg", &S::relax_reg)
|
|
304
|
+
.def_rw("relax_factor_retries", &S::relax_factor_retries);
|
|
309
305
|
|
|
310
306
|
using Sv = elastiqp::das::Solver;
|
|
311
307
|
nb::enum_<Sv::RowState>(das, "RowState")
|
|
@@ -317,6 +313,7 @@ NB_MODULE(_core, m) {
|
|
|
317
313
|
auto cls =
|
|
318
314
|
nb::class_<Sv>(das, "Solver", "Dual active-set backend (default)");
|
|
319
315
|
def_common(cls);
|
|
316
|
+
def_relax(cls, 50);
|
|
320
317
|
cls.def("row_state", &Sv::row_state, nb::arg("i"),
|
|
321
318
|
"RowState of inequality row i")
|
|
322
319
|
.def("proximal", &Sv::proximal, "True if Q is proximally shifted")
|
|
@@ -369,45 +366,28 @@ NB_MODULE(_core, m) {
|
|
|
369
366
|
.def_rw("bcl_release_jump", &S::bcl_release_jump)
|
|
370
367
|
.def_rw("bcl_release_jump_horizon", &S::bcl_release_jump_horizon)
|
|
371
368
|
.def_rw("bcl_warm_eta", &S::bcl_warm_eta)
|
|
372
|
-
.def_rw("cold_reset_mu", &S::cold_reset_mu)
|
|
373
|
-
.def_rw("cold_reset_threshold", &S::cold_reset_threshold)
|
|
374
|
-
.def_rw("cold_reset_residual", &S::cold_reset_residual)
|
|
375
|
-
.def_rw("cold_reset_limit", &S::cold_reset_limit)
|
|
376
369
|
.def_rw("safe_guard", &S::safe_guard)
|
|
377
370
|
.def_rw("ruiz", &S::ruiz)
|
|
378
371
|
.def_rw("ruiz_max_iter", &S::ruiz_max_iter)
|
|
379
372
|
.def_rw("ruiz_tol", &S::ruiz_tol)
|
|
380
373
|
.def_rw("ruiz_refresh_ratio", &S::ruiz_refresh_ratio)
|
|
381
|
-
.def_rw("relax_reg", &S::relax_reg)
|
|
382
|
-
.def_rw("relax_warm_budget", &S::relax_warm_budget)
|
|
383
|
-
.def_rw("relax_warm_flip_tol", &S::relax_warm_flip_tol);
|
|
374
|
+
.def_rw("relax_reg", &S::relax_reg);
|
|
384
375
|
|
|
385
376
|
using Sv = elastiqp::pdal::Solver;
|
|
386
377
|
auto cls = nb::class_<Sv>(pdal, "Solver",
|
|
387
378
|
"Primal-dual augmented Lagrangian backend");
|
|
388
379
|
def_common(cls);
|
|
380
|
+
def_relax(cls, 50);
|
|
389
381
|
cls.def(
|
|
390
382
|
"solution", [](const Sv& s) -> Solution { return s.solution(); },
|
|
391
383
|
"Result of the last solve() or relax()")
|
|
392
384
|
.def("factorizations", &Sv::factorizations,
|
|
393
385
|
"KKT factorizations in the last solve()")
|
|
394
|
-
.def("cold_resets", &Sv::cold_resets,
|
|
395
|
-
"BCL cold resets in the last solve()")
|
|
396
386
|
.def("reequilibrate", &Sv::reequilibrate,
|
|
397
387
|
"Recompute the Ruiz scaling in place; solve() does this when "
|
|
398
388
|
"drift exceeds settings.ruiz_refresh_ratio")
|
|
399
389
|
.def("scaling_drift", &Sv::scaling_drift,
|
|
400
390
|
"Largest factor a scaled row/column max-norm has drifted from 1")
|
|
401
|
-
.def(
|
|
402
|
-
"relax",
|
|
403
|
-
[](Sv& s, double kappa, double tol, int max_iter, bool warm)
|
|
404
|
-
-> Solution { return s.relax(kappa, tol, max_iter, warm); },
|
|
405
|
-
nb::arg("kappa"), nb::arg("tol") = 1e-6, nb::arg("max_iter") = 50,
|
|
406
|
-
nb::arg("warm") = true,
|
|
407
|
-
"Move the solve() result to the kappa-relaxed point (s.z = kappa) "
|
|
408
|
-
"for differentiation; the solver's own iterate stays tight. With "
|
|
409
|
-
"warm=True, repeated calls continue from the previous relaxed "
|
|
410
|
-
"point.")
|
|
411
391
|
.def(
|
|
412
392
|
"set_warm_start",
|
|
413
393
|
[](Sv& s, const Eigen::VectorXd& x, const Eigen::VectorXd& y,
|
|
@@ -431,7 +411,6 @@ NB_MODULE(_core, m) {
|
|
|
431
411
|
.def_rw("eps_duality_gap_abs", &S::eps_duality_gap_abs)
|
|
432
412
|
.def_rw("eps_duality_gap_rel", &S::eps_duality_gap_rel)
|
|
433
413
|
.def_rw("max_factor_retries", &S::max_factor_retries)
|
|
434
|
-
.def_rw("warm_start", &S::warm_start)
|
|
435
414
|
.def_rw("check_eq_consistency", &S::check_eq_consistency)
|
|
436
415
|
.def_rw("max_iter", &S::max_iter)
|
|
437
416
|
.def_rw("rho_init", &S::rho_init)
|
|
@@ -444,9 +423,6 @@ NB_MODULE(_core, m) {
|
|
|
444
423
|
.def_rw("reg_finetune_dual_update_threshold",
|
|
445
424
|
&S::reg_finetune_dual_update_threshold)
|
|
446
425
|
.def_rw("tau", &S::tau)
|
|
447
|
-
.def_rw("warm_start_fraction", &S::warm_start_fraction)
|
|
448
|
-
.def_rw("warm_start_min_floor", &S::warm_start_min_floor)
|
|
449
|
-
.def_rw("warm_start_max_floor", &S::warm_start_max_floor)
|
|
450
426
|
.def_rw("ruiz", &S::ruiz)
|
|
451
427
|
.def_rw("ruiz_max_iter", &S::ruiz_max_iter)
|
|
452
428
|
.def_rw("ruiz_tol", &S::ruiz_tol);
|
|
@@ -455,35 +431,8 @@ NB_MODULE(_core, m) {
|
|
|
455
431
|
auto cls = nb::class_<Sv>(ipm, "Solver", "Proximal interior-point backend");
|
|
456
432
|
def_common(cls);
|
|
457
433
|
def_relax(cls, 30);
|
|
458
|
-
cls.def(
|
|
459
|
-
|
|
460
|
-
"Result of the last solve() or relax()")
|
|
461
|
-
.def(
|
|
462
|
-
"set_warm_start",
|
|
463
|
-
[](Sv& s, const Eigen::VectorXd& x, const Eigen::VectorXd& t,
|
|
464
|
-
const Eigen::VectorXd& y, const Eigen::VectorXd& s_t,
|
|
465
|
-
const Eigen::VectorXd& s_ineq, const Eigen::VectorXd& z_t,
|
|
466
|
-
const Eigen::VectorXd& z, double rho, double delta) {
|
|
467
|
-
s.set_warm_start(x, t, y, s_t, s_ineq, z_t, z, rho, delta);
|
|
468
|
-
},
|
|
469
|
-
nb::arg("x"), nb::arg("t"), nb::arg("y"), nb::arg("s_t"),
|
|
470
|
-
nb::arg("s_ineq"), nb::arg("z_t"), nb::arg("z"), nb::kw_only(),
|
|
471
|
-
nb::arg("rho") = 0.0, nb::arg("delta") = 0.0,
|
|
472
|
-
"Seed the next solve() with a strictly interior iterate, used as "
|
|
473
|
-
"given.")
|
|
474
|
-
.def(
|
|
475
|
-
"warm_start_from",
|
|
476
|
-
[](Sv& s, const Solution& sol) { s.warm_start_from(sol); },
|
|
477
|
-
nb::arg("solution"),
|
|
478
|
-
"Seed the next solve() from a Solution; slacks are rebuilt and "
|
|
479
|
-
"floored off the boundary.")
|
|
480
|
-
.def(
|
|
481
|
-
"warm_start_from",
|
|
482
|
-
[](Sv& s, const Eigen::VectorXd& x, const Eigen::VectorXd& y,
|
|
483
|
-
const Eigen::VectorXd& z) { s.warm_start_from(x, y, z); },
|
|
484
|
-
nb::arg("x"), nb::arg("y"), nb::arg("z"),
|
|
485
|
-
"Seed the next solve() from a user-frame (x, y, z); slacks are "
|
|
486
|
-
"rebuilt and floored off the boundary.");
|
|
434
|
+
cls.def("solution", [](const Sv& s) -> Solution { return s.solution(); },
|
|
435
|
+
"Result of the last solve() or relax()");
|
|
487
436
|
}
|
|
488
437
|
|
|
489
438
|
// --- one-shot solve ------------------------------------------------------
|
|
@@ -530,7 +479,7 @@ NB_MODULE(_core, m) {
|
|
|
530
479
|
"method: 'das' (default), 'pdal' or 'ipm'. eps_abs, max_iter (outer "
|
|
531
480
|
"budget) and ruiz override the backend defaults; settings= passes a "
|
|
532
481
|
"full Settings object for that method.\n\n"
|
|
533
|
-
"For gradients use
|
|
482
|
+
"For gradients use a Solver object with relax().";
|
|
534
483
|
|
|
535
484
|
m.def(
|
|
536
485
|
"solve",
|
|
@@ -585,11 +534,6 @@ NB_MODULE(_core, m) {
|
|
|
585
534
|
bool ruiz, double target_kappa, const std::string& method) {
|
|
586
535
|
const Method mth = parse_method(method);
|
|
587
536
|
const bool want_relax = target_kappa > 0 && h.size() > 0;
|
|
588
|
-
if (want_relax && mth == Method::kDAS) {
|
|
589
|
-
throw std::invalid_argument(
|
|
590
|
-
"method='das' has no kappa relaxation (not differentiable); "
|
|
591
|
-
"use method='pdal' or 'ipm' for gradients");
|
|
592
|
-
}
|
|
593
537
|
Solution sol, rsol;
|
|
594
538
|
// Gradient accuracy is set by the relax residual, so cap it at 1e-6.
|
|
595
539
|
const double rtol = std::min(eps_abs, 1e-6);
|
|
@@ -598,7 +542,7 @@ NB_MODULE(_core, m) {
|
|
|
598
542
|
apply_options(solver.settings, eps_abs, max_iter, ruiz);
|
|
599
543
|
solver.setup(Q, q, A, b, G, h, penalty);
|
|
600
544
|
sol = solver.solve();
|
|
601
|
-
rsol = sol;
|
|
545
|
+
rsol = want_relax ? solver.relax(target_kappa, rtol, 50) : sol;
|
|
602
546
|
} else if (mth == Method::kPDAL) {
|
|
603
547
|
elastiqp::pdal::Solver solver;
|
|
604
548
|
apply_options(solver.settings, eps_abs, max_iter, ruiz);
|
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
// ElastiqpSolve: (Q, q, A, b, G, h, penalty)
|
|
5
5
|
// -> (x, t, y, z_t, z, xr, tr, yr, z_t_r, z_r, info)
|
|
6
6
|
// ElastiqpSolveWarm: (Q, q, A, b, G, h, penalty, x0, y0, z0)
|
|
7
|
-
// -> (x, t, y, z_t, z, info)
|
|
7
|
+
// -> (x, t, y, z_t, z, info) (das and pdal only)
|
|
8
8
|
//
|
|
9
9
|
// method: 0 = das, 1 = pdal, 2 = ipm. max_iter is the backend's outer budget.
|
|
10
10
|
// The *r block is the kappa-relaxed point the Python wrappers differentiate
|
|
11
|
-
// at; it copies the tight solution when target_kappa <= 0
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// equalities.
|
|
11
|
+
// at; it copies the tight solution when target_kappa <= 0. info = [converged,
|
|
12
|
+
// iters, relax_converged], without the last entry for the warm handler. A has 0
|
|
13
|
+
// rows and b 0 entries when there are no equalities.
|
|
15
14
|
|
|
16
15
|
#include <algorithm>
|
|
17
16
|
#include <cstdint>
|
|
@@ -46,7 +45,6 @@ template <>
|
|
|
46
45
|
void apply_options(elastiqp::pdal::Solver& solver, double eps_abs,
|
|
47
46
|
int64_t max_iter, int64_t ruiz) {
|
|
48
47
|
solver.settings.eps_abs = eps_abs;
|
|
49
|
-
solver.settings.eps_duality_gap_abs = eps_abs;
|
|
50
48
|
solver.settings.max_outer_iter = static_cast<int>(max_iter);
|
|
51
49
|
solver.settings.ruiz = ruiz != 0;
|
|
52
50
|
}
|
|
@@ -54,22 +52,12 @@ template <>
|
|
|
54
52
|
void apply_options(elastiqp::ipm::Solver& solver, double eps_abs,
|
|
55
53
|
int64_t max_iter, int64_t ruiz) {
|
|
56
54
|
solver.settings.eps_abs = eps_abs;
|
|
57
|
-
solver.settings.eps_duality_gap_abs = eps_abs;
|
|
58
55
|
solver.settings.max_iter = static_cast<int>(max_iter);
|
|
59
56
|
solver.settings.ruiz = ruiz != 0;
|
|
60
57
|
}
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const VectorXd& z0) {
|
|
65
|
-
if constexpr (std::is_same_v<SolverT, elastiqp::ipm::Solver>) {
|
|
66
|
-
solver.warm_start_from(x0, y0, z0); // reconstructs + floors the slacks
|
|
67
|
-
} else {
|
|
68
|
-
solver.set_warm_start(x0, y0, z0);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// x0 != nullptr seeds the solve from (x0, y0, z0).
|
|
59
|
+
// x0 != nullptr seeds the solve from (x0, y0, z0); the IPM has no warm
|
|
60
|
+
// start (dispatch rejects it).
|
|
73
61
|
template <typename SolverT>
|
|
74
62
|
void run_solver(double eps_abs, int64_t max_iter, int64_t ruiz,
|
|
75
63
|
double target_kappa, int64_t n, int64_t m, int64_t p,
|
|
@@ -83,19 +71,18 @@ void run_solver(double eps_abs, int64_t max_iter, int64_t ruiz,
|
|
|
83
71
|
solver.setup(MapMatrix(Q, n, n), MapVector(q, n), MapMatrix(A, m, n),
|
|
84
72
|
MapVector(b, m), MapMatrix(G, p, n), MapVector(h, p),
|
|
85
73
|
MapVector(penalty, p));
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
74
|
+
if constexpr (!std::is_same_v<SolverT, elastiqp::ipm::Solver>) {
|
|
75
|
+
if (x0 != nullptr) {
|
|
76
|
+
solver.set_warm_start(VectorXd(MapVector(x0, n)),
|
|
77
|
+
VectorXd(MapVector(y0, m)),
|
|
78
|
+
VectorXd(MapVector(z0, p)));
|
|
79
|
+
}
|
|
89
80
|
}
|
|
90
81
|
sol = solver.solve();
|
|
91
82
|
// Gradient accuracy is set by the relax residual, so cap it at 1e-6.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
rsol = (target_kappa > 0 && p > 0)
|
|
96
|
-
? solver.relax(target_kappa, std::min(eps_abs, 1e-6), 50)
|
|
97
|
-
: sol;
|
|
98
|
-
}
|
|
83
|
+
rsol = (target_kappa > 0 && p > 0)
|
|
84
|
+
? solver.relax(target_kappa, std::min(eps_abs, 1e-6), 50)
|
|
85
|
+
: sol;
|
|
99
86
|
}
|
|
100
87
|
|
|
101
88
|
ffi::Error dispatch(int64_t method, double eps_abs, int64_t max_iter,
|
|
@@ -107,11 +94,6 @@ ffi::Error dispatch(int64_t method, double eps_abs, int64_t max_iter,
|
|
|
107
94
|
elastiqp::Solution& rsol) {
|
|
108
95
|
switch (method) {
|
|
109
96
|
case 0:
|
|
110
|
-
if (target_kappa > 0 && p > 0) {
|
|
111
|
-
return ffi::Error(ffi::ErrorCode::kInvalidArgument,
|
|
112
|
-
"method='das' has no kappa relaxation (not "
|
|
113
|
-
"differentiable); use method='pdal' or 'ipm'");
|
|
114
|
-
}
|
|
115
97
|
run_solver<elastiqp::das::Solver>(eps_abs, max_iter, ruiz, target_kappa,
|
|
116
98
|
n, m, p, Q, q, A, b, G, h, penalty, x0,
|
|
117
99
|
y0, z0, sol, rsol);
|
|
@@ -122,6 +104,11 @@ ffi::Error dispatch(int64_t method, double eps_abs, int64_t max_iter,
|
|
|
122
104
|
y0, z0, sol, rsol);
|
|
123
105
|
return ffi::Error::Success();
|
|
124
106
|
case 2:
|
|
107
|
+
if (x0 != nullptr) {
|
|
108
|
+
return ffi::Error(ffi::ErrorCode::kInvalidArgument,
|
|
109
|
+
"method='ipm' has no warm start; use method='das' "
|
|
110
|
+
"or 'pdal'");
|
|
111
|
+
}
|
|
125
112
|
run_solver<elastiqp::ipm::Solver>(eps_abs, max_iter, ruiz, target_kappa,
|
|
126
113
|
n, m, p, Q, q, A, b, G, h, penalty, x0,
|
|
127
114
|
y0, z0, sol, rsol);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@PACKAGE_INIT@
|
|
2
|
+
|
|
3
|
+
include(CMakeFindDependencyMacro)
|
|
4
|
+
find_dependency(Eigen3)
|
|
5
|
+
|
|
6
|
+
include("${CMAKE_CURRENT_LIST_DIR}/elastiqpTargets.cmake")
|
|
7
|
+
|
|
8
|
+
# The static C interface requires the C++ linker and runtime.
|
|
9
|
+
if(TARGET elastiqp::elastiqp_c)
|
|
10
|
+
get_target_property(_elastiqp_c_type elastiqp::elastiqp_c TYPE)
|
|
11
|
+
get_property(_elastiqp_languages GLOBAL PROPERTY ENABLED_LANGUAGES)
|
|
12
|
+
if(_elastiqp_c_type STREQUAL "STATIC_LIBRARY"
|
|
13
|
+
AND NOT "CXX" IN_LIST _elastiqp_languages)
|
|
14
|
+
enable_language(CXX)
|
|
15
|
+
endif()
|
|
16
|
+
endif()
|
|
17
|
+
|
|
18
|
+
check_required_components(elastiqp)
|
|
@@ -3,7 +3,14 @@ include(CMakePackageConfigHelpers)
|
|
|
3
3
|
|
|
4
4
|
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
set(_elastiqp_install_targets elastiqp)
|
|
7
|
+
if(ELASTIQP_BUILD_C)
|
|
8
|
+
list(APPEND _elastiqp_install_targets elastiqp_c)
|
|
9
|
+
endif()
|
|
10
|
+
install(TARGETS ${_elastiqp_install_targets} EXPORT elastiqpTargets
|
|
11
|
+
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
12
|
+
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
13
|
+
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
|
7
14
|
install(EXPORT elastiqpTargets
|
|
8
15
|
NAMESPACE elastiqp::
|
|
9
16
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/elastiqp)
|
|
@@ -121,8 +121,7 @@ def main():
|
|
|
121
121
|
print("\nNote that this speedup can be significant on robot control problems!")
|
|
122
122
|
|
|
123
123
|
print("\n--- Differentiability ---")
|
|
124
|
-
print("
|
|
125
|
-
print("kappa-smoothed differentiation point; see the JAX and PyTorch examples")
|
|
124
|
+
print("For smooth derivative evaluation, see the JAX and PyTorch examples")
|
|
126
125
|
|
|
127
126
|
|
|
128
127
|
if __name__ == "__main__":
|
|
@@ -68,9 +68,6 @@ def main():
|
|
|
68
68
|
print("\nAs in solvers like qpax, we relax the solution to some kappa > 0")
|
|
69
69
|
print("smoothed value for well-conditioned gradients")
|
|
70
70
|
|
|
71
|
-
print("\nNote: Currently, differentiation is only supported in the `ipm`")
|
|
72
|
-
print("and `pdal` backends")
|
|
73
|
-
|
|
74
71
|
def loss(q_, kappa=1e-6):
|
|
75
72
|
sol = elastiqp.jax.solve(
|
|
76
73
|
Q,
|
|
@@ -80,7 +77,6 @@ def main():
|
|
|
80
77
|
penalty,
|
|
81
78
|
A=A,
|
|
82
79
|
b=b,
|
|
83
|
-
method="pdal",
|
|
84
80
|
target_kappa=kappa,
|
|
85
81
|
eps_abs=1e-10,
|
|
86
82
|
)
|
|
@@ -108,7 +104,7 @@ def main():
|
|
|
108
104
|
def x0(h0, kappa):
|
|
109
105
|
h_mod = h.at[0].set(h0)
|
|
110
106
|
return elastiqp.jax.solve(
|
|
111
|
-
Q, q, G, h_mod, penalty, A=A, b=b,
|
|
107
|
+
Q, q, G, h_mod, penalty, A=A, b=b, target_kappa=kappa
|
|
112
108
|
).x[0]
|
|
113
109
|
|
|
114
110
|
print("\nTo see this, let's compare d(x0)/d(h0): the sensitivity of x0 to its")
|