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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nashopt
3
- Version: 1.0.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 $\{\Delta u_{i,k}\}_{k=0}^{T-1}$ over a prediction horizon of $T$ steps, where $\Delta u_k=u_k-u_{k-1}$, by solving:
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
- \Delta u_i,\epsilon_i \in\arg\min \sum_{k=0}^{T-1}
442
- \left( (y_{k+1}-r(t))^\top Q_i (y_{k+1}-r(t))
443
- + \Delta u_{i,k}^\top Q_{\Delta u,i}\Delta u_{i,k}\right)
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
- @misc{nashopt,
518
+ @article{NashOpt,
513
519
  author={A. Bemporad},
514
520
  title={{NashOpt}: A {Python} Library for Computing Generalized {Nash} Equilibria and Game Design},
515
- howpublished = {\url{https://github.com/bemporad/nashopt}},
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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 LQR cost is solved by approximating the infinite-horizon cost by a finite-horizon cost using "dare_iters" fixed-point iterations.
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
- B=self.B, Q=self.Q, R=self.R))
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
- sol = gnep.solve(x0=K0, **kwargs)
2064
- K_Nash, residual, stats = sol.x, sol.res, sol.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
 
@@ -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,,