nashopt 1.0.0__py3-none-any.whl → 1.0.1__py3-none-any.whl
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.
- {nashopt-1.0.0.dist-info → nashopt-1.0.1.dist-info}/METADATA +15 -8
- nashopt-1.0.1.dist-info/RECORD +6 -0
- {nashopt-1.0.0.dist-info → nashopt-1.0.1.dist-info}/WHEEL +1 -1
- nashopt.py +24 -25
- nashopt-1.0.0.dist-info/RECORD +0 -6
- {nashopt-1.0.0.dist-info → nashopt-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {nashopt-1.0.0.dist-info → nashopt-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nashopt
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: NashOpt - A Python Library for Computing Generalized Nash Equilibria and Solving Game-Design and Game-Theoretic Control Problems.
|
|
5
5
|
Author-email: Alberto Bemporad <alberto.bemporad@imtlucca.it>
|
|
6
6
|
Project-URL: Homepage, https://github.com/bemporad/nashopt
|
|
@@ -26,6 +26,9 @@ Dynamic: license-file
|
|
|
26
26
|
|
|
27
27
|
This repository includes a library for solving different classes of nonlinear **Generalized Nash Equilibrium Problems** (GNEPs). The decision variables and Lagrange multipliers that jointly satisfy the KKT conditions for all agents are determined by solving a nonlinear least-squares problem. If a zero residual is obtained, this corresponds to a potential generalized Nash equilibrium, a property that can be verified by evaluating the individual **best responses**. For the special case of **Linear-Quadratic Games**, one or more equilibria are obtained by solving mixed-integer linear programming problems. The package can also solve **game-design** problems by optimizing the parameters of a **multiparametric GNEP** by box-constrained nonlinear optimization, as well as **game-theoretic control** problems, such as **Linear Quadratic Regulation** and **Model Predictive Control** problems.
|
|
28
28
|
|
|
29
|
+
For more details about the mathematical formulations implemented in the library, see the
|
|
30
|
+
<a href="https://arxiv.org/abs/2512.23636">arXiv preprint 2512.23636</a>.
|
|
31
|
+
|
|
29
32
|
---
|
|
30
33
|
## Installation
|
|
31
34
|
|
|
@@ -310,6 +313,7 @@ of finding a vector $p$ (if one exists) such that $x^\star\approx x_{\textrm des
|
|
|
310
313
|
$$J(x^\star,p)=\|x^\star-x_{\rm des}\|_2^2.$$
|
|
311
314
|
|
|
312
315
|
We solve the game-design problem as
|
|
316
|
+
|
|
313
317
|
$$
|
|
314
318
|
\begin{aligned}
|
|
315
319
|
\min_{z,p}\quad & J(x,p) + \frac{\rho}{2}\|R(z,p)\|_2^2\\
|
|
@@ -435,13 +439,14 @@ You can retrieve extra information after solving the Nash equilibrium problem, s
|
|
|
435
439
|
|
|
436
440
|
### Game-Theoretic Model Predictive Control
|
|
437
441
|
We now want to make the output vector $y(t)$ of the system track a given setpoint $r(t)$.
|
|
438
|
-
Each agent optimizes a sequence of input increments
|
|
442
|
+
Each agent optimizes a sequence of input increments
|
|
443
|
+
$\Delta u_{i,k}$, $k=0,\ldots,T-1$,
|
|
444
|
+
over a prediction horizon of $T$ steps, where $\Delta u_k=u_k-u_{k-1}$, by minimizing
|
|
439
445
|
|
|
440
446
|
$$
|
|
441
|
-
\
|
|
442
|
-
\
|
|
443
|
-
+ \Delta u_{i,k}^\top Q_{\Delta u,i}\Delta u_{i,k}
|
|
444
|
-
+ q_{\epsilon,i}^\top \epsilon_i
|
|
447
|
+
q_{\epsilon,i}^\top \epsilon_i +
|
|
448
|
+
\sum_{k=0}^{T-1} (y_{k+1}-r(t))^\top Q_i (y_{k+1}-r(t))
|
|
449
|
+
+ \Delta u_{i,k}^\top Q_{\Delta u,i}\Delta u_{i,k}
|
|
445
450
|
$$
|
|
446
451
|
|
|
447
452
|
$$
|
|
@@ -455,6 +460,7 @@ $$
|
|
|
455
460
|
& i=1,\ldots,N,\ k=0,\ldots,T-1.
|
|
456
461
|
\end{array}
|
|
457
462
|
$$
|
|
463
|
+
|
|
458
464
|
where $Q_i\succeq 0$, $Q_{\Delta u,i}\succeq 0$ and $\epsilon_i\geq 0$ is a slack variable
|
|
459
465
|
used to soften shared output constraints (with linear penalty $q_{\epsilon,i}\geq 0$). Each agent's MPC problem can be simplified by imposing the constraints only on a shorter constraint horizon of $T_c<T$ steps.
|
|
460
466
|
|
|
@@ -509,10 +515,11 @@ sol = nash_mpc.solve(x0, u1, ref, ..., solver='gurobi')
|
|
|
509
515
|
## Citation
|
|
510
516
|
|
|
511
517
|
```
|
|
512
|
-
@
|
|
518
|
+
@article{NashOpt,
|
|
513
519
|
author={A. Bemporad},
|
|
514
520
|
title={{NashOpt}: A {Python} Library for Computing Generalized {Nash} Equilibria and Game Design},
|
|
515
|
-
|
|
521
|
+
journal = {arXiv preprint 2512.23636},
|
|
522
|
+
note = {\url{https://github.com/bemporad/nashopt}},
|
|
516
523
|
year=2025
|
|
517
524
|
}
|
|
518
525
|
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
nashopt.py,sha256=i0cBKaY7QUBKI2ENvJURB5Oc7h1UgCmSocw_ViChNKI,107366
|
|
2
|
+
nashopt-1.0.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
3
|
+
nashopt-1.0.1.dist-info/METADATA,sha256=pUZqIWvUwxg2UqfzQ2qr3SsQwJjDLkZFJF04rI3Y6CI,18333
|
|
4
|
+
nashopt-1.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
5
|
+
nashopt-1.0.1.dist-info/top_level.txt,sha256=NuR1yd9NPYwmiknuGNUFNSw8tKTzPpaspAD7VtTvaFk,8
|
|
6
|
+
nashopt-1.0.1.dist-info/RECORD,,
|
nashopt.py
CHANGED
|
@@ -1935,8 +1935,8 @@ class NashLQR():
|
|
|
1935
1935
|
|
|
1936
1936
|
subject to dynamics x(k+1) = (A -B_{-i}K_{-i})x(k) + B_i u_i(k).
|
|
1937
1937
|
|
|
1938
|
-
The
|
|
1939
|
-
|
|
1938
|
+
The Nash equilibrium is found by letting agent i minimize the difference between K_i and the LQR gain K_i for the dynamics (A -B_{-i}K_{-i}, B_i). The LQR gain is computed approximately by evaluating the LQ cost over "dare_iters" time steps. The method is initialized from the centralized LQR solution with matrix Q=sum(Q_i) and R=block_diag(R_1, ..., R_N), obtained by "dare_iters" Riccati iterations.
|
|
1939
|
+
|
|
1940
1940
|
(C) 2025 Alberto Bemporad, December 20, 2025
|
|
1941
1941
|
"""
|
|
1942
1942
|
self.sizes = sizes
|
|
@@ -1978,7 +1978,7 @@ class NashLQR():
|
|
|
1978
1978
|
not_i.append(list(range(sum_i[i-1])) + list(range(sum_i[i], nu)))
|
|
1979
1979
|
self.not_i = not_i
|
|
1980
1980
|
self.ii = [list(range(sum_i[i]-sizes[i], sum_i[i])) for i in range(N)]
|
|
1981
|
-
|
|
1981
|
+
|
|
1982
1982
|
def solve(self, **kwargs):
|
|
1983
1983
|
"""Solve the Nash-LQR game.
|
|
1984
1984
|
|
|
@@ -1988,6 +1988,7 @@ class NashLQR():
|
|
|
1988
1988
|
"""
|
|
1989
1989
|
|
|
1990
1990
|
dare_iters = self.dare_iters
|
|
1991
|
+
sol = SimpleNamespace()
|
|
1991
1992
|
|
|
1992
1993
|
@jax.jit
|
|
1993
1994
|
def jax_dare(A, B, Q, R):
|
|
@@ -2027,6 +2028,22 @@ class NashLQR():
|
|
|
2027
2028
|
K_final = get_K(X_final, A, B, R)
|
|
2028
2029
|
return X_final, K_final
|
|
2029
2030
|
|
|
2031
|
+
# Initial guess = centralized LQR
|
|
2032
|
+
nu = self.nu
|
|
2033
|
+
bigR = block_diag(*self.R)
|
|
2034
|
+
bigQ = sum(self.Q[i] for i in range(self.N))
|
|
2035
|
+
_, K_cen = jax_dare(self.A, self.B, bigQ, bigR)
|
|
2036
|
+
# # Check for comparison using python control library
|
|
2037
|
+
# from control import dare
|
|
2038
|
+
# P1, _, K1 = dare(A, B, bigQ, bigR)
|
|
2039
|
+
# print("Max difference between LQR gains: ", np.max(np.abs(K_cen - K1)))
|
|
2040
|
+
# print("Max difference between Riccati matrices: ", np.max(np.abs(P - P1)))
|
|
2041
|
+
|
|
2042
|
+
sol.K_centralized = K_cen
|
|
2043
|
+
self.jax_dare = jax_dare # store for possible later use outside solve()
|
|
2044
|
+
|
|
2045
|
+
print("Solving Nash-LQR problem ... ", end='')
|
|
2046
|
+
|
|
2030
2047
|
@partial(jax.jit, static_argnums=(1,)) # i is static
|
|
2031
2048
|
def lqr_fun(K_flat, i, A, B, Q, R):
|
|
2032
2049
|
K = K_flat.reshape(self.nu, self.nx)
|
|
@@ -2039,37 +2056,19 @@ class NashLQR():
|
|
|
2039
2056
|
f = []
|
|
2040
2057
|
for i in range(self.N):
|
|
2041
2058
|
f.append(partial(lqr_fun, i=i, A=self.A,
|
|
2042
|
-
|
|
2059
|
+
B=self.B, Q=self.Q, R=self.R))
|
|
2043
2060
|
|
|
2044
2061
|
# each agent's variable is K_i (size[i] x nx) flattened
|
|
2045
2062
|
sizes = [self.sizes[i]*self.nx for i in range(self.N)]
|
|
2046
2063
|
gnep = GNEP(sizes, f=f)
|
|
2047
2064
|
|
|
2048
|
-
# Initial guess = centralized LQR
|
|
2049
|
-
nu = self.nu
|
|
2050
|
-
bigR = block_diag(*self.R)
|
|
2051
|
-
bigQ = sum(self.Q[i] for i in range(self.N))
|
|
2052
|
-
_, K_cen = jax_dare(self.A, self.B, bigQ, bigR)
|
|
2053
|
-
|
|
2054
|
-
# # Check for comparison using python control library
|
|
2055
|
-
# from control import dare
|
|
2056
|
-
# P1, _, K1 = dare(A, B, bigQ, bigR)
|
|
2057
|
-
# print("Max difference between LQR gains: ", np.max(np.abs(K_cen - K1)))
|
|
2058
|
-
# print("Max difference between Riccati matrices: ", np.max(np.abs(P - P1)))
|
|
2059
|
-
|
|
2060
|
-
print("Solving Nash-LQR problem ... ", end='')
|
|
2061
|
-
|
|
2062
2065
|
K0 = K_cen.flatten()
|
|
2063
|
-
|
|
2064
|
-
K_Nash, residual, stats =
|
|
2066
|
+
sol_residual = gnep.solve(x0=K0, **kwargs)
|
|
2067
|
+
K_Nash, residual, stats = sol_residual.x, sol_residual.res, sol_residual.stats
|
|
2065
2068
|
print("done.")
|
|
2066
|
-
K_Nash = K_Nash.reshape(nu, self.nx)
|
|
2067
|
-
|
|
2068
|
-
sol = SimpleNamespace()
|
|
2069
|
-
sol.K_Nash = K_Nash
|
|
2069
|
+
sol.K_Nash = K_Nash.reshape(nu, self.nx)
|
|
2070
2070
|
sol.residual = residual
|
|
2071
2071
|
sol.stats = stats
|
|
2072
|
-
sol.K_centralized = K_cen
|
|
2073
2072
|
return sol
|
|
2074
2073
|
|
|
2075
2074
|
|
nashopt-1.0.0.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
nashopt.py,sha256=-yiXFuMCJishfgpZS3CBCVwwhnEdobpmdNOr-xvt8XI,106990
|
|
2
|
-
nashopt-1.0.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
3
|
-
nashopt-1.0.0.dist-info/METADATA,sha256=5r73xvkjYGjK7GDhcWLMYTRv62Bs8C3-xoC11fe0q98,18169
|
|
4
|
-
nashopt-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
nashopt-1.0.0.dist-info/top_level.txt,sha256=NuR1yd9NPYwmiknuGNUFNSw8tKTzPpaspAD7VtTvaFk,8
|
|
6
|
-
nashopt-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|