diff --git a/README.md b/README.md index ce44044..a0aeebe 100644 --- a/README.md +++ b/README.md @@ -25,17 +25,30 @@ conda install -n base -c conda-forge ipopt=3.11.1 pkg-config - If you have any installation issues, [see the docs](https://docs.astral.sh/uv/getting-started/installation/) for troubleshooting 2. Install optimizer dependencies. - On MacOS: `brew install ipopt pkg-config` - - Linux: `sudo apt-get install coinor-libipopt-dev pkg-config` + - Linux: `sudo apt-get install coinor-libipopt-dev libblas-dev liblapack-dev pkg-config` - Windows: See note above if you have not previously installed Ipopt or JAX 3. Synchronize the environment ```bash uv sync ``` -4. Finally, you will want to make sure you activate the python environment each time you use it. +4. **Linux only — obtain the `ipopt` executable.** On Linux the `coinor-libipopt-dev` package provides the Ipopt *library* (needed to build `cyipopt` during `uv sync`) but **not** the `ipopt` command-line executable, which Pyomo's `SolverFactory("ipopt")` invokes to solve the models. The simplest way to get a prebuilt binary is via the IDAES extensions: +```bash + uv run --with idaes-pse idaes get-extensions +``` + - This installs `ipopt` (and related solvers) into `~/.idaes/bin`. Add that directory to your `PATH` so Pyomo can find it, e.g. `export PATH="$HOME/.idaes/bin:$PATH"`. + - On MacOS (`brew install ipopt`) and Windows (`conda install ... ipopt`) this step is unnecessary because those installs already include the `ipopt` executable. +5. Finally, you will want to make sure you activate the python environment each time you use it. - In VS Code you can activate the default environment with `>Python: Select Interpreter` to be the `.venv` local to the directory - If the debugger isn't working in that case, sometimes setting the vscode `terminal.integrated.shellIntegration.enabled: true` in the settings can help - Outside of vscode, a simple, platform specific CLI line will [activate .venv](https://docs.python.org/3/tutorial/venv.html#creating-virtual-environments) in your terminal. +## Optimization solvers +The models use two solver paths: + +- **Convex / DCP models** (e.g. asset pricing, `asset_pricing_matern_cvxpy.py`) are solved with [`cvxpy`](https://www.cvxpy.org/) using open-source solvers — **Clarabel, OSQP, SCS, and HiGHS**. These require **no extra setup**: `cvxpy` bundles all four as dependencies, so `uv sync` installs them automatically (HiGHS arrives via `highspy`). +- **Nonconvex / NLP models** (neoclassical growth, neoclassical human capital, optimal advertising, concave-convex growth — the `*_matern_cvxpy.py` files) are solved through `cvxpy`'s DNLP interface (`prob.solve(nlp=True, ...)`), which hands the smooth nonlinear program to a nonlinear solver. The default is **UNO**, via [`unopy`](https://pypi.org/project/unopy/) — a self-contained binary wheel that statically bundles `libuno`, so `uv sync` installs it with no extra setup. This includes the concave-convex model: its exact complementarity (MPCC) reformulation uses a flat warm start with the marginal product seeded on the *active* production branch (consistent with the complementarity constraints), and UNO resolves the bistable threshold structure. **IPOPT** (via `cyipopt`, also in-process) stays selectable. No solver binary on `PATH` is needed for this path. The figure/table scripts take `--implementation cvxpy|pyomo` (default `cvxpy`), including the concave-convex **threshold** figure. +- **Pyomo path (alternative).** The same nonconvex models also ship a `pyomo` implementation (e.g. `neoclassical_growth_matern.py`, selected with `--implementation pyomo`) that drives the `ipopt` *binary* on `PATH` — see step 4 above (the IDAES `ipopt` executable on Linux; `brew`/`conda` on MacOS/Windows). UNO is used only through `cvxpy` (the `unopy` wheel above), so no standalone solver binary needs to be installed for it. + **Troubleshooting**: - If you receive JAX errors about DLL load failures, you may need to update [https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#visual-studio-2015-2017-2019-and-2022) - See notes above on Windows Ipopt installation challenges diff --git a/asset_pricing_matern_cvxpy.py b/asset_pricing_matern_cvxpy.py new file mode 100644 index 0000000..2f38831 --- /dev/null +++ b/asset_pricing_matern_cvxpy.py @@ -0,0 +1,114 @@ +import time +import jax +import jax.numpy as jnp +import numpy as np +import cvxpy as cp +import jsonargparse +from jax import config +from kernels import integrated_matern_kernel_matrices +from asset_pricing_benchmark import mu_f_array +from typing import List, Optional + +config.update("jax_enable_x64", True) + +# Pyomo-free DCP port of asset_pricing_matern. The model is a convex QP: +# minimize alpha' K alpha +# s.t. K alpha == r (mu_0 + K_tilde alpha) - x, mu_0 >= 0 +# solver_type selects an open-source cvxpy backend. OSQP (native QP) is the +# default; CLARABEL is the more robust choice for ill-conditioned kernels +# (large nu/rho/N), where OSQP's ADMM can report infeasibility. +SOLVER_OPTIONS = { + "OSQP": (cp.OSQP, dict(eps_abs=1e-12, eps_rel=1e-12, max_iter=5000)), + "CLARABEL": ( + cp.CLARABEL, + dict(tol_gap_abs=1e-12, tol_gap_rel=1e-12, tol_feas=1e-12), + ), + "SCS": (cp.SCS, dict(eps=1e-9, max_iters=20000)), + "HIGHS": (cp.HIGHS, dict(primal_feasibility_tolerance=1e-4)), +} + + +def asset_pricing_matern_cvxpy( + r: float = 0.1, + c: float = 0.02, + g: float = -0.2, + x_0: float = 0.01, + nu: float = 0.5, + sigma: float = 1.0, + rho: float = 10, + solver_type: str = "OSQP", + train_T: float = 40.0, + train_points: int = 41, + test_T: float = 50.0, + test_points: int = 100, + train_points_list: Optional[List[float]] = None, + verbose: bool = False, +): + # if passing in `train_points_list` then doesn't use a grid. Otherwise, uses linspace + if train_points_list is None: + train_data = jnp.linspace(0, train_T, train_points) + else: + train_data = jnp.array(train_points_list) + test_data = jnp.linspace(0, test_T, test_points) + + # Construct kernel matrices. cvxpy needs numpy (not jax) arrays. + N = len(train_data) + K, K_tilde = integrated_matern_kernel_matrices( + train_data, train_data, nu, sigma, rho + ) + K = np.asarray((K + K.T) / 2) # symmetrize -> exactly PSD for quad_form + K_tilde = np.asarray(K_tilde) + x = (x_0 + c / g) * np.exp(g * np.asarray(train_data)) - c / g + + # Solve the QP. psd_wrap asserts the (provably PSD) Gram matrix K so cvxpy + # skips its O(N^3) PSD certification, which both dominates canonicalization + # time and fails to converge for ill-conditioned K. + alpha_mu = cp.Variable(N) + mu_0 = cp.Variable(nonneg=True) + prob = cp.Problem( + cp.Minimize(cp.quad_form(alpha_mu, cp.psd_wrap(K))), + [K @ alpha_mu == r * (mu_0 + K_tilde @ alpha_mu) - x], + ) + solver, options = SOLVER_OPTIONS[solver_type] + start = time.perf_counter() + prob.solve(solver=solver, verbose=verbose, **options) + print(f"elapsed solve(s) = {time.perf_counter() - start}") + if prob.status not in ("optimal", "optimal_inaccurate"): + print(f"solver status: {prob.status}") + + alpha_mu = jnp.array(alpha_mu.value) + mu_0 = float(mu_0.value) + + # Interpolator using training data + @jax.jit + def kernel_solution(test_data): + # pointwise comparison test_data to train_data + _, K_tilde_test = integrated_matern_kernel_matrices( + test_data, train_data, nu, sigma, rho + ) + mu_test = mu_0 + K_tilde_test @ alpha_mu + return mu_test + + # Generate test_data and compare to the benchmark + mu_benchmark = mu_f_array(test_data, c, g, r, x_0) + mu_test = kernel_solution(test_data) + + mu_rel_error = jnp.abs(mu_benchmark - mu_test) / mu_benchmark + print( + f"solve_time(s) = {prob.solver_stats.solve_time}, E(|rel_error(p)|) = {mu_rel_error.mean()}" + ) + return { + "t_train": train_data, + "t_test": test_data, + "p_test": mu_test, + "p_benchmark": mu_benchmark, + "p_rel_error": mu_rel_error, + "alpha": alpha_mu, + "p_0": mu_0, + "solve_time": prob.solver_stats.solve_time, + "kernel_solution": kernel_solution, # interpolator + } + + +if __name__ == "__main__": + jsonargparse.CLI(asset_pricing_matern_cvxpy) diff --git a/figures_asset_pricing.py b/figures_asset_pricing.py index 845982e..daed9d9 100644 --- a/figures_asset_pricing.py +++ b/figures_asset_pricing.py @@ -1,7 +1,9 @@ import jax.numpy as jnp import matplotlib.pyplot as plt import os +import jsonargparse from asset_pricing_matern import asset_pricing_matern +from asset_pricing_matern_cvxpy import asset_pricing_matern_cvxpy from mpl_toolkits.axes_grid1.inset_locator import ( zoomed_inset_axes, @@ -126,8 +128,16 @@ def plot_asset_pricing( plt.savefig(output_path, format="pdf") -# Plots with various parameters -sol_matern = asset_pricing_matern() -plot_asset_pricing( - sol_matern, "figures/asset_pricing_contiguous.pdf" -) +# Plots with various parameters. implementation selects the solve backend: +# "cvxpy" (default, open-source DCP/QP solvers) or "pyomo" (Ipopt). +def main(implementation: str = "cvxpy"): + solve = { + "cvxpy": asset_pricing_matern_cvxpy, + "pyomo": asset_pricing_matern, + }[implementation] + sol_matern = solve() + plot_asset_pricing(sol_matern, "figures/asset_pricing_contiguous.pdf") + + +if __name__ == "__main__": + jsonargparse.CLI(main) diff --git a/figures_neoclassical_growth_baseline.py b/figures_neoclassical_growth_baseline.py index 9436213..84f00f0 100644 --- a/figures_neoclassical_growth_baseline.py +++ b/figures_neoclassical_growth_baseline.py @@ -1,189 +1,207 @@ -import jax.numpy as jnp -import matplotlib.pyplot as plt -import os -from neoclassical_growth_matern import neoclassical_growth_matern - -from mpl_toolkits.axes_grid1.inset_locator import ( - zoomed_inset_axes, - mark_inset, - inset_axes, -) - -fontsize = 17 -ticksize = 16 -figsize = (15, 10) -params = { - "font.family": "serif", - "figure.figsize": figsize, - "figure.dpi": 80, - "figure.edgecolor": "k", - "figure.constrained_layout.use": True, # Adjust layout to prevent overlap - "font.size": fontsize, - "axes.labelsize": fontsize, - "axes.titlesize": fontsize, - "xtick.labelsize": ticksize, - "ytick.labelsize": ticksize, -} -plt.rcParams.update(params) - - -## Plot given solution - -sol_matern = neoclassical_growth_matern() -output_path = "figures/neoclassical_growth_model_baseline.pdf" - -zoom = True -zoom_loc = [90, 99] - -t = sol_matern["t_test"] -T = sol_matern["t_train"].max() -c_hat_matern = sol_matern["c_test"] -k_hat_matern = sol_matern["k_test"] -c_benchmark = sol_matern["c_benchmark"] -k_benchmark = sol_matern["k_benchmark"] -k_rel_error_matern = sol_matern["k_rel_error"] -c_rel_error_matern = sol_matern["c_rel_error"] - -# Plotting -plt.figure(figsize=(15, 10)) - -ax_capital = plt.subplot(2, 2, 1) - -plt.plot(t, k_hat_matern, color="k", label=r"$\hat{x}(t)$: Kernel Approximation")# Matérn -plt.plot(t, k_benchmark, linestyle="--", color="k", label=r"$x(t)$: Benchmark Solution") -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") - -plt.ylabel("Capital: $x(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels - - -ax_rel_k = plt.subplot(2, 2, 2) -k_rel_error_ylim = (1e-6, 2 * 1e-2) - -plt.plot( - t, - k_rel_error_matern, - color="k", - label=r"$\varepsilon_x(t)$: Relative Errors for $x(t)$", -) -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.yscale("log") # Set y-scale to logarithmic -plt.ylim(k_rel_error_ylim[0], k_rel_error_ylim[1]) -plt.xlabel("Time") -plt.legend() - -ax_consumption = plt.subplot(2, 2, 3) -c_rel_error_ylim = (1e-7, 2 * 1e-2) - -plt.plot(t, c_hat_matern, color="b", label=r"$\hat{y}(t)$: Kernel Approximation") #Matérn -plt.plot(t, c_benchmark, linestyle="--", color="b", label=r"$y(t)$: Benchmark Solution") -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") - -plt.ylabel("Consumption: $y(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels - -ax_rel_c = plt.subplot(2, 2, 4) - -plt.plot( - t, - c_rel_error_matern, - color="b", - label=r"$\varepsilon_y(t)$: Reletaive Errors for $y(t)$", -) - -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.yscale("log") # Set y-scale to logarithmic -plt.ylim(c_rel_error_ylim[0], c_rel_error_ylim[1]) -plt.xlabel("Time") -plt.legend() # Show legend with labels - - - -# Zoom in part of the plot -if zoom == True: - time_window = ( - zoom_loc # Indices: The window on the x-axis that want to be zoomed in - ) - ave_value = 0.5 * ( - k_benchmark[time_window[0]] + k_benchmark[time_window[1]] - ) # The average on the y-axis that want to be zoomed in - window_width = 0.01 * ave_value - axins = zoomed_inset_axes( - ax_capital, - 3, - loc="center", - bbox_to_anchor=(0.5, 0.7, -0.3, -0.3), - bbox_transform=ax_capital.transAxes, - ) - - axins.plot( - t[time_window[0] - 1 : time_window[1] + 1], - k_hat_matern[time_window[0] - 1 : time_window[1] + 1], - color="k", - ) - - axins.plot( - t[time_window[0] - 1 : time_window[1] + 1], - k_benchmark[time_window[0] - 1 : time_window[1] + 1], - linestyle="--", - color="k", - ) - - x1, x2, y1, y2 = ( - t[time_window[0]], - t[time_window[1]], - ave_value - window_width, - ave_value + window_width, - ) - axins.set_xlim(x1, x2) - axins.set_ylim(y1, y2) - plt.xticks(fontsize=8, visible=False) - plt.tick_params(axis="x", which="both", bottom=False, top=False, labelbottom=False) - plt.yticks(fontsize=8) - mark_inset(ax_capital, axins, loc1=1, loc2=3, linewidth="0.7", ls="--", ec="0.5") - - time_window = ( - zoom_loc # Indices: The window on the x-axis that want to be zoomed in - ) - ave_value = 0.5 * ( - c_benchmark[time_window[0]] + c_benchmark[time_window[1]] - ) # The average on the y-axis that want to be zoomed in - window_width = 0.01 * ave_value - axins = zoomed_inset_axes( - ax_consumption, - 3, - loc="center", - bbox_to_anchor=(0.5, 0.7, -0.3, -0.3), - bbox_transform=ax_consumption.transAxes, - ) - - axins.plot( - t[time_window[0] - 1 : time_window[1] + 1], - c_hat_matern[time_window[0] - 1 : time_window[1] + 1], - color="b", - ) - - axins.plot( - t[time_window[0] - 1 : time_window[1] + 1], - c_benchmark[time_window[0] - 1 : time_window[1] + 1], - linestyle="--", - color="b", - ) - - x1, x2, y1, y2 = ( - t[time_window[0]], - t[time_window[1]], - ave_value - window_width, - ave_value + window_width, - ) - axins.set_xlim(x1, x2) - axins.set_ylim(y1, y2) - plt.xticks(fontsize=8, visible=False) - plt.tick_params(axis="x", which="both", bottom=False, top=False, labelbottom=False) - plt.yticks(fontsize=8) - mark_inset( - ax_consumption, axins, loc1=1, loc2=3, linewidth="0.7", ls="--", ec="0.5" - ) -plt.savefig(output_path, format="pdf") +import jax.numpy as jnp +import matplotlib.pyplot as plt +import os +import jsonargparse +from neoclassical_growth_matern import neoclassical_growth_matern +from neoclassical_growth_matern_cvxpy import neoclassical_growth_matern_cvxpy + +from mpl_toolkits.axes_grid1.inset_locator import ( + zoomed_inset_axes, + mark_inset, + inset_axes, +) + +fontsize = 17 +ticksize = 16 +figsize = (15, 10) +params = { + "font.family": "serif", + "figure.figsize": figsize, + "figure.dpi": 80, + "figure.edgecolor": "k", + "figure.constrained_layout.use": True, # Adjust layout to prevent overlap + "font.size": fontsize, + "axes.labelsize": fontsize, + "axes.titlesize": fontsize, + "xtick.labelsize": ticksize, + "ytick.labelsize": ticksize, +} +plt.rcParams.update(params) + + +## Plot given solution +def plot_neoclassical_growth_baseline( + sol_matern, + output_path, + zoom=True, + zoom_loc=[90, 99], +): + t = sol_matern["t_test"] + T = sol_matern["t_train"].max() + c_hat_matern = sol_matern["c_test"] + k_hat_matern = sol_matern["k_test"] + c_benchmark = sol_matern["c_benchmark"] + k_benchmark = sol_matern["k_benchmark"] + k_rel_error_matern = sol_matern["k_rel_error"] + c_rel_error_matern = sol_matern["c_rel_error"] + + # Plotting + plt.figure(figsize=(15, 10)) + + ax_capital = plt.subplot(2, 2, 1) + + plt.plot(t, k_hat_matern, color="k", label=r"$\hat{x}(t)$: Kernel Approximation")# Matérn + plt.plot(t, k_benchmark, linestyle="--", color="k", label=r"$x(t)$: Benchmark Solution") + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + + plt.ylabel("Capital: $x(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels + + + ax_rel_k = plt.subplot(2, 2, 2) + k_rel_error_ylim = (1e-6, 2 * 1e-2) + + plt.plot( + t, + k_rel_error_matern, + color="k", + label=r"$\varepsilon_x(t)$: Relative Errors for $x(t)$", + ) + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.yscale("log") # Set y-scale to logarithmic + plt.ylim(k_rel_error_ylim[0], k_rel_error_ylim[1]) + plt.xlabel("Time") + plt.legend() + + ax_consumption = plt.subplot(2, 2, 3) + c_rel_error_ylim = (1e-7, 2 * 1e-2) + + plt.plot(t, c_hat_matern, color="b", label=r"$\hat{y}(t)$: Kernel Approximation") #Matérn + plt.plot(t, c_benchmark, linestyle="--", color="b", label=r"$y(t)$: Benchmark Solution") + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + + plt.ylabel("Consumption: $y(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels + + ax_rel_c = plt.subplot(2, 2, 4) + + plt.plot( + t, + c_rel_error_matern, + color="b", + label=r"$\varepsilon_y(t)$: Reletaive Errors for $y(t)$", + ) + + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.yscale("log") # Set y-scale to logarithmic + plt.ylim(c_rel_error_ylim[0], c_rel_error_ylim[1]) + plt.xlabel("Time") + plt.legend() # Show legend with labels + + + + # Zoom in part of the plot + if zoom == True: + time_window = ( + zoom_loc # Indices: The window on the x-axis that want to be zoomed in + ) + ave_value = 0.5 * ( + k_benchmark[time_window[0]] + k_benchmark[time_window[1]] + ) # The average on the y-axis that want to be zoomed in + window_width = 0.01 * ave_value + axins = zoomed_inset_axes( + ax_capital, + 3, + loc="center", + bbox_to_anchor=(0.5, 0.7, -0.3, -0.3), + bbox_transform=ax_capital.transAxes, + ) + + axins.plot( + t[time_window[0] - 1 : time_window[1] + 1], + k_hat_matern[time_window[0] - 1 : time_window[1] + 1], + color="k", + ) + + axins.plot( + t[time_window[0] - 1 : time_window[1] + 1], + k_benchmark[time_window[0] - 1 : time_window[1] + 1], + linestyle="--", + color="k", + ) + + x1, x2, y1, y2 = ( + t[time_window[0]], + t[time_window[1]], + ave_value - window_width, + ave_value + window_width, + ) + axins.set_xlim(x1, x2) + axins.set_ylim(y1, y2) + plt.xticks(fontsize=8, visible=False) + plt.tick_params(axis="x", which="both", bottom=False, top=False, labelbottom=False) + plt.yticks(fontsize=8) + mark_inset(ax_capital, axins, loc1=1, loc2=3, linewidth="0.7", ls="--", ec="0.5") + + time_window = ( + zoom_loc # Indices: The window on the x-axis that want to be zoomed in + ) + ave_value = 0.5 * ( + c_benchmark[time_window[0]] + c_benchmark[time_window[1]] + ) # The average on the y-axis that want to be zoomed in + window_width = 0.01 * ave_value + axins = zoomed_inset_axes( + ax_consumption, + 3, + loc="center", + bbox_to_anchor=(0.5, 0.7, -0.3, -0.3), + bbox_transform=ax_consumption.transAxes, + ) + + axins.plot( + t[time_window[0] - 1 : time_window[1] + 1], + c_hat_matern[time_window[0] - 1 : time_window[1] + 1], + color="b", + ) + + axins.plot( + t[time_window[0] - 1 : time_window[1] + 1], + c_benchmark[time_window[0] - 1 : time_window[1] + 1], + linestyle="--", + color="b", + ) + + x1, x2, y1, y2 = ( + t[time_window[0]], + t[time_window[1]], + ave_value - window_width, + ave_value + window_width, + ) + axins.set_xlim(x1, x2) + axins.set_ylim(y1, y2) + plt.xticks(fontsize=8, visible=False) + plt.tick_params(axis="x", which="both", bottom=False, top=False, labelbottom=False) + plt.yticks(fontsize=8) + mark_inset( + ax_consumption, axins, loc1=1, loc2=3, linewidth="0.7", ls="--", ec="0.5" + ) + plt.savefig(output_path, format="pdf") + + +# implementation selects the solve backend: "cvxpy" (default, DNLP via UNO) +# or "pyomo" (Ipopt binary). +def main(implementation: str = "cvxpy"): + solve = { + "cvxpy": neoclassical_growth_matern_cvxpy, + "pyomo": neoclassical_growth_matern, + }[implementation] + sol_matern = solve() + plot_neoclassical_growth_baseline( + sol_matern, "figures/neoclassical_growth_model_baseline.pdf" + ) + + +if __name__ == "__main__": + jsonargparse.CLI(main) diff --git a/figures_neoclassical_growth_concave_convex.py b/figures_neoclassical_growth_concave_convex.py index c26eaaa..09da62f 100644 --- a/figures_neoclassical_growth_concave_convex.py +++ b/figures_neoclassical_growth_concave_convex.py @@ -1,128 +1,147 @@ -import jax.numpy as jnp -import matplotlib.pyplot as plt -import numpy as np -import os -from neoclassical_growth_concave_convex_matern import neoclassical_growth_concave_convex_matern - -from mpl_toolkits.axes_grid1.inset_locator import ( - zoomed_inset_axes, - mark_inset, - inset_axes, -) - -fontsize = 17 -ticksize = 16 -figsize = (15, 7) -params = { - "font.family": "serif", - "figure.figsize": figsize, - "figure.dpi": 80, - "figure.edgecolor": "k", - "figure.constrained_layout.use": True, # Adjust layout to prevent overlap - "font.size": fontsize, - "axes.labelsize": fontsize, - "axes.titlesize": fontsize, - "xtick.labelsize": ticksize, - "ytick.labelsize": ticksize, -} -plt.rcParams.update(params) - - -## Plots for concave-convex production function -sol_1 = neoclassical_growth_concave_convex_matern(k_0=0.5, train_points=20) -sol_2 = neoclassical_growth_concave_convex_matern(k_0=1.0, train_points=20) -sol_3 = neoclassical_growth_concave_convex_matern(k_0=3.0, train_points=20) -sol_4 = neoclassical_growth_concave_convex_matern(k_0=4.0, train_points=20) -output_path = "figures/neoclassical_growth_model_concave_convex.pdf" - -plt.figure(figsize=(15, 8)) - -k_hat_1 = sol_1["k_test"] -k_hat_2 = sol_2["k_test"] -k_hat_3 = sol_3["k_test"] -k_hat_4 = sol_4["k_test"] - -c_hat_1 = sol_1["c_test"] -c_hat_2 = sol_2["c_test"] -c_hat_3 = sol_3["c_test"] -c_hat_4 = sol_4["c_test"] - -T = sol_1["t_train"].max() -t = sol_1["t_test"] - -ax_capital = plt.subplot(1, 2, 1) -plt.plot(t, k_hat_1, color="b", label=r"$\hat{x}(t): x_0 = 0.5$") -plt.plot(t, k_hat_2, color="gray", label=r"$\hat{x}(t): x_0 = 1$") -plt.plot(t, k_hat_3, color="r", label=r"$\hat{x}(t): x_0 = 3$") -plt.plot(t, k_hat_4, color="c", label=r"$\hat{x}(t): x_0 = 4$") -# plt.axhline(y=sol_1["k_ss_low"], linestyle="-.", color="k", label=r"$x_1^*$: Steady-State") -# plt.axhline(y=sol_1["k_ss_high"], linestyle="dashed", color="k", label=r"$x_2^*$: Steady-State") -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Capital: $x(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels - -ax_consumption = plt.subplot(1, 2, 2) -plt.plot(t, c_hat_1, color="b", label=r"$\hat{y}(t): x_0 = 0.5$") -plt.plot(t, c_hat_2, color="gray", label=r"$\hat{y}(t): x_0 = 1$") -plt.plot(t, c_hat_3, color="r", label=r"$\hat{y}(t): x_0 = 3$") -plt.plot(t, c_hat_4, color="c", label=r"$\hat{y}(t): x_0 = 4$") -# plt.axhline(y=sol_1["c_ss_low"], linestyle="-.", color="k", label=r"$y_1^*$: Steady-State") -# plt.axhline(y=sol_1["c_ss_high"], linestyle="dashed", color="k", label=r"$y_2^*$: Steady-State") -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Consumption: $y(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels - -#plt.savefig(output_path, format="pdf") - - -sols = [ - neoclassical_growth_concave_convex_matern(k_0=k_0, train_points=20) - for k_0 in np.linspace(0.5, 4.0, 70) -] - -output_path = "figures/neoclassical_growth_model_concave_convex_threshold.pdf" - -plt.figure(figsize=(15,8)) - -T = sols[0]["t_train"].max() -t = sols[0]["t_test"] - -ax_capital = plt.subplot(1, 2, 1) -for sol in sols: - plt.plot(t, sol["k_test"], color="gray") - -# plt.axhline( -# y=sols[0]["k_ss_low"], linestyle="-.", color="k", label=r"$k_1^*$: Steady-State" -# ) -# plt.axhline( -# y=sols[0]["k_ss_high"], -# linestyle="dashed", -# color="k", -# label=r"$k_2^*$: Steady-State", -# ) -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Capital: $x(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels - -ax_consumption = plt.subplot(1, 2, 2) -for sol in sols: - plt.plot(t, sol["c_test"], color="b") - -# plt.axhline( -# y=sols[0]["c_ss_low"], linestyle="-.", color="k", label=r"$c_1^*$: Steady-State" -# ) -# plt.axhline( -# y=sols[0]["c_ss_high"], -# linestyle="dashed", -# color="k", -# label=r"$c_2^*$: Steady-State", -# ) -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Consumption: $y(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels - -plt.savefig(output_path, format="pdf") +import jax.numpy as jnp +import matplotlib.pyplot as plt +import numpy as np +import os +import jsonargparse +from neoclassical_growth_concave_convex_matern import neoclassical_growth_concave_convex_matern +from neoclassical_growth_concave_convex_matern_cvxpy import neoclassical_growth_concave_convex_matern_cvxpy + +from mpl_toolkits.axes_grid1.inset_locator import ( + zoomed_inset_axes, + mark_inset, + inset_axes, +) + +fontsize = 17 +ticksize = 16 +figsize = (15, 7) +params = { + "font.family": "serif", + "figure.figsize": figsize, + "figure.dpi": 80, + "figure.edgecolor": "k", + "figure.constrained_layout.use": True, # Adjust layout to prevent overlap + "font.size": fontsize, + "axes.labelsize": fontsize, + "axes.titlesize": fontsize, + "xtick.labelsize": ticksize, + "ytick.labelsize": ticksize, +} +plt.rcParams.update(params) + + +## Plots for concave-convex production function. implementation selects the solve +## backend and defaults to "cvxpy", the pyomo-free DNLP reformulation solved with +## UNO. Pass --implementation pyomo to use the pyomo/ipopt model instead. +def main(implementation: str = "cvxpy"): + solve = { + "cvxpy": neoclassical_growth_concave_convex_matern_cvxpy, + "pyomo": neoclassical_growth_concave_convex_matern, + }[implementation] + + sol_1 = solve(k_0=0.5, train_points=20) + sol_2 = solve(k_0=1.0, train_points=20) + sol_3 = solve(k_0=3.0, train_points=20) + sol_4 = solve(k_0=4.0, train_points=20) + output_path = "figures/neoclassical_growth_model_concave_convex.pdf" + + plt.figure(figsize=(15, 8)) + + k_hat_1 = sol_1["k_test"] + k_hat_2 = sol_2["k_test"] + k_hat_3 = sol_3["k_test"] + k_hat_4 = sol_4["k_test"] + + c_hat_1 = sol_1["c_test"] + c_hat_2 = sol_2["c_test"] + c_hat_3 = sol_3["c_test"] + c_hat_4 = sol_4["c_test"] + + T = sol_1["t_train"].max() + t = sol_1["t_test"] + + ax_capital = plt.subplot(1, 2, 1) + plt.plot(t, k_hat_1, color="b", label=r"$\hat{x}(t): x_0 = 0.5$") + plt.plot(t, k_hat_2, color="gray", label=r"$\hat{x}(t): x_0 = 1$") + plt.plot(t, k_hat_3, color="r", label=r"$\hat{x}(t): x_0 = 3$") + plt.plot(t, k_hat_4, color="c", label=r"$\hat{x}(t): x_0 = 4$") + # plt.axhline(y=sol_1["k_ss_low"], linestyle="-.", color="k", label=r"$x_1^*$: Steady-State") + # plt.axhline(y=sol_1["k_ss_high"], linestyle="dashed", color="k", label=r"$x_2^*$: Steady-State") + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Capital: $x(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels + + ax_consumption = plt.subplot(1, 2, 2) + plt.plot(t, c_hat_1, color="b", label=r"$\hat{y}(t): x_0 = 0.5$") + plt.plot(t, c_hat_2, color="gray", label=r"$\hat{y}(t): x_0 = 1$") + plt.plot(t, c_hat_3, color="r", label=r"$\hat{y}(t): x_0 = 3$") + plt.plot(t, c_hat_4, color="c", label=r"$\hat{y}(t): x_0 = 4$") + # plt.axhline(y=sol_1["c_ss_low"], linestyle="-.", color="k", label=r"$y_1^*$: Steady-State") + # plt.axhline(y=sol_1["c_ss_high"], linestyle="dashed", color="k", label=r"$y_2^*$: Steady-State") + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Consumption: $y(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels + + #plt.savefig(output_path, format="pdf") + + # Sweep x_0 across both basins, skipping any failed or non-physical solve. + sols = [] + for k_0 in np.linspace(0.5, 4.0, 70): + try: + sol = solve(k_0=k_0, train_points=20) + except Exception: + continue + k = np.asarray(sol["k_test"]) + if np.all(np.isfinite(k)) and k.min() > 0 and k.max() < 10: + sols.append(sol) + + output_path = "figures/neoclassical_growth_model_concave_convex_threshold.pdf" + + plt.figure(figsize=(15,8)) + + T = sols[0]["t_train"].max() + t = sols[0]["t_test"] + + ax_capital = plt.subplot(1, 2, 1) + for sol in sols: + plt.plot(t, sol["k_test"], color="gray") + + # plt.axhline( + # y=sols[0]["k_ss_low"], linestyle="-.", color="k", label=r"$k_1^*$: Steady-State" + # ) + # plt.axhline( + # y=sols[0]["k_ss_high"], + # linestyle="dashed", + # color="k", + # label=r"$k_2^*$: Steady-State", + # ) + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Capital: $x(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels + + ax_consumption = plt.subplot(1, 2, 2) + for sol in sols: + plt.plot(t, sol["c_test"], color="b") + + # plt.axhline( + # y=sols[0]["c_ss_low"], linestyle="-.", color="k", label=r"$c_1^*$: Steady-State" + # ) + # plt.axhline( + # y=sols[0]["c_ss_high"], + # linestyle="dashed", + # color="k", + # label=r"$c_2^*$: Steady-State", + # ) + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Consumption: $y(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels + + plt.savefig(output_path, format="pdf") + + +if __name__ == "__main__": + jsonargparse.CLI(main) diff --git a/figures_neoclassical_growth_robustness.py b/figures_neoclassical_growth_robustness.py index b6f8974..7572a22 100644 --- a/figures_neoclassical_growth_robustness.py +++ b/figures_neoclassical_growth_robustness.py @@ -1,7 +1,9 @@ import jax.numpy as jnp import matplotlib.pyplot as plt import os +import jsonargparse from neoclassical_growth_matern import neoclassical_growth_matern +from neoclassical_growth_matern_cvxpy import neoclassical_growth_matern_cvxpy from mpl_toolkits.axes_grid1.inset_locator import ( zoomed_inset_axes, @@ -192,26 +194,36 @@ def plot_neoclassical_growth( plt.savefig(output_path, format="pdf") -# Plots with various parameters -sol = neoclassical_growth_matern( - train_points_list=[0.0, 1.0, 3.0, 5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 38.0, 40.0], lambda_p = 1e-6 -) -plot_neoclassical_growth( - sol, - "figures/neoclassical_growth_model_sparse.pdf", - c_rel_error_ylim=(1e-7, 2 * 1e-2), - zoom=True, - zoom_loc=[10, 20], -) +# Plots with various parameters. implementation selects the solve backend: +# "cvxpy" (default, DNLP via IPOPT) or "pyomo" (Ipopt binary). +def main(implementation: str = "cvxpy"): + solve = { + "cvxpy": neoclassical_growth_matern_cvxpy, + "pyomo": neoclassical_growth_matern, + }[implementation] + sol = solve( + train_points_list=[0.0, 1.0, 3.0, 5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, 38.0, 40.0], lambda_p = 1e-6 + ) + plot_neoclassical_growth( + sol, + "figures/neoclassical_growth_model_sparse.pdf", + c_rel_error_ylim=(1e-7, 2 * 1e-2), + zoom=True, + zoom_loc=[10, 20], + ) -sol = neoclassical_growth_matern(train_T=10.0, train_points=11, test_T=15.0) -plot_neoclassical_growth( - sol, - "figures/neoclassical_growth_model_far_steady_state.pdf", - k_rel_error_ylim=(1e-4, 1e-1), - c_rel_error_ylim=(1e-4, 1e-1), - zoom=False, -) + sol = solve(train_T=10.0, train_points=11, test_T=15.0) + plot_neoclassical_growth( + sol, + "figures/neoclassical_growth_model_far_steady_state.pdf", + k_rel_error_ylim=(1e-4, 1e-1), + c_rel_error_ylim=(1e-4, 1e-1), + zoom=False, + ) + + +if __name__ == "__main__": + jsonargparse.CLI(main) ''' sol = neoclassical_growth_matern(nu=1.5) plot_neoclassical_growth( diff --git a/figures_neoclassical_human_capital.py b/figures_neoclassical_human_capital.py index 98e6608..cc48b81 100644 --- a/figures_neoclassical_human_capital.py +++ b/figures_neoclassical_human_capital.py @@ -1,7 +1,9 @@ import jax.numpy as jnp import matplotlib.pyplot as plt import os +import jsonargparse from neoclassical_human_capital_matern import human_capital_matern +from neoclassical_human_capital_matern_cvxpy import human_capital_matern_cvxpy from mpl_toolkits.axes_grid1.inset_locator import ( zoomed_inset_axes, @@ -26,86 +28,96 @@ plt.rcParams.update(params) -## Plot given solution -sol = human_capital_matern() -output_path = "figures/neoclassical_human_capital.pdf" +## Plot given solution. implementation selects the solve backend: "cvxpy" +## (default, DNLP via UNO) or "pyomo" (Ipopt binary). +def main(implementation: str = "cvxpy"): + solve = { + "cvxpy": human_capital_matern_cvxpy, + "pyomo": human_capital_matern, + }[implementation] + sol = solve() + output_path = "figures/neoclassical_human_capital.pdf" -t = sol["t_test"] -T = sol["t_train"].max() -c_hat = sol["c_test"] -k_hat = sol["k_test"] -h_hat = sol["h_test"] -i_k_hat = sol["i_k_test"] -i_h_hat = sol["i_h_test"] -mu_k_hat = sol["mu_k_test"] -mu_h_hat = sol["mu_h_test"] + t = sol["t_test"] + T = sol["t_train"].max() + c_hat = sol["c_test"] + k_hat = sol["k_test"] + h_hat = sol["h_test"] + i_k_hat = sol["i_k_test"] + i_h_hat = sol["i_h_test"] + mu_k_hat = sol["mu_k_test"] + mu_h_hat = sol["mu_h_test"] -# Plotting + # Plotting -ax_physical_capital = plt.subplot(4, 2, 1) + ax_physical_capital = plt.subplot(4, 2, 1) -plt.plot(t, k_hat, color="k", label=r"$\hat{x}_k(t)$: Kernel Approximation") -#plt.axhline(y=sol["k_ss"], linestyle="-.", color="k", label=r"$k^*$:Steady-State") -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Physical Capital: $x_k(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels + plt.plot(t, k_hat, color="k", label=r"$\hat{x}_k(t)$: Kernel Approximation") + #plt.axhline(y=sol["k_ss"], linestyle="-.", color="k", label=r"$k^*$:Steady-State") + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Physical Capital: $x_k(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels -ax_human_capital = plt.subplot(4, 2, 2) + ax_human_capital = plt.subplot(4, 2, 2) -plt.plot(t, h_hat, color="k", label=r"$\hat{x}_h(t)$: Kernel Approximation") -#plt.axhline(y=sol["h_ss"], linestyle="-.", color="grey", label=r"$h^*$: Steady-State") -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Human Capital: $x_h(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels + plt.plot(t, h_hat, color="k", label=r"$\hat{x}_h(t)$: Kernel Approximation") + #plt.axhline(y=sol["h_ss"], linestyle="-.", color="grey", label=r"$h^*$: Steady-State") + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Human Capital: $x_h(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels -ax_consumption = plt.subplot(4, 2, 3) + ax_consumption = plt.subplot(4, 2, 3) -plt.plot(t, c_hat, color="b", label=r"$\hat{y}_c(t)$: Kernel Approximation") -#plt.axhline(y=sol["c_ss"], linestyle="-.", color="b", label=r"$c^*$: Steady-State") -plt.axvline(x=T, color="b", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Consumption: $y_c(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels + plt.plot(t, c_hat, color="b", label=r"$\hat{y}_c(t)$: Kernel Approximation") + #plt.axhline(y=sol["c_ss"], linestyle="-.", color="b", label=r"$c^*$: Steady-State") + plt.axvline(x=T, color="b", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Consumption: $y_c(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels -ax_investment_k = plt.subplot(4, 2, 4) + ax_investment_k = plt.subplot(4, 2, 4) -plt.plot(t, i_k_hat, color="b", label=r"$\hat{y}_k(t)$: Kernel Approximation") -#plt.axhline(y=sol["i_k_ss"], linestyle="-.", color="k", label=r"$i_k^*$: Steady-State") -plt.axvline(x=T, color="b", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Physical Capital Investment: $y_k(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels + plt.plot(t, i_k_hat, color="b", label=r"$\hat{y}_k(t)$: Kernel Approximation") + #plt.axhline(y=sol["i_k_ss"], linestyle="-.", color="k", label=r"$i_k^*$: Steady-State") + plt.axvline(x=T, color="b", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Physical Capital Investment: $y_k(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels -ax_investment_h = plt.subplot(4, 2, 5) + ax_investment_h = plt.subplot(4, 2, 5) -plt.plot(t, i_h_hat, color="b", label=r"$\hat{y}_h(t)$: Kernel Approximation") -#plt.axhline(y=sol["i_h_ss"], linestyle="-.", color="grey", label=r"$i_h^*$: Steady-State") -plt.axvline(x=T, color="b", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel("Human Capital Investment: $y_h(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels + plt.plot(t, i_h_hat, color="b", label=r"$\hat{y}_h(t)$: Kernel Approximation") + #plt.axhline(y=sol["i_h_ss"], linestyle="-.", color="grey", label=r"$i_h^*$: Steady-State") + plt.axvline(x=T, color="b", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel("Human Capital Investment: $y_h(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels -ax_investment_mu_k = plt.subplot(4, 2, 6) + ax_investment_mu_k = plt.subplot(4, 2, 6) -plt.plot(t, mu_k_hat, color="grey", label=r"$\hat{\mu}_k(t)$: Kernel Approximation") -#plt.axhline(y=sol["i_h_ss"], linestyle="-.", color="grey", label=r"$i_h^*$: Steady-State") -plt.axvline(x=T, color="grey", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel(r"Co-state Variable: $\mu_k(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels + plt.plot(t, mu_k_hat, color="grey", label=r"$\hat{\mu}_k(t)$: Kernel Approximation") + #plt.axhline(y=sol["i_h_ss"], linestyle="-.", color="grey", label=r"$i_h^*$: Steady-State") + plt.axvline(x=T, color="grey", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel(r"Co-state Variable: $\mu_k(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels -ax_investment_mu_h = plt.subplot(4, 2, 7) + ax_investment_mu_h = plt.subplot(4, 2, 7) -plt.plot(t, mu_h_hat, color="grey", label=r"$\hat{\mu}_h(t)$: Kernel Approximation") -#plt.axhline(y=sol["i_h_ss"], linestyle="-.", color="grey", label=r"$i_h^*$: Steady-State") -plt.axvline(x=T, color="grey", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel(r"Co-state Variable: $\mu_h(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels + plt.plot(t, mu_h_hat, color="grey", label=r"$\hat{\mu}_h(t)$: Kernel Approximation") + #plt.axhline(y=sol["i_h_ss"], linestyle="-.", color="grey", label=r"$i_h^*$: Steady-State") + plt.axvline(x=T, color="grey", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel(r"Co-state Variable: $\mu_h(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels -plt.tight_layout() # Adjust layout to prevent overlap + plt.tight_layout() # Adjust layout to prevent overlap -plt.savefig(output_path, format="pdf") + plt.savefig(output_path, format="pdf") + + +if __name__ == "__main__": + jsonargparse.CLI(main) diff --git a/figures_optimal_advertising.py b/figures_optimal_advertising.py index f0c0292..2fb7a1c 100644 --- a/figures_optimal_advertising.py +++ b/figures_optimal_advertising.py @@ -2,9 +2,9 @@ import matplotlib.pyplot as plt import numpy as np import os -from optimal_advertising_matern import ( - optimal_advertising_matern, -) +import jsonargparse +from optimal_advertising_matern import optimal_advertising_matern +from optimal_advertising_matern_cvxpy import optimal_advertising_matern_cvxpy from mpl_toolkits.axes_grid1.inset_locator import ( zoomed_inset_axes, @@ -29,34 +29,44 @@ plt.rcParams.update(params) -## Plot for optimal advertising -sol = optimal_advertising_matern() -output_path = "figures/optimal_advertising.pdf" +## Plot for optimal advertising. implementation selects the solve backend: +## "cvxpy" (default, DNLP via UNO) or "pyomo" (Ipopt binary). +def main(implementation: str = "cvxpy"): + solve = { + "cvxpy": optimal_advertising_matern_cvxpy, + "pyomo": optimal_advertising_matern, + }[implementation] + sol = solve() + output_path = "figures/optimal_advertising.pdf" + + plt.figure(figsize=(15, 7)) + + x_hat = sol["x_test"] + mu_hat = sol["mu_test"] + u_hat = sol["u_test"] -plt.figure(figsize=(15, 7)) + T = sol["t_train"].max() + t = sol["t_test"] -x_hat = sol["x_test"] -mu_hat = sol["mu_test"] -u_hat = sol["u_test"] + ax_market_share = plt.subplot(1, 2, 1) + plt.plot(t, x_hat, color="k", label=r"$\hat{x}(t)$: Kernel Approximation") + #plt.axhline(y=sol["x_ss"], linestyle="-.", color="k", label=r"$x^*$: Steady-State") + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel(r"Market Share: $x(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels -T = sol["t_train"].max() -t = sol["t_test"] + ax_mu = plt.subplot(1, 2, 2) + plt.plot(t, mu_hat, color="blue", label=r"$\hat{\mu}(t)$: Kernel Approximation") + #plt.axhline(y=sol["mu_ss"], linestyle="-.", color="b", label=r"$\mu^*$: Steady-State") + plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") + plt.ylabel(r"Co-state Variable: $\mu(t)$") + plt.xlabel("Time") + plt.legend() # Show legend with labels + plt.tight_layout() -ax_market_share = plt.subplot(1, 2, 1) -plt.plot(t, x_hat, color="k", label=r"$\hat{x}(t)$: Kernel Approximation") -#plt.axhline(y=sol["x_ss"], linestyle="-.", color="k", label=r"$x^*$: Steady-State") -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel(r"Market Share: $x(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels + plt.savefig(output_path, format="pdf") -ax_mu = plt.subplot(1, 2, 2) -plt.plot(t, mu_hat, color="blue", label=r"$\hat{\mu}(t)$: Kernel Approximation") -#plt.axhline(y=sol["mu_ss"], linestyle="-.", color="b", label=r"$\mu^*$: Steady-State") -plt.axvline(x=T, color="k", linestyle=":", label="Extrapolation/Interpolation") -plt.ylabel(r"Co-state Variable: $\mu(t)$") -plt.xlabel("Time") -plt.legend() # Show legend with labels -plt.tight_layout() -plt.savefig(output_path, format="pdf") +if __name__ == "__main__": + jsonargparse.CLI(main) diff --git a/neoclassical_growth_concave_convex_matern_cvxpy.py b/neoclassical_growth_concave_convex_matern_cvxpy.py new file mode 100644 index 0000000..153caae --- /dev/null +++ b/neoclassical_growth_concave_convex_matern_cvxpy.py @@ -0,0 +1,156 @@ +import time +import jax +import jax.numpy as jnp +import numpy as np +import cvxpy as cp +import jsonargparse +from jax import config +from kernels import integrated_matern_kernel_matrices +from typing import List, Optional + +config.update("jax_enable_x64", True) + +# Pyomo-free DNLP port of neoclassical_growth_concave_convex_matern. With z = k**a +# the concave-convex envelope A*max(k**a, b_1*k**a - b_2) is convex-PWL and bounds +# output Y from below; two complementarity equalities pin the marginal product P to +# the active branch and force Y to bind -- an exact reformulation, no smoothing. +NLP_SOLVERS = { + "IPOPT": ( + cp.IPOPT, + dict(tol=1e-8, dual_inf_tol=1e-8, constr_viol_tol=1e-8, max_iter=1000), + ), + "UNO": (cp.UNO, dict(preset="filtersqp")), +} + + +def neoclassical_growth_concave_convex_matern_cvxpy( + a: float = 1 / 3, + delta: float = 0.1, + rho_hat: float = 0.11, + A: float = 0.5, + b_1: float = 3.0, + b_2: float = 2.5, + k_0: float = 1.0, + nu: float = 0.5, + sigma: float = 1.0, + rho: float = 10, + solver_type: str = "UNO", + train_T: float = 40.0, + train_points: int = 41, + test_T: float = 50, + test_points: int = 41, + benchmark_T: float = 60.0, + benchmark_points: int = 300, + train_points_list: Optional[List[float]] = None, + verbose: bool = False, +): + # if passing in `train_points` then doesn't us a grid. Otherwise, uses linspace + if train_points_list is None: + train_data = jnp.linspace(0, train_T, train_points) + else: + train_data = jnp.array(train_points_list) + test_data = jnp.linspace(0, test_T, test_points) + benchmark_grid = jnp.linspace(0, benchmark_T, benchmark_points) + + # Construct kernel matrices. cvxpy needs numpy (not jax) arrays. + N = len(train_data) + K, K_tilde = integrated_matern_kernel_matrices( + train_data, train_data, nu, sigma, rho + ) + K = np.asarray((K + K.T) / 2) # symmetrize -> exactly PSD for quad_form + K_tilde = np.asarray(K_tilde) + + # Consumption is substituted out via c = 1/mu. z = k**a linearizes the + # production branches; Y is output, P the marginal product of capital. k and + # mu carry lower bounds so trial points stay in the domain of k**a and 1/mu. + alpha_mu, alpha_k = cp.Variable(N), cp.Variable(N) + mu_0 = cp.Variable(nonneg=True) + z, Y, P = cp.Variable(N), cp.Variable(N), cp.Variable(N) + k, mu = cp.Variable(N), cp.Variable(N) + + # Flat warm start at k_0 (no ramp toward a steady state): consumption held at + # the capital-stationary level, with z, Y, P on the active production branch + # (m1 below the kink k_bar, m2 above) so complementarity holds at the start. + k_bar = (b_2 / (b_1 - 1)) ** (1 / a) + f_0 = A * max(k_0**a, b_1 * k_0**a - b_2) + c_0 = f_0 - delta * k_0 + m1_0 = A * a * k_0 ** (a - 1) + alpha_mu.value = np.zeros(N) + alpha_k.value = np.zeros(N) + mu_0.value = 1.0 / c_0 + k.value = np.full(N, k_0) + mu.value = np.full(N, 1.0 / c_0) + z.value = np.full(N, k_0**a) + Y.value = np.full(N, f_0) + P.value = np.full(N, b_1 * m1_0 if k_0 >= k_bar else m1_0) + + dmu_dt = K @ alpha_mu + dk_dt = K @ alpha_k + m1 = A * a * cp.power(k, a - 1) + m2 = b_1 * m1 + prob = cp.Problem( + cp.Minimize( + cp.quad_form(alpha_mu, cp.psd_wrap(K)) + cp.quad_form(alpha_k, cp.psd_wrap(K)) + ), + [ + k == k_0 + K_tilde @ alpha_k, # state/costate from the kernel expansion + mu == mu_0 + K_tilde @ alpha_mu, + k >= 1e-4, mu >= 1e-4, z >= 1e-6, # domain bounds + z == cp.power(k, a), + cp.maximum(A * z, A * (b_1 * z - b_2)) <= Y, # production envelope + cp.multiply(Y - A * z, P - m2) == 0, # complementarity: pin P, bind Y + cp.multiply(Y - A * (b_1 * z - b_2), P - m1) == 0, + dk_dt == Y - delta * k - cp.power(mu, -1), # resource (c = 1/mu) + dmu_dt == -cp.multiply(mu, P - delta - rho_hat), # Euler (MPK = P) + ], + ) + assert prob.is_dnlp() + solver, options = NLP_SOLVERS[solver_type] + options = dict(options) + if solver_type == "UNO" and not verbose: + options["logger"] = "SILENT" # mute UNO's C-level iteration table + start = time.perf_counter() + prob.solve(nlp=True, solver=solver, verbose=verbose, **options) + elapsed = time.perf_counter() - start + print(f"elapsed solve(s) = {elapsed}") + if prob.status not in ("optimal", "optimal_inaccurate"): + print(f"solver status: {prob.status}") + + alpha_mu = jnp.array(alpha_mu.value) + alpha_k = jnp.array(alpha_k.value) + mu_0 = float(mu_0.value) + + # Interpolator using training data + @jax.jit + def kernel_solution(test_data): + # pointwise comparison test_data to train_data + K_test, K_tilde_test = integrated_matern_kernel_matrices( + test_data, train_data, nu, sigma, rho + ) + mu_test = mu_0 + K_tilde_test @ alpha_mu + k_test = k_0 + K_tilde_test @ alpha_k + c_test = 1.0 / mu_test + return k_test, c_test + + # Generate test_data and compare to the benchmark + k_test, c_test = kernel_solution(test_data) + + solve_time = prob.solver_stats.solve_time + if solve_time is None: + solve_time = elapsed + print(f"solve_time(s) = {solve_time}") + return { + "t_train": train_data, + "t_test": test_data, + "k_test": k_test, + "c_test": c_test, + "alpha_m": alpha_mu, + "alpha_k": alpha_k, + "mu_0": mu_0, + "solve_time": solve_time, + "kernel_solution": kernel_solution, # interpolator + } + + +if __name__ == "__main__": + jsonargparse.CLI(neoclassical_growth_concave_convex_matern_cvxpy) diff --git a/neoclassical_growth_matern_cvxpy.py b/neoclassical_growth_matern_cvxpy.py new file mode 100644 index 0000000..8280d0b --- /dev/null +++ b/neoclassical_growth_matern_cvxpy.py @@ -0,0 +1,166 @@ +import time +import jax +import jax.numpy as jnp +import numpy as np +import cvxpy as cp +import jsonargparse +from jax import config +from kernels import integrated_matern_kernel_matrices +from neoclassical_growth_benchmark import neoclassical_growth_benchmark +from typing import List, Optional + +config.update("jax_enable_x64", True) + +# Pyomo-free DNLP port of neoclassical_growth_matern. The optimal-growth FOC +# collocation is genuinely nonconvex (bilinear shadow-price DAE mu*c == 1, the +# k**a production term), so it cannot use the conic/QP backends; cvxpy's DNLP +# interface (prob.solve(nlp=True, ...)) hands the smooth nonlinear program to a +# nonlinear solver. solver_type defaults to UNO (unopy, in-process), run on its +# "filtersqp" SQP preset whose line search handles the bilinear shadow-price DAE +# from the zero init. IPOPT (cyipopt) stays available as an alternative; its +# options mirror the pyomo tolerances. +NLP_SOLVERS = { + "UNO": (cp.UNO, dict(preset="filtersqp")), + "IPOPT": ( + cp.IPOPT, + dict(tol=1e-8, dual_inf_tol=1e-8, constr_viol_tol=1e-8, max_iter=2000), + ), +} + + +def neoclassical_growth_matern_cvxpy( + a: float = 1 / 3, + delta: float = 0.1, + rho_hat: float = 0.11, + k_0: float = 1.0, # k_0 is the state variable initial conditions here, i.e., x_0 + nu: float = 0.5, + sigma: float = 1.0, + rho: float = 10, + solver_type: str = "UNO", + train_T: float = 40.0, + train_points: int = 41, + test_T: float = 50, + test_points: int = 100, + benchmark_T: float = 60.0, + benchmark_points: int = 300, + train_points_list: Optional[List[float]] = None, + lambda_p: float = 0.0, # Smoothing penalty for the optimizer, This is purely because of the DAE term mu*c = 1 + verbose: bool = False, +): + # if passing in `train_points` then doesn't us a grid. Otherwise, uses linspace + if train_points_list is None: + train_data = jnp.linspace(0, train_T, train_points) + else: + train_data = jnp.array(train_points_list) + test_data = jnp.linspace(0, test_T, test_points) + benchmark_grid = jnp.linspace(0, benchmark_T, benchmark_points) + + # Construct kernel matrices. cvxpy needs numpy (not jax) arrays. + N = len(train_data) + K, K_tilde = integrated_matern_kernel_matrices( + train_data, train_data, nu, sigma, rho + ) + K = np.asarray((K + K.T) / 2) # symmetrize -> exactly PSD for quad_form + K_tilde = np.asarray(K_tilde) + + # Decision variables. Mirror the pyomo init so the local NLP solve starts + # from the identical point: zero kernel coefficients and the steady-flow + # guess k_0**a - delta*k_0 for the scalar initial conditions c_0, mu_0 >= 0. + alpha_mu, alpha_c, alpha_k = cp.Variable(N), cp.Variable(N), cp.Variable(N) + c_0 = cp.Variable(nonneg=True) + mu_0 = cp.Variable(nonneg=True) + alpha_mu.value = np.zeros(N) + alpha_c.value = np.zeros(N) + alpha_k.value = np.zeros(N) + c_0.value = mu_0.value = k_0**a - delta * k_0 + + # Affine kernel expansions (the pyomo mu/c/k/dmu_dt/dk_dt helpers inline). + # k is the state x; mu the costate; c the control. + mu = mu_0 + K_tilde @ alpha_mu + c = c_0 + K_tilde @ alpha_c + k = k_0 + K_tilde @ alpha_k + dmu_dt = K @ alpha_mu + dk_dt = K @ alpha_k + + # lambda_p makes sure the optimizer returns smooth (non-wiggly) solutions in + # the extrapolation; it is zero by default, matching the pyomo penalty term. + smoothing = lambda_p * ( + cp.quad_form(alpha_c, cp.psd_wrap(K)) + + cp.quad_form(alpha_k, cp.psd_wrap(K)) + + cp.quad_form(alpha_mu, cp.psd_wrap(K)) + ) + prob = cp.Problem( + cp.Minimize( + cp.quad_form(alpha_mu, cp.psd_wrap(K)) + + cp.quad_form(alpha_k, cp.psd_wrap(K)) + + smoothing + ), + [ + dk_dt == cp.power(k, a) - delta * k - c, # resource + dmu_dt + == -cp.multiply(mu, a * cp.power(k, a - 1) - delta - rho_hat), # Euler + cp.multiply(mu, c) == 1.0, # shadow price (DAE) + ], + ) + assert prob.is_dnlp() + solver, options = NLP_SOLVERS[solver_type] + start = time.perf_counter() + prob.solve(nlp=True, solver=solver, verbose=verbose, **options) + elapsed = time.perf_counter() - start + print(f"elapsed solve(s) = {elapsed}") + if prob.status not in ("optimal", "optimal_inaccurate"): + print(f"solver status: {prob.status}") + + alpha_c = jnp.array(alpha_c.value) + alpha_k = jnp.array(alpha_k.value) + c_0 = float(c_0.value) + + # Interpolator using training data + @jax.jit + def kernel_solution(test_data): + # pointwise comparison test_data to train_data + K_test, K_tilde_test = integrated_matern_kernel_matrices( + test_data, train_data, nu, sigma, rho + ) + c_test = c_0 + K_tilde_test @ alpha_c + k_test = k_0 + K_tilde_test @ alpha_k + return k_test, c_test + + sol_benchmark = neoclassical_growth_benchmark( + a, delta, rho_hat, 1.0, k_0, benchmark_grid + ) + + # Generate test_data and compare to the benchmark + k_benchmark, c_benchmark = sol_benchmark(test_data) + k_test, c_test = kernel_solution(test_data) + + k_rel_error = jnp.abs(k_benchmark - k_test) / k_benchmark + c_rel_error = jnp.abs(c_benchmark - c_test) / c_benchmark + # cvxpy reports solver_stats.solve_time for UNO; IPOPT leaves it None, so + # fall back to the wall-clock timer around the solve. + solve_time = prob.solver_stats.solve_time + if solve_time is None: + solve_time = elapsed + print( + f"solve_time(s) = {solve_time}, E(|rel_error(k)|) = {k_rel_error.mean()}, E(|rel_error(c)|) = {c_rel_error.mean()}" + ) + return { + "t_train": train_data, + "t_test": test_data, + "k_test": k_test, + "c_test": c_test, + "k_benchmark": k_benchmark, + "c_benchmark": c_benchmark, + "k_rel_error": k_rel_error, + "c_rel_error": c_rel_error, + "alpha_c": alpha_c, + "alpha_k": alpha_k, + "c_0": c_0, + "solve_time": solve_time, + "kernel_solution": kernel_solution, # interpolator + "benchmark_solution": sol_benchmark, # interpolator + } + + +if __name__ == "__main__": + jsonargparse.CLI(neoclassical_growth_matern_cvxpy) diff --git a/neoclassical_human_capital_matern_cvxpy.py b/neoclassical_human_capital_matern_cvxpy.py new file mode 100644 index 0000000..4900f96 --- /dev/null +++ b/neoclassical_human_capital_matern_cvxpy.py @@ -0,0 +1,246 @@ +import time +from typing import List, Optional + +import jax.numpy as jnp +import jsonargparse +import numpy as np +import cvxpy as cp +from jax import config +from scipy.optimize import fsolve + +from kernels import integrated_matern_kernel_matrices + +config.update("jax_enable_x64", True) + +# Pyomo-free DNLP port of neoclassical_human_capital_matern. The two-capital +# (physical + human) FOC collocation is nonconvex (Cobb-Douglas f = k**a_k h**a_h, +# the bilinear costate/feasibility/shadow-price relations), so it is solved +# through cvxpy's DNLP interface (prob.solve(nlp=True, ...)). solver_type defaults +# to UNO (unopy, in-process, filtersqp preset); IPOPT (cyipopt) stays available as +# an alternative, with options mirroring the pyomo tolerances. +NLP_SOLVERS = { + "UNO": (cp.UNO, dict(preset="filtersqp")), + "IPOPT": ( + cp.IPOPT, + dict( + tol=1e-8, + dual_inf_tol=1e-8, + constr_viol_tol=1e-8, + acceptable_tol=1e-6, + mu_strategy="adaptive", + max_iter=4000, + ), + ), +} + + +def human_capital_matern_cvxpy( + a_k: float = 1 / 3, + a_h: float = 1 / 4, + delta_k: float = 0.1, + delta_h: float = 0.05, + rho_hat: float = 0.11, # discount rate + k_0: float = 1.5, + nu: float = 0.5, + sigma: float = 1.0, + rho: float = 10, + solver_type: str = "UNO", + train_T: float = 80.0, + train_points: int = 61, + test_T: float = 100.0, + test_points: int = 100, + benchmark_T: float = 60.0, + benchmark_points: int = 300, + train_points_list: Optional[List[float]] = None, + lambda_p: float = 5e-3, # small smoothing penalty to stabilize the solve + verbose: bool = False, +): + # if passing in `train_points` then doesn't us a grid. Otherwise, uses linspace + if train_points_list is None: + train_data = jnp.linspace(0, train_T, train_points) + else: + train_data = jnp.array(train_points_list) + test_data = jnp.linspace(0, test_T, test_points) + benchmark_grid = jnp.linspace(0, benchmark_T, benchmark_points) + + # Construct kernel matrices. cvxpy needs numpy (not jax) arrays. + N = len(train_data) + K, K_tilde = integrated_matern_kernel_matrices( + train_data, train_data, nu, sigma, rho + ) + K = np.asarray((K + K.T) / 2) # symmetrize -> exactly PSD for quad_form + K_tilde = np.asarray(K_tilde) + + # Solve the no-arbitrage condition for the initial human capital h_0, given + # k_0, so the two marginal products net of depreciation coincide at t=0. + h_0 = fsolve( + lambda h: (k_0**a_k) * (a_h * h ** (a_h - 1)) + - (a_k * k_0 ** (a_k - 1)) * (h**a_h) + - delta_h + + delta_k, + [k_0], + )[0] + c_0_init = (k_0**a_k) * (h_0**a_h) - delta_h * h_0 - delta_k * k_0 + + # Decision variables (7 coefficient vectors, 5 scalars), initialized at the + # pyomo starting point so the local NLP solve begins identically. + alpha_k, alpha_h = cp.Variable(N), cp.Variable(N) + alpha_mu_k, alpha_mu_h = cp.Variable(N), cp.Variable(N) + alpha_i_k, alpha_i_h, alpha_c = cp.Variable(N), cp.Variable(N), cp.Variable(N) + i_k_0 = cp.Variable(nonneg=True) + i_h_0 = cp.Variable(nonneg=True) + c_0 = cp.Variable(nonneg=True) + mu_k_0 = cp.Variable(nonneg=True) + mu_h_0 = cp.Variable(nonneg=True) + for v in (alpha_k, alpha_h, alpha_mu_k, alpha_mu_h, alpha_i_k, alpha_i_h, alpha_c): + v.value = np.zeros(N) + i_k_0.value = delta_k * k_0 + i_h_0.value = delta_h * h_0 + c_0.value = c_0_init + mu_k_0.value = mu_h_0.value = 1 / c_0_init + + # Affine kernel expansions (the pyomo helper functions inline). k/h are the + # physical/human capital states, i_k/i_h investments, c consumption, mu_* costates. + k = k_0 + K_tilde @ alpha_k + h = h_0 + K_tilde @ alpha_h + i_k = i_k_0 + K_tilde @ alpha_i_k + i_h = i_h_0 + K_tilde @ alpha_i_h + c = c_0 + K_tilde @ alpha_c + mu_k = mu_k_0 + K_tilde @ alpha_mu_k + mu_h = mu_h_0 + K_tilde @ alpha_mu_h + dk_dt = K @ alpha_k + dh_dt = K @ alpha_h + dmu_k_dt = K @ alpha_mu_k + dmu_h_dt = K @ alpha_mu_h + + # Cobb-Douglas production and its marginal products as cvxpy expressions. + f = cp.multiply(cp.power(k, a_k), cp.power(h, a_h)) + f_k = a_k * cp.multiply(cp.power(k, a_k - 1), cp.power(h, a_h)) + f_h = a_h * cp.multiply(cp.power(k, a_k), cp.power(h, a_h - 1)) + + # Core RKHS norms on the state/costate coefficients; small smoothing reg on the + # coefficients that only enter via constraints (investments and consumption). + core = ( + cp.quad_form(alpha_mu_k, cp.psd_wrap(K)) + + cp.quad_form(alpha_k, cp.psd_wrap(K)) + + cp.quad_form(alpha_mu_h, cp.psd_wrap(K)) + + cp.quad_form(alpha_h, cp.psd_wrap(K)) + ) + reg = ( + cp.quad_form(alpha_i_k, cp.psd_wrap(K)) + + cp.quad_form(alpha_i_h, cp.psd_wrap(K)) + + cp.quad_form(alpha_c, cp.psd_wrap(K)) + ) + prob = cp.Problem( + cp.Minimize(core + lambda_p * reg), + [ + dk_dt == i_k - delta_k * k, # physical capital accumulation + dh_dt == i_h - delta_h * h, # human capital accumulation + dmu_k_dt == -cp.multiply(mu_k, f_k - delta_k - rho_hat), # physical Euler + dmu_h_dt == -cp.multiply(mu_h, f_h - delta_h - rho_hat), # human Euler + c + i_h + i_k - f == 0.0, # resource feasibility + cp.multiply(mu_k, c) == 1.0, # shadow price + mu_k - mu_h == 0.0, # both capitals priced equally + ], + ) + assert prob.is_dnlp() + solver, options = NLP_SOLVERS[solver_type] + start = time.perf_counter() + try: + prob.solve(nlp=True, solver=solver, verbose=verbose, **options) + assert prob.status in ("optimal", "optimal_inaccurate") + except Exception: + # retry once with relaxed tolerances (mirrors the pyomo IPOPT fallback) + relaxed = {**options} + if solver_type == "IPOPT": + relaxed.update( + tol=1e-6, dual_inf_tol=1e-6, constr_viol_tol=1e-6, acceptable_tol=1e-4 + ) + prob.solve(nlp=True, solver=solver, verbose=verbose, **relaxed) + elapsed = time.perf_counter() - start + print(f"elapsed solve(s) = {elapsed}") + if prob.status not in ("optimal", "optimal_inaccurate"): + print(f"solver status: {prob.status}") + + alpha_c = jnp.array(alpha_c.value) + alpha_k = jnp.array(alpha_k.value) + alpha_h = jnp.array(alpha_h.value) + alpha_i_k = jnp.array(alpha_i_k.value) + alpha_i_h = jnp.array(alpha_i_h.value) + alpha_mu_k = jnp.array(alpha_mu_k.value) + alpha_mu_h = jnp.array(alpha_mu_h.value) + c_0 = float(c_0.value) + i_k_0 = float(i_k_0.value) + i_h_0 = float(i_h_0.value) + mu_k_0 = float(mu_k_0.value) + mu_h_0 = float(mu_h_0.value) + + # Interpolator using training data + def kernel_solution(test_data): + # pointwise comparison test_data to train_data + K_test, K_tilde_test = integrated_matern_kernel_matrices( + test_data, train_data, nu, sigma, rho + ) + c_test = c_0 + K_tilde_test @ alpha_c + k_test = k_0 + K_tilde_test @ alpha_k + h_test = h_0 + K_tilde_test @ alpha_h + i_k_test = i_k_0 + K_tilde_test @ alpha_i_k + i_h_test = i_h_0 + K_tilde_test @ alpha_i_h + mu_k_test = mu_k_0 + K_tilde_test @ alpha_mu_k + mu_h_test = mu_h_0 + K_tilde_test @ alpha_mu_h + feasibility_test = ( + c_test + i_h_test + i_k_test - (k_test**a_k) * (h_test**a_h) + ) + return ( + k_test, + h_test, + c_test, + i_k_test, + i_h_test, + mu_k_test, + mu_h_test, + feasibility_test, + ) + + # Generate test_data + ( + k_test, + h_test, + c_test, + i_k_test, + i_h_test, + mu_k_test, + mu_h_test, + feasibility_test, + ) = kernel_solution(test_data) + + solve_time = prob.solver_stats.solve_time + if solve_time is None: + solve_time = elapsed + print(f"solve_time(s) = {solve_time}") + return { + "t_train": train_data, + "t_test": test_data, + "k_test": k_test, + "h_test": h_test, + "c_test": c_test, + "i_k_test": i_k_test, + "i_h_test": i_h_test, + "mu_k_test": mu_k_test, + "mu_h_test": mu_h_test, + "feasibility_test": feasibility_test, + "alpha_c": alpha_c, + "alpha_k": alpha_k, + "alpha_h": alpha_h, + "alpha_i_k": alpha_i_k, + "alpha_i_h": alpha_i_h, + "c_0": c_0, + "i_k_0": i_k_0, + "i_h_0": i_h_0, + "solve_time": solve_time, + "kernel_solution": kernel_solution, # interpolator + } + + +if __name__ == "__main__": + jsonargparse.CLI(human_capital_matern_cvxpy) diff --git a/optimal_advertising_matern_cvxpy.py b/optimal_advertising_matern_cvxpy.py new file mode 100644 index 0000000..4e0d3d2 --- /dev/null +++ b/optimal_advertising_matern_cvxpy.py @@ -0,0 +1,139 @@ +import time +import jax +import jax.numpy as jnp +import numpy as np +import cvxpy as cp +import jsonargparse +from jax import config +from kernels import integrated_matern_kernel_matrices +from typing import List, Optional + +config.update("jax_enable_x64", True) + +# Pyomo-free DNLP port of optimal_advertising_matern. The advertising-capital +# FOC collocation is nonconvex (the bilinear (1-x)*u and mu*u terms, and the +# u**((1-kappa)/kappa) marginal-cost relation), so it is solved through cvxpy's +# DNLP interface (prob.solve(nlp=True, ...)). solver_type defaults to UNO (unopy, +# in-process, filtersqp preset); cyipopt's interior point diverges on this model's +# free-sign control u, so IPOPT is not offered here. +NLP_SOLVERS = { + "UNO": (cp.UNO, dict(preset="filtersqp")), +} + + +def optimal_advertising_matern_cvxpy( + rho_hat: float = 0.11, + c: float = 0.5, + beta: float = 0.05, + kappa: float = 0.5, + x_0: float = 0.4, + nu: float = 0.5, + sigma: float = 1.0, + rho: float = 15, + solver_type: str = "UNO", + train_T: float = 40.0, + train_points: int = 41, + test_T: float = 50.0, + test_points: int = 100, + benchmark_T: float = 60.0, + benchmark_points: int = 300, + train_points_list: Optional[List[float]] = None, + verbose: bool = False, +): + # if passing in `train_points` then doesn't us a grid. Otherwise, uses linspace + if train_points_list is None: + train_data = jnp.linspace(0, train_T, train_points) + else: + train_data = jnp.array(train_points_list) + test_data = jnp.linspace(0, test_T, test_points) + benchmark_grid = jnp.linspace(0, benchmark_T, benchmark_points) + + # Construct kernel matrices. cvxpy needs numpy (not jax) arrays. + N = len(train_data) + K, K_tilde = integrated_matern_kernel_matrices( + train_data, train_data, nu, sigma, rho + ) + K = np.asarray((K + K.T) / 2) # symmetrize -> exactly PSD for quad_form + K_tilde = np.asarray(K_tilde) + + # Decision variables, initialized at the pyomo starting point (zero kernel + # coefficients, mu_0 = u_0 = 0) so the local NLP solve begins identically. + alpha_x, alpha_mu, alpha_u = cp.Variable(N), cp.Variable(N), cp.Variable(N) + mu_0 = cp.Variable(nonneg=True) + u_0 = cp.Variable(nonneg=True) + alpha_x.value = np.zeros(N) + alpha_mu.value = np.zeros(N) + alpha_u.value = np.zeros(N) + mu_0.value = u_0.value = 0.0 + + # Affine kernel expansions (the pyomo mu/x/u/dmu_dt/dx_dt helpers inline). + # x is the market-share state, mu the costate, u the advertising control. + mu = mu_0 + K_tilde @ alpha_mu + x = x_0 + K_tilde @ alpha_x + u = u_0 + K_tilde @ alpha_u + dmu_dt = K @ alpha_mu + dx_dt = K @ alpha_x + + gamma = (beta + rho_hat) / c + prob = cp.Problem( + cp.Minimize( + cp.quad_form(alpha_mu, cp.psd_wrap(K)) + cp.quad_form(alpha_x, cp.psd_wrap(K)) + ), + [ + dx_dt == cp.multiply(1 - x, u) - beta * x, # market-share dynamics + dmu_dt == -gamma + (rho_hat + beta) * mu + cp.multiply(mu, u), # costate + cp.power(u, (1.0 - kappa) / kappa) - kappa * cp.multiply(mu, 1 - x) + == 0.0, # shadow price (marginal cost of advertising) + ], + ) + assert prob.is_dnlp() + solver, options = NLP_SOLVERS[solver_type] + start = time.perf_counter() + prob.solve(nlp=True, solver=solver, verbose=verbose, **options) + elapsed = time.perf_counter() - start + print(f"elapsed solve(s) = {elapsed}") + if prob.status not in ("optimal", "optimal_inaccurate"): + print(f"solver status: {prob.status}") + + alpha_mu = jnp.array(alpha_mu.value) + alpha_x = jnp.array(alpha_x.value) + alpha_u = jnp.array(alpha_u.value) + u_0 = float(u_0.value) + mu_0 = float(mu_0.value) + + # Interpolator using training data + @jax.jit + def kernel_solution(test_data): + # pointwise comparison test_data to train_data + K_test, K_tilde_test = integrated_matern_kernel_matrices( + test_data, train_data, nu, sigma, rho + ) + mu_test = mu_0 + K_tilde_test @ alpha_mu + x_test = x_0 + K_tilde_test @ alpha_x + u_test = u_0 + K_tilde_test @ alpha_u + return x_test, mu_test, u_test + + # Generate test_data and compare to the benchmark + x_test, mu_test, u_test = kernel_solution(test_data) + + solve_time = prob.solver_stats.solve_time + if solve_time is None: + solve_time = elapsed + print(f"solve_time(s) = {solve_time}") + return { + "t_train": train_data, + "t_test": test_data, + "x_test": x_test, + "mu_test": mu_test, + "u_test": u_test, + "alpha_mu": alpha_mu, + "alpha_x": alpha_x, + "mu_0": mu_0, + "u_0": u_0, + "solve_time": solve_time, + "kernel_solution": kernel_solution, # interpolator + } + + +if __name__ == "__main__": + jsonargparse.CLI(optimal_advertising_matern_cvxpy) diff --git a/pyproject.toml b/pyproject.toml index 6e32d13..9858f60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "Replication Code for Solving Models of Economic Dynamics with Rid readme = "README.md" requires-python = ">=3.13" dependencies = [ + "cvxpy>=1.9.1", "cyipopt>=1.6.1", "jax>=0.7.2", "jinja2>=3.1.6", @@ -13,4 +14,10 @@ dependencies = [ "pandas>=2.3.2", "pyomo>=6.9.4", "quadax>=0.2.9", + "unopy>=0.4.10", +] + +[project.optional-dependencies] +gurobi = [ + "gurobipy>=13.0.2", ] diff --git a/tables_neoclassical_growth.py b/tables_neoclassical_growth.py index 0754bd9..89da762 100644 --- a/tables_neoclassical_growth.py +++ b/tables_neoclassical_growth.py @@ -1,44 +1,59 @@ -import pandas as pd - -from neoclassical_growth_matern import neoclassical_growth_matern - -sol_default = neoclassical_growth_matern() -sol_nu_1_5 = neoclassical_growth_matern(nu=1.5) -sol_nu_2_5 = neoclassical_growth_matern(nu=2.5,lambda_p = 1e-4) -sol_rho_2 = neoclassical_growth_matern(rho=2) -sol_rho_20 = neoclassical_growth_matern(rho=20) - -k_rel_error = sol_default["k_rel_error"] -c_rel_error = sol_default["c_rel_error"] - -sols = [sol_default, sol_nu_1_5, sol_nu_2_5, sol_rho_2, sol_rho_20] - -df = pd.DataFrame( - { - r"$\nu$": [r"$1/2$", r"$3/2$", r"$5/2$", r"$1/2$", r"$1/2$"], - r"$\ell$": [10, 10, 10, 2, 20], - r"Max of Rel. Error: $\hat{x}(t)$": [ - sol["k_rel_error"].max().item() for sol in sols - ], - r"Max of Rel. Error: $\hat{y}(t)$": [ - sol["c_rel_error"].max().item() for sol in sols - ], - r"Min of Rel. Error: $\hat{x}(t)$": [ - sol["k_rel_error"][1:].min().item() for sol in sols - ], - r"Min of Rel. Error: $\hat{y}(t)$": [ - sol["c_rel_error"].min().item() for sol in sols - ], - } -) - -with open("figures/neoclassical_growth_model_nu_rho.tex", "w") as f: - f.write(df.to_latex(index=False, float_format="%.1e")) - - -# r"Avg. of Rel. Error: $\hat{k}(t)$": [ -# sol["k_rel_error"].mean().item() for sol in sols -# ], -# r"Avg. of Rel. Error: $\hat{c}(t)$": [ -# sol["c_rel_error"].mean().item() for sol in sols -# ], +import pandas as pd +import jsonargparse + +from neoclassical_growth_matern import neoclassical_growth_matern +from neoclassical_growth_matern_cvxpy import neoclassical_growth_matern_cvxpy + + +# implementation selects the solve backend: "cvxpy" (default, DNLP via UNO) +# or "pyomo" (Ipopt binary). +def main(implementation: str = "cvxpy"): + solve = { + "cvxpy": neoclassical_growth_matern_cvxpy, + "pyomo": neoclassical_growth_matern, + }[implementation] + + sol_default = solve() + sol_nu_1_5 = solve(nu=1.5) + sol_nu_2_5 = solve(nu=2.5, lambda_p=1e-4) + sol_rho_2 = solve(rho=2) + sol_rho_20 = solve(rho=20) + + k_rel_error = sol_default["k_rel_error"] + c_rel_error = sol_default["c_rel_error"] + + sols = [sol_default, sol_nu_1_5, sol_nu_2_5, sol_rho_2, sol_rho_20] + + df = pd.DataFrame( + { + r"$\nu$": [r"$1/2$", r"$3/2$", r"$5/2$", r"$1/2$", r"$1/2$"], + r"$\ell$": [10, 10, 10, 2, 20], + r"Max of Rel. Error: $\hat{x}(t)$": [ + sol["k_rel_error"].max().item() for sol in sols + ], + r"Max of Rel. Error: $\hat{y}(t)$": [ + sol["c_rel_error"].max().item() for sol in sols + ], + r"Min of Rel. Error: $\hat{x}(t)$": [ + sol["k_rel_error"][1:].min().item() for sol in sols + ], + r"Min of Rel. Error: $\hat{y}(t)$": [ + sol["c_rel_error"].min().item() for sol in sols + ], + } + ) + + with open("figures/neoclassical_growth_model_nu_rho.tex", "w") as f: + f.write(df.to_latex(index=False, float_format="%.1e")) + + +if __name__ == "__main__": + jsonargparse.CLI(main) + + +# r"Avg. of Rel. Error: $\hat{k}(t)$": [ +# sol["k_rel_error"].mean().item() for sol in sols +# ], +# r"Avg. of Rel. Error: $\hat{c}(t)$": [ +# sol["c_rel_error"].mean().item() for sol in sols +# ], diff --git a/uv.lock b/uv.lock index 4988d85..54195a5 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,69 @@ version = 1 revision = 3 requires-python = ">=3.13" +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "clarabel" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/e2/47f692161779dbd98876015de934943effb667a014e6f79a6d746b3e4c2a/clarabel-0.11.1.tar.gz", hash = "sha256:e7c41c47f0e59aeab99aefff9e58af4a8753ee5269bbeecbd5526fc6f41b9598", size = 253949, upload-time = "2025-06-11T16:49:05.864Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/f7/f82698b6d00a40a80c67e9a32b2628886aadfaf7f7b32daa12a463e44571/clarabel-0.11.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c39160e4222040f051f2a0598691c4f9126b4d17f5b9e7678f76c71d611e12d8", size = 1039511, upload-time = "2025-06-11T16:48:58.525Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8f/13650cfe25762b51175c677330e6471d5d2c5851a6fbd6df77f0681bb34e/clarabel-0.11.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8963687ee250d27310d139eea5a6816f9c3ae31f33691b56579ca4f0f0b64b63", size = 935135, upload-time = "2025-06-11T16:48:59.901Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/7af10d2b540b39f1a05d1ebba604fce933cc9bc0e65e88ec3b7a84976425/clarabel-0.11.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4837b9d0db01e98239f04b1e3526a6cf568529d3c19a8b3f591befdc467f9bb", size = 1079226, upload-time = "2025-06-11T16:49:00.987Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a9/c76edf781ca3283186ff4b54a9a4fb51367fd04313a68e2b09f062407439/clarabel-0.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8c41aaa6f3f8c0f3bd9d86c3e568dcaee079562c075bd2ec9fb3a80287380ef", size = 1164345, upload-time = "2025-06-11T16:49:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/4eee3062088c221e5a18b054e51c69f616e0bb0dc1b0a1a5e0fe90dfa18e/clarabel-0.11.1-cp39-abi3-win_amd64.whl", hash = "sha256:557d5148a4377ae1980b65d00605ae870a8f34f95f0f6a41e04aa6d3edf67148", size = 887310, upload-time = "2025-06-11T16:49:04.277Z" }, +] + [[package]] name = "contourpy" version = "1.3.3" @@ -57,6 +120,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, ] +[[package]] +name = "cvxpy" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "clarabel" }, + { name = "highspy" }, + { name = "numpy" }, + { name = "osqp" }, + { name = "qdldl" }, + { name = "scipy" }, + { name = "scs" }, + { name = "sparsediffpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/52/be03c63aa2f101ea6b50d676bd4618c77b17d4b0ce3e173cf306fb805dc4/cvxpy-1.9.1.tar.gz", hash = "sha256:4504d44011e0ef21348db1d8d1d2784d39742c646853fed13894d9b2292ae853", size = 1889168, upload-time = "2026-05-26T22:15:56.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/a3/f43c4ab773193ec690e82536f136d35132243892bea9712f276bad6c4694/cvxpy-1.9.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b5b367dda7851414295805900c131af9df910aa69ab0e86a54138d64be24fc96", size = 1606295, upload-time = "2026-05-26T22:09:45.747Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cf/c96e988298093cc8c01ade4078d0d3ba5940757351da5a57967aa8e4f521/cvxpy-1.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd9e3f5cc6aa39c1537dacdd159806e21b388c8b7e73093c8c98365ca60ae527", size = 1396098, upload-time = "2026-05-26T22:09:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/12/34/b05bf8e6d58f9e0bd9a548d2fc71a544fe6f13b4204f975929054cb3053b/cvxpy-1.9.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7760ddc04bad6ee8b3632d4b8fa092fb5bea3a00750e01e21d57f1aedfdd01", size = 4348103, upload-time = "2026-05-26T22:13:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7c/8cd7c88a905307f96cf21e2498d159f452a886f412966371db38932de666/cvxpy-1.9.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99f489ad58fb9ac27b02c38ff5eb5d6f2852c6cb7ae2cb98c58846c707d7237f", size = 4397182, upload-time = "2026-05-26T22:13:19.13Z" }, + { url = "https://files.pythonhosted.org/packages/ed/48/5a8f0e9e9c421005c570d7060b6cf2a6c70fd5c5e99dbb9eb31b9ac92ac3/cvxpy-1.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:e824f0b626e6ec56d84901e56cfbbe067f4397390cbcfe7438dbeafdee4463c9", size = 1356989, upload-time = "2026-05-26T22:11:35.864Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e6/ef14cf6f78406070095a532500d3c433f5d2b492f5e6fec1ecc1f38893e5/cvxpy-1.9.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b3a3fb63f284b13db893f7245fc8c1fddf20a3d3d415d85634b2cc107b0261d7", size = 1607137, upload-time = "2026-05-26T22:07:28.93Z" }, + { url = "https://files.pythonhosted.org/packages/a3/13/cf8a4db898e674239d1517833f7d099fd1568bdaa0a02ddbdfe8b4a99731/cvxpy-1.9.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d84447e850664aca7e6c17fa37fc7072d97d1de8c1b89896702b955ece7787fb", size = 1396560, upload-time = "2026-05-26T22:07:30.06Z" }, + { url = "https://files.pythonhosted.org/packages/bf/96/bbe9c41e6a95bda5a2cbb1010377efa1762be3dbfb4b2be10788d914f277/cvxpy-1.9.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91e412469037dc0b775ffc2315f8022fd8f15d8f37108847ea31520f93d78cfd", size = 4344740, upload-time = "2026-05-26T22:09:34.405Z" }, + { url = "https://files.pythonhosted.org/packages/8a/b8/ffbf8d0ee8e89ff10530fc91fcc54adf95848da43db2abba37b1a8a59a77/cvxpy-1.9.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69cff046b34c568a5911b36a6122085b0685102e7cf5cf845b6ab31adeb93478", size = 4392772, upload-time = "2026-05-26T22:09:36.103Z" }, + { url = "https://files.pythonhosted.org/packages/94/62/c842a85cd6d542952a59b8badeaadfb8c14c83428e2062ff62379b9dc598/cvxpy-1.9.1-cp314-cp314-win_amd64.whl", hash = "sha256:649c637095d77fd6ab998f3792a425071df30e49e641e4ea459ed3ee01cb487c", size = 1362971, upload-time = "2026-05-26T22:10:03.895Z" }, + { url = "https://files.pythonhosted.org/packages/a6/48/b5a223c97a2ef5e5bc9332a5209d02d7a6ea74758f93354962ed465a9d61/cvxpy-1.9.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:88fa05388e70ed49c92bae4251ce722f2170c3f7b7defd54f943d0002b509d12", size = 1614659, upload-time = "2026-05-26T22:05:08.035Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f5/e7b3bb8958d7e1f554493d8fcd5cee58ca01244c695c891c2e6b85b56c0f/cvxpy-1.9.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f0adc20ec54b6d7bcb26aeb82e4ef408cf62506420213177f05f3e1a4694bc07", size = 1400901, upload-time = "2026-05-26T22:05:10.175Z" }, + { url = "https://files.pythonhosted.org/packages/db/67/183bafeebdf150baabf223c45779fa6002b9fb53e67ce604ced297a08b5b/cvxpy-1.9.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee9969fbba34749d026983b14b8882ae023619b5d95c5bdc3bc5fa3dca47a738", size = 4332292, upload-time = "2026-05-26T22:14:12.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/3c/659638af87e56f5d7fd1b65b9e9976379e8d410c93338bf134e19334e927/cvxpy-1.9.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca433d097adae8b2c6d017c9d9ca95eac7fe41f59accd8cb07800e10b9543255", size = 4384057, upload-time = "2026-05-26T22:14:14.672Z" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -123,6 +218,56 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/a4/247d3e54eb5ed59e94e09866cfc4f9567e274fbf310ba390711851f63b3b/fonttools-4.60.0-py3-none-any.whl", hash = "sha256:496d26e4d14dcccdd6ada2e937e4d174d3138e3d73f5c9b6ec6eb2fd1dab4f66", size = 1142186, upload-time = "2025-09-17T11:33:59.287Z" }, ] +[[package]] +name = "gurobipy" +version = "13.0.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/de/d32f793c5c208f34b289049c05fbc003ed5c6818e7e84c113651992cc923/gurobipy-13.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6cc4be757540f48b3f206427cacbcd78f5e8c07dddd8654e3b1c6e3b87327082", size = 15978090, upload-time = "2026-05-05T10:43:18.396Z" }, + { url = "https://files.pythonhosted.org/packages/a5/11/77458930745fb661a0b5aea39211101a22c5413161bb10f954f015af3f70/gurobipy-13.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d7840fdf7928a6faf9fff795dde727940164e87d0bced98075df1ead38b12f9", size = 14866672, upload-time = "2026-05-05T10:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/79/08/70c909650ae684a4d8468fec8b2e5d2f3be5b7f602e48b3cffd1e67ee6cc/gurobipy-13.0.2-cp313-cp313-manylinux_2_26_aarch64.whl", hash = "sha256:407f87b87b556b2d73f738ca4b9a41c3aed0ee412958a68b127d2108a8c300e2", size = 87208386, upload-time = "2026-05-05T10:43:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7f/e68bd10b04c87f60d82f0d81340cf23e6e8d5e012dbac474dffbdf990657/gurobipy-13.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:5b26c7ab11fd406f6ecf10958728b9f9344b818fee86db5b516a46aefe8400f2", size = 11244495, upload-time = "2026-05-05T10:43:29.624Z" }, + { url = "https://files.pythonhosted.org/packages/83/ae/0481b8f6d883b1907713bb9f92313257d3f1b7608f02d73f27d6c39516ae/gurobipy-13.0.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a827eff3ae926b67625026b80365007ab7be398e60e188753268dc1f0e876b3e", size = 15848186, upload-time = "2026-05-05T10:43:32.11Z" }, + { url = "https://files.pythonhosted.org/packages/30/ce/b62840162e24e03bd55fc265882fb9eb602d47a4dc27a9b7775c4741993e/gurobipy-13.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f82340708955c24ab20f5d41b4d7fbd0e94cf4e7740841360e2024c9d222961f", size = 14766019, upload-time = "2026-05-05T10:43:34.359Z" }, + { url = "https://files.pythonhosted.org/packages/27/b9/467bce6c7b03883b8dc1646756b5c870c7a156c89cf13ca32dfcee0cc7b3/gurobipy-13.0.2-cp314-cp314-manylinux_2_26_aarch64.whl", hash = "sha256:f7714d69d778af882d756e222340920e8dbfc004a509998604cb9b4cb1d43242", size = 87137188, upload-time = "2026-05-05T10:43:38.447Z" }, + { url = "https://files.pythonhosted.org/packages/1a/50/22546477a0277d1eaf7f67da5629c27782b63b85daeeabf394ce52627cac/gurobipy-13.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:443d79ab626c383f01f0ea56c37533c9023e4d65abf8356563d2cf8da513001d", size = 11465255, upload-time = "2026-05-05T10:43:53.498Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/06e98babc2b0df16f2e42e52a222b4f880bd0a59e7e025ced25c47b7aeae/gurobipy-13.0.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:57c7c129f9c305296c12130bb0d8f0157785e4aaf5cee501174b8320a3a9195d", size = 16161861, upload-time = "2026-05-05T10:43:41.663Z" }, + { url = "https://files.pythonhosted.org/packages/42/21/6b0ceecfc45ebed38c5f9f3d87441baad8832743eaa85a31ac09ba4543e7/gurobipy-13.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:29a7449544e9b17def147b8c77d0eb30c8f343e2900bc0b004877b903ed41595", size = 14761111, upload-time = "2026-05-05T10:43:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d4/b3b915fdce536d1331d45305cc55e8952694109377cbfe4e82c1fa38c986/gurobipy-13.0.2-cp314-cp314t-manylinux_2_26_aarch64.whl", hash = "sha256:8207e3503a74c43a19c120c6c117a5c6892d060e5f803fe072209be127a984e0", size = 87131303, upload-time = "2026-05-05T10:43:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/b3/6b/2532e54305cf3a905f4df5a050d51f066391829f18f3c167a6ccd15da28d/gurobipy-13.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9e2f956649fa46304a5aa2d18dbccfdb6d742092db2f0f798d37a4c3ccca5e99", size = 11906319, upload-time = "2026-05-05T10:43:50.882Z" }, +] + +[[package]] +name = "highspy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/66/e74b1a805f65c52666e3b54cfc1ba783e745c2c8a7abaae9e7ef2d9e7270/highspy-1.14.0.tar.gz", hash = "sha256:b09cb5e3179a25fc615b8b0941130b0f71e19372c119f3dd620d63b54cd3ca4c", size = 1654913, upload-time = "2026-04-06T15:53:31.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/2e/43b5d51852a8b06a079cc324ee877a91d2e87128ecd99905b45840b0fd3e/highspy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:777ce930bc29a984826bc4e65ca7fec0407284a30877bc0d179dacc6bb7ee972", size = 2311865, upload-time = "2026-04-06T15:52:24.112Z" }, + { url = "https://files.pythonhosted.org/packages/1d/49/7e1a308163954faa3e91cbc0f73282a24d99b9cb6e7314c6f4266fee88d5/highspy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:479be627bbdf7646dccf5621059b9416e6480c3ffa16998e6cc4de89c40a3716", size = 2122334, upload-time = "2026-04-06T15:52:25.938Z" }, + { url = "https://files.pythonhosted.org/packages/87/8c/8ae1a6f3f645deeaaf5522ebd47c61f7d856e8883dd5e7f51318e00ca287/highspy-1.14.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863a9363b624d0ef0a5a5bfcbe437bcd3891676c8b7c4d801462320b1387ef37", size = 2409256, upload-time = "2026-04-06T15:52:27.306Z" }, + { url = "https://files.pythonhosted.org/packages/7c/40/501586f760677501f3b2f19f0b7236515d06c08db29ccaae838a6f20c05d/highspy-1.14.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba290153919da6368e9144189060a496077a738522555e89403504d379e8d63f", size = 2632208, upload-time = "2026-04-06T15:52:29.122Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/a4137e3fd564fe2615093771463d66fa74cc4a2a94e6b68c15a0f8572218/highspy-1.14.0-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:bdc317bc2572a591cf9f0f2415d4b0879f9f0a47bdff3cda44f85a943941e64b", size = 2792091, upload-time = "2026-04-06T15:52:30.586Z" }, + { url = "https://files.pythonhosted.org/packages/af/91/aa3d6758212f4bb14c4c424053932a030ead226f8138da396a0bb6216d66/highspy-1.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:02a987e0020bf28757df9efbdfd6abde6d501b64294f8201301958095dae7979", size = 3466025, upload-time = "2026-04-06T15:52:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/64/10/25cd0fe6b0dfbae39bee2b7a6dfb91e29b1c78a338e537eb5fc8475ea03d/highspy-1.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:03c9b39a9ca8fe973eb89093ac386a8606ac37beb0e637b1a9255e31bfe87ef3", size = 4043109, upload-time = "2026-04-06T15:52:34.203Z" }, + { url = "https://files.pythonhosted.org/packages/8c/27/4dbcba90d2c8b2ddcd3157d62f5531c2bfa75a3e360b8f05ac52996b489b/highspy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a92b50e7508b90c6d65b315b7dd3b37c6d0db331e698052f7054da523e446eb4", size = 3712794, upload-time = "2026-04-06T15:52:36.163Z" }, + { url = "https://files.pythonhosted.org/packages/de/5a/72d9840b26b0b26916bd7624d606f31a0ce83f14709fcfaf52df31e29898/highspy-1.14.0-cp313-cp313-win32.whl", hash = "sha256:e8eb72be8766717ec856cf5fa3fcadd8ddd59bee0f4d71d4f45ae0a2b861795f", size = 1955044, upload-time = "2026-04-06T15:52:37.532Z" }, + { url = "https://files.pythonhosted.org/packages/22/7e/89f07a03ff9f5460043ba4815583fd3fda22aa6d088be3fb730346ccae63/highspy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:3d15faaab62b408320373540bfd5a7b9a48e9a01072b847f2270b8d5d881647c", size = 2323514, upload-time = "2026-04-06T15:52:38.939Z" }, + { url = "https://files.pythonhosted.org/packages/27/d4/2658ccfef1c31e25a29e337c9ede3107394fad0a9535820a096cd50ad055/highspy-1.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:cc9dfde9ad829f3463627dab84152ceb4c30c08b89b19226bf8f69d47a7fed5d", size = 2317451, upload-time = "2026-04-06T15:52:40.659Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f8/7d9be61c80a6daa782bf51b4324bf8425896bf120809ca804ad08f69d12e/highspy-1.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fc8ad7a6fc0a44c99b9aa3a3d0c3a917b50dfa8c61e332cb2ee1f7ea4c234e4f", size = 2121732, upload-time = "2026-04-06T15:52:42.189Z" }, + { url = "https://files.pythonhosted.org/packages/01/eb/47f960ccb56986c2c9ef4ce9f293bae95de7c22a662f6c45b844c316d7ff/highspy-1.14.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39339a7a000998ab26eb87add814222fcdc304d38cfba2f6e098189b53ef0a79", size = 2410647, upload-time = "2026-04-06T15:52:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/2d/38/3b37047686105955e2d54ec753c3b9e9d135bdd8e5ee1df310a87be25472/highspy-1.14.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8f31f2635517c7d370be612a1e9102481483c749b9db786760dd489fad22519", size = 2632928, upload-time = "2026-04-06T15:52:45.373Z" }, + { url = "https://files.pythonhosted.org/packages/23/73/2a6673db80a5388f364d0f7218af08aebbfc05c866d1b4cc2ec79bc4d822/highspy-1.14.0-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:68b944014ce307e24921c3bf904253d8849799ded819bda39a4b09359074ea0a", size = 2791407, upload-time = "2026-04-06T15:52:47.051Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/75f862b96420dd77a5f88d6401212534833b99aa0ef971d4eddfd227f913/highspy-1.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:03538f68dff2038582d8aa7f5d05690ce459435f498ea0022869fe962d70d8ae", size = 3471297, upload-time = "2026-04-06T15:52:48.892Z" }, + { url = "https://files.pythonhosted.org/packages/ed/17/564c24dcc05d6f11876485654956dfb6b2f4ba17f21ecafe84b059a17ab4/highspy-1.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a54d687522347348639a62df270f45546a3cbd84a6cd230dc41732e9559c766f", size = 4045124, upload-time = "2026-04-06T15:52:50.383Z" }, + { url = "https://files.pythonhosted.org/packages/b1/34/a611fe3271be165fb13a7b85970820579515d652bd59f69aed001ed2ff98/highspy-1.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88db4d1ebefb119991ff16adb624c682ebc20fc586c86156843cb4be7c965d8a", size = 3713436, upload-time = "2026-04-06T15:52:51.849Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0d/001726678facdd7ca435d430bf039732cc50d77fdbd6231fd3bac428893b/highspy-1.14.0-cp314-cp314-win32.whl", hash = "sha256:7a85730676ffc88eadca1721252bec168f6ffc0423f6141f6ad41f79bb441327", size = 1999958, upload-time = "2026-04-06T15:52:54.154Z" }, + { url = "https://files.pythonhosted.org/packages/a4/4d/c7f2b5c23c4b7103095a9959add5119c5653c17c6bc7817fd003bd3ba8c6/highspy-1.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:90b7074d4bc34a4390636aaf9e4232ae15d4536098f1f1f39f04329c08751148", size = 2410786, upload-time = "2026-04-06T15:52:56.161Z" }, +] + [[package]] name = "jax" version = "0.7.2" @@ -189,6 +334,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + [[package]] name = "jsonargparse" version = "4.41.0" @@ -206,6 +360,7 @@ name = "kernel-econ-alignment" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "cvxpy" }, { name = "cyipopt" }, { name = "jax" }, { name = "jinja2" }, @@ -214,11 +369,19 @@ dependencies = [ { name = "pandas" }, { name = "pyomo" }, { name = "quadax" }, + { name = "unopy" }, +] + +[package.optional-dependencies] +gurobi = [ + { name = "gurobipy" }, ] [package.metadata] requires-dist = [ + { name = "cvxpy", specifier = ">=1.9.1" }, { name = "cyipopt", specifier = ">=1.6.1" }, + { name = "gurobipy", marker = "extra == 'gurobi'", specifier = ">=13.0.2" }, { name = "jax", specifier = ">=0.7.2" }, { name = "jinja2", specifier = ">=3.1.6" }, { name = "jsonargparse", specifier = ">=4.41.0" }, @@ -226,7 +389,9 @@ requires-dist = [ { name = "pandas", specifier = ">=2.3.2" }, { name = "pyomo", specifier = ">=6.9.4" }, { name = "quadax", specifier = ">=0.2.9" }, + { name = "unopy", specifier = ">=0.4.10" }, ] +provides-extras = ["gurobi"] [[package]] name = "kiwisolver" @@ -450,6 +615,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932, upload-time = "2024-09-26T14:33:23.039Z" }, ] +[[package]] +name = "osqp" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/09/fb28f57d6eba067fbb6c941c9136f8d2e41b3d4fd4ddac643cf734210085/osqp-1.1.1.tar.gz", hash = "sha256:1719e6a88f2ec2bd5dab06131331d1433152fb222372832727d9eb5604d7acf4", size = 57059, upload-time = "2026-02-11T18:15:45.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/ea/4717391fc0a611913895ca8cf585ebbdcb989ddd2018663c8398b303428c/osqp-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da548997e7187b1b55358ef291fbb3f9d29b6103917bedbbe77ab8d2307a43a", size = 321447, upload-time = "2026-02-11T18:08:55.612Z" }, + { url = "https://files.pythonhosted.org/packages/16/45/efd9e1233468778040748b3675a51ef17f227eb353f749a455d38f9b1ffb/osqp-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e33d9de8e6d68a77ef5ca3fee77d42fac89fb5c3fbac3ab5df452176009d28be", size = 302215, upload-time = "2026-02-11T18:08:56.734Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6a/a4f27e087f9ab46eb8171272f31d6c01477fe895e56038cb6550387c1787/osqp-1.1.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c45eb7a2ef39751417d964c792f3bfe396642b8bc1ae6eca7b28aaa7398ca5", size = 322726, upload-time = "2026-02-11T18:08:58.3Z" }, + { url = "https://files.pythonhosted.org/packages/70/97/6e1a593f0292f31e8abd6a1b8d8bd87443635b56064fb60bd6a71ae5e207/osqp-1.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3c726d8516d90b2d6acb47acf0bef248188119c52692cca307e418f0f2d8fad", size = 345685, upload-time = "2026-02-11T18:08:59.938Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/1bc9acd0183177c5a3cb7e729f0543513f58d3edd5ec24198144b39df218/osqp-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1bd86a9fb19f484705acdff47ec89d68af5c12fba9def921df503bc6bca8e39", size = 310834, upload-time = "2026-02-11T18:09:01.049Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bd/3ed5180c89c35da4a5de5e468a47df217572ec529ce07fcbc1f6a0bad21b/osqp-1.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:42315f8047708c7a2ae184df2255a2b5d323164e67a20df5c03ecd9b4208f2f7", size = 321887, upload-time = "2026-02-11T18:09:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9e/8e2215ef3755ac728dcaa32e3a9fab7e9900dcd6dfd2ddcc5893551681b4/osqp-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:610a4ecba7a274348f95eeb3c6d56d131207482b6ad95bd20e2a5e4f87111887", size = 302669, upload-time = "2026-02-11T18:09:03.362Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/53eebc2493c81c240def661dfa1b9feeaa0dc88989f7055467aee77420b4/osqp-1.1.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1532b0ade13cb10d8875e121e6131448528fb79e931ffb5dccef555b26b464e", size = 323424, upload-time = "2026-02-11T18:09:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/ea/05/dd94a55c32a9fc72aa0e4fbea33b9894414bb422c285a23729a02ea2a3f4/osqp-1.1.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1ee59dbda22d283de001e7948f7523f509279f7131d5abf0e53fc5ab66b8bb0", size = 345972, upload-time = "2026-02-11T18:09:06.07Z" }, + { url = "https://files.pythonhosted.org/packages/8a/c4/d6c1d030b6df9233ee5b50d32fcb6a8c720891d88f674810f934a380bc93/osqp-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:514b2e1d14b5bad9a91ff4dbcbad8da75ef4fb5eee18864e0bbbb620fc6dbcd7", size = 316454, upload-time = "2026-02-11T18:09:07.723Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -550,6 +740,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, ] +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + [[package]] name = "pyomo" version = "6.9.4" @@ -613,6 +812,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] +[[package]] +name = "qdldl" +version = "0.1.9.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/4e/452984a63df9421cf8e7d25e8e6a44832cf0247a5e7b65e437cd516a0f8f/qdldl-0.1.9.post1.tar.gz", hash = "sha256:da2016d541c26cefc79bca4d8b5bebfa00f35db19704abb20efbd1c08df3b4c7", size = 76295, upload-time = "2026-02-19T16:48:36.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/f6/5ab7f37e7607396eb7a61736471b54be881fc5697bb18e57992fb14a0867/qdldl-0.1.9.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00331a5e45cc60bf6f75f027ae2a2c137295de0b5e1a0af358a37aaa07427476", size = 122503, upload-time = "2026-02-19T16:47:57.435Z" }, + { url = "https://files.pythonhosted.org/packages/e1/49/144ec3c6b7cfe650191dac35a21d93eebfcc043a3ecd8b499adb3b7cec1d/qdldl-0.1.9.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbfb46f7290917146b076449fb3fee1eefdf018e710c7033c11739ae1723da07", size = 117760, upload-time = "2026-02-19T16:47:58.407Z" }, + { url = "https://files.pythonhosted.org/packages/82/a1/f8d58f100140d416f40912a7bf8bc28ab8276e7e8e6a24a8889a4e5c895a/qdldl-0.1.9.post1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e881f51dc779441c0910dc5a9d89bfd40a459daaf8088934aebf6f4c84adf1e0", size = 1448988, upload-time = "2026-02-19T16:47:59.377Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7d/6d621819d3340f2cb4555e5263481be1e741578634d09569c67acc186ff0/qdldl-0.1.9.post1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc0f30e521da345d25e4fa5308fda4964e25beacd10e8de9f08d084a600a42e2", size = 1476075, upload-time = "2026-02-19T16:48:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/76/97/423a7cbe11add0b7b6afe97c9d6e934776c632a8c6e1d2a457c1f9dffbf6/qdldl-0.1.9.post1-cp313-cp313-win_amd64.whl", hash = "sha256:27b02a730c39b2dba205bb5c08dca5deb994f655f6eeacee687ac006c50b3366", size = 104586, upload-time = "2026-02-19T16:48:01.586Z" }, + { url = "https://files.pythonhosted.org/packages/ca/dc/425dfec0bd5a18ad43790764f091567fba18d63d9f9d6b202a796a8fd9ca/qdldl-0.1.9.post1-cp313-cp313-win_arm64.whl", hash = "sha256:98a13e234ea335484cf76441b95638d0f9ecdd43242dd5a073f77c758977d980", size = 98954, upload-time = "2026-02-19T16:48:02.486Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a4/bc38a8f2edd88adc3bd87a53493e3ddfcab1c173a0310dcfe7f56063d254/qdldl-0.1.9.post1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:576edaf8b847d087806e3d2a860a06d52a435aa3192b21bbc39e69a55187805d", size = 129346, upload-time = "2026-02-19T16:48:03.325Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/0b157bd706ee91838d22d57dd0bd3a7ea0837142fb97b5df3d8acfad9f6c/qdldl-0.1.9.post1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:547f73046ef615827317fbedb21b5e1b11f5082d304e8d775e9e17d6c447a598", size = 124236, upload-time = "2026-02-19T16:48:04.313Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/185e0620e424fab6eeafd3e37f37723e72c80a795419556575d47b27d4c5/qdldl-0.1.9.post1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cb2e82b4f40cb18638da47adf491a016d76d0d82f25377c4c330453a8785f94", size = 1494339, upload-time = "2026-02-19T16:48:05.364Z" }, + { url = "https://files.pythonhosted.org/packages/1a/55/0982ba5565863e3fa4306891158cb509088a875e76a5eeb683744d8a1610/qdldl-0.1.9.post1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ddd5b43b760ffb4cc85ca1acfb0eb139bff78bcc7bd30cad4c55564074685d4", size = 1517878, upload-time = "2026-02-19T16:48:06.517Z" }, + { url = "https://files.pythonhosted.org/packages/79/bf/b75b0fee2ea07e0dd2c8824356cd78200ac429172fa24f12a2cf84f95bd8/qdldl-0.1.9.post1-cp313-cp313t-win_amd64.whl", hash = "sha256:0fba64949c3197c6691c8fe4c3fbcb4f95d6e85b314824ba75981efcdf06d98c", size = 113642, upload-time = "2026-02-19T16:48:07.858Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/cc4bfa56f8d29a5ed7956c97ede9dfc4ec408f61e4b75ecd9e19232a64a8/qdldl-0.1.9.post1-cp313-cp313t-win_arm64.whl", hash = "sha256:31296314679c6ccfad8760704dbc9e25b5e49fd442f803638a2aaa1886724f7d", size = 104321, upload-time = "2026-02-19T16:48:08.79Z" }, + { url = "https://files.pythonhosted.org/packages/08/28/9b991fdcc16569b1bc7119ae1f62495c948033d791d469c031058d7b2c4c/qdldl-0.1.9.post1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0d69a8d011f47f287c5e7668b92b9e3574798b9687bc751af6f89c15037aa26e", size = 122733, upload-time = "2026-02-19T16:48:09.694Z" }, + { url = "https://files.pythonhosted.org/packages/18/92/56437eb5a31edb9275450dac94a94f7df252147527974ef50ba56cbb8d66/qdldl-0.1.9.post1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:496bebf154c002fb7a36443b9a26cc0f7251e8e9251d3742590300cd2edec7a8", size = 117977, upload-time = "2026-02-19T16:48:10.606Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/9594e0cea5aaf33c2579f722ad02e5117ede62e99aca62245f2c2d3df0d5/qdldl-0.1.9.post1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1922c11ddb59419ab969cd1a069e85da9e37db9b14d92b0900e73be94d25c318", size = 1448404, upload-time = "2026-02-19T16:48:11.497Z" }, + { url = "https://files.pythonhosted.org/packages/bf/85/c5e380aeaa3f5d61cbf21b815b3caac5909a165c747aadd5675500fa92dc/qdldl-0.1.9.post1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8c0148de01346722ef07ce9434485b97b8f3aa7042cac5c341b58a728fe7588", size = 1474644, upload-time = "2026-02-19T16:48:12.831Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a7/a3b62d9ec100604778d0596910f4bd20eaf23445af20bdd1d3cac77b6632/qdldl-0.1.9.post1-cp314-cp314-win_amd64.whl", hash = "sha256:9adb8625012e96ceb6c24a8278b8aa9477727ae8fde35dee730988747ab95144", size = 107693, upload-time = "2026-02-19T16:48:14.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/ec/55b21669cad250f55fee6a8ae0fe65dfb547baeb3463f576e8642795e392/qdldl-0.1.9.post1-cp314-cp314-win_arm64.whl", hash = "sha256:0db9f197fb51c6fa96ff1964707e2ba9a89b7b0d91c305e6e0b668e1bcb66fbf", size = 102546, upload-time = "2026-02-19T16:48:15.226Z" }, + { url = "https://files.pythonhosted.org/packages/43/e8/09c59c9f4a1df9b2b415cbc13eb0daadab418e252c698f622d94e4dcb026/qdldl-0.1.9.post1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:25da2bcd44dd272435db3b4208b47943837503f43db4d24c02e5e15b7d5ddf33", size = 129500, upload-time = "2026-02-19T16:48:16.221Z" }, + { url = "https://files.pythonhosted.org/packages/86/03/dcf73d806a4c3f0250a56cc56dafadc2dfacccf5df9caf8f7f181a5e3a89/qdldl-0.1.9.post1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e3438e9cb3f8a26531a24f25bb05152a7446112f4c8ff62d537debfb8147435c", size = 124224, upload-time = "2026-02-19T16:48:17.17Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2e/7770f0ad70c6cc834041c2c19262c3315d312d0d3e72ac33ecb7c94f2f9a/qdldl-0.1.9.post1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a2aed01dbdc705bcab71270ce20753a016bd7b112ea7e06d1166d897e7483cb", size = 1485196, upload-time = "2026-02-19T16:48:18.415Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6d/f1919ae97e1482484239edcda8b4f29aaba11817c808c775403e8a35aa54/qdldl-0.1.9.post1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd08bbb431f138c2946a0d99cb70fbfe67435c8b3dc1dbf23e4e80edfd2b1456", size = 1509644, upload-time = "2026-02-19T16:48:20.273Z" }, + { url = "https://files.pythonhosted.org/packages/7a/33/89ca4741cb651385a12600c0e93d24ad20eef5a954bb2b7ddc596442fc8c/qdldl-0.1.9.post1-cp314-cp314t-win_amd64.whl", hash = "sha256:213f6125564f61d9597d5d36c5556d4c898225bc31a99f8c87fd549b2e65b60a", size = 117569, upload-time = "2026-02-19T16:48:21.459Z" }, + { url = "https://files.pythonhosted.org/packages/63/48/34fa827457aa0e373fb50dab4490757350076f9afcf7eb749a71926023af/qdldl-0.1.9.post1-cp314-cp314t-win_arm64.whl", hash = "sha256:fcc2184cac1e502ed624efe7f00c1c215b95cfd324a8cacf7f0053c00fa524f5", size = 107844, upload-time = "2026-02-19T16:48:22.899Z" }, +] + [[package]] name = "quadax" version = "0.2.9" @@ -678,6 +913,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, ] +[[package]] +name = "scs" +version = "3.2.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/59/5cb7f9612a5a3ff6efd4ab2d899902a536cc5974a7edb589084c5577291c/scs-3.2.11.tar.gz", hash = "sha256:2a5455cf2093d07f84f2f848c199faed52e79cdb3a11fe250b5622b6bbac4913", size = 1691825, upload-time = "2026-01-09T17:53:54.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/1b/6611b98b114621444078da50be9e83c43fadc4079ef0df867091b5c9ee38/scs-3.2.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a42696b0a26c3e749b8da8d2ffc57a93af4f0f500fc3a83acb50daad92386de4", size = 96309, upload-time = "2026-01-09T17:53:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/52/16/e49eff778292000bc7dca204952e430bc138c21e7eb1c4348341f3bd2ef5/scs-3.2.11-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50d204ae417c014a1756be36e8c0b857ed39c7b64e2c63b6afb1ff64c0a465d0", size = 5071425, upload-time = "2026-01-09T17:53:14.966Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/b7b2df5bece1b7e5f11d2bf21744c9fa346f3cea0a849cb54dabb9b83055/scs-3.2.11-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:594c09207395de922e0ff40ced562453e46f4f197dd0128022cb82c096f06615", size = 12079963, upload-time = "2026-01-09T17:53:16.976Z" }, + { url = "https://files.pythonhosted.org/packages/d6/78/11db8c58c071ece82aaefe53c7f6d5932fe8dafe381b2f6fd35fcc22cf33/scs-3.2.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd672701ed81744e8c300df71d823700b05e7fe3d6a26f8b19b74b0a31fe3c8a", size = 11973938, upload-time = "2026-01-09T17:53:19.239Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ca/cd9cc63fc22f452188b9cda41a8d65d4124e733bc54f34f706b9c5939f92/scs-3.2.11-cp313-cp313-win_amd64.whl", hash = "sha256:2f4ebc0be14783ce3fcda61c616a7e922ac528af033e44a0da952dda0fe98091", size = 7478466, upload-time = "2026-01-09T17:53:21.494Z" }, + { url = "https://files.pythonhosted.org/packages/94/8a/52facc80a6515edd6560d918a68a0dd9186299a709e64c180ad24551aeb8/scs-3.2.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe43181c3822bed600363c25c7566a643b319e0edb0c2af385c5f086a9c826d2", size = 96344, upload-time = "2026-01-09T17:53:22.949Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/c125e6b01aa6936f604e1b46a4b8c37e126af703cc228af7e9d0fe012bcb/scs-3.2.11-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3c59ce43585d3ea0c6771c5ce3df272b6c8239231acbb9567876be5d0a0474d", size = 5071403, upload-time = "2026-01-09T17:53:24.327Z" }, + { url = "https://files.pythonhosted.org/packages/58/ae/94055cafac0d9b81ffa2a12f7050c394c39182ec901faff42a471cca50cc/scs-3.2.11-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c46f597892c9f8c5551bb9a3a680dfb86e86a1a6c3bc67b09a5af2e89ba5357", size = 12079963, upload-time = "2026-01-09T17:53:26.2Z" }, + { url = "https://files.pythonhosted.org/packages/d8/72/43ff8bc4a281e84d4ae8f13dcf7436ff030bc5f67fba02368c829537386d/scs-3.2.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:513131af6991fb4983f84c4ba276c756c0a3574003c2790dda891c68d5b6da30", size = 11973979, upload-time = "2026-01-09T17:53:29.979Z" }, + { url = "https://files.pythonhosted.org/packages/af/55/695c509c0852bc32695b1995ff12227dfc78e9d91867ccf637d7cf85a948/scs-3.2.11-cp314-cp314-win_amd64.whl", hash = "sha256:7b2c37e87baca0389f005fe19a0ca8209d43c0f1e9136a1a6fde23cae1735db9", size = 7569717, upload-time = "2026-01-09T17:53:32.938Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a1/b30e470a7440c57ed53a1d92a9e58f17ecf548888b4eea658be047500ae5/scs-3.2.11-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:29c0a5c233fb5a964ea5f7523ec2b2209f000217c0a24423ab5dcd8b8922f37d", size = 97042, upload-time = "2026-01-09T17:53:35.676Z" }, + { url = "https://files.pythonhosted.org/packages/14/31/86b6aa0fca4be4701b59bcfcf29007b16dea118051a5b46a43396f2a4543/scs-3.2.11-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7519c2f436e793b004d1eae4aaf98c18857e519f8169219d1167fe88b3b0a568", size = 5072473, upload-time = "2026-01-09T17:53:37.021Z" }, + { url = "https://files.pythonhosted.org/packages/98/eb/2c07015938c50f46e9323e379e9799c3e28e0d07c9bae8b6735a6ecf1b6c/scs-3.2.11-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c166768dc87c389b2d000b5dcd472bb0ba40f96b4cf0e63c0fb603a4a5c80db", size = 12080259, upload-time = "2026-01-09T17:53:38.897Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1b/d52e3b17554791726ba788abff053f4b27df157a49438f01134fec3c859d/scs-3.2.11-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f51a14a5315974fae4ca4e1b4dc8926f872eca7e66b42e070dbdcfa6904b7860", size = 11974311, upload-time = "2026-01-09T17:53:41.003Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d7/023ba290cfaf97b21c710b675b8a860b97d8226f62e35d7a08e37ddbb6d3/scs-3.2.11-cp314-cp314t-win_amd64.whl", hash = "sha256:7fe26e8a0efc96232f4c5b7649817e48dae04a61be911417e925071091b8cbf6", size = 7570221, upload-time = "2026-01-09T17:53:42.845Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -687,6 +958,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "sparsediffpy" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/e7/6a3227a25a79a440e5ec5eff1e90f5911515a7c219ee95fc6dfe5d74ec30/sparsediffpy-0.3.0.tar.gz", hash = "sha256:fdd9115db63ee228d09e1917365b263a16811645c6d32ee7dce50ada09b3d5a5", size = 180927, upload-time = "2026-05-14T06:57:48.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/e0/c9bc5b18b025567b02df87198d434c286777f491bc3c0b38949861e2e039/sparsediffpy-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aae700842a43bfdf09c7709c23354999d8e34da7f82dbc78d933a008fd63c285", size = 207404, upload-time = "2026-05-14T06:57:28.183Z" }, + { url = "https://files.pythonhosted.org/packages/11/ca/021de6a523e739a038f6e527bf71acab6ccb5181ddd5beff304f349e968e/sparsediffpy-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:afcc042d56af4e978e9a798c8c41a7432d9ff715d993cc2ac733565d72080a5e", size = 138483, upload-time = "2026-05-14T06:57:29.899Z" }, + { url = "https://files.pythonhosted.org/packages/76/64/e5ffb808435040177345cbaeed5623ebb49bc5567665e2001373197d6239/sparsediffpy-0.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6bc0720d58f4858ed28b1a760a96bc328d4a9591da5abf2c2c46955bcd2d6f59", size = 5091193, upload-time = "2026-05-14T06:57:32.066Z" }, + { url = "https://files.pythonhosted.org/packages/8f/17/670c5fd1f0b4da16e4b691d48963b195092d7bcf3d9afb92722a6a878f42/sparsediffpy-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73df847a5ae8fbb14b4af9b8eb8e0c2e96f2c8479da2eff8afd89adc55752c5d", size = 12099975, upload-time = "2026-05-14T06:57:34.696Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1c/8c2a4f03e10b418dc957db87287a777cda74b58cf39779cecefccd5f4b2d/sparsediffpy-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:b018a3e9783ae547a83a82049f26d72a6f0a5dfb61f42eb88ed921412f8aa087", size = 130147, upload-time = "2026-05-14T06:57:36.83Z" }, + { url = "https://files.pythonhosted.org/packages/80/f0/0b8b505563699767fa125a453cd40004476084c3e7a1975e7dfca957ca3a/sparsediffpy-0.3.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b62d0531c6470d6cea800ff60952857d16b7a3e177369c729a8e5f31dba9a4ea", size = 207430, upload-time = "2026-05-14T06:57:38.273Z" }, + { url = "https://files.pythonhosted.org/packages/56/8c/9be63f71098b8c3ffe82ab4094195ea95a33822787fa67b97e8eb5e13b77/sparsediffpy-0.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5140ee4bb70c877d01ad1e07cee76a375c684af8e3672bf37c1de424e6f8b3b7", size = 138542, upload-time = "2026-05-14T06:57:40.124Z" }, + { url = "https://files.pythonhosted.org/packages/77/01/e2f5cc15d0fd7244c26632a4ee746e94d2721e5e44f49c2a3988b3e23d87/sparsediffpy-0.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a746cc822c2bce50dd696ef7a7c59f4e795f2c4c0a24750146b7c106809e12a", size = 5091213, upload-time = "2026-05-14T06:57:41.842Z" }, + { url = "https://files.pythonhosted.org/packages/48/03/6656660e7f0564c99405173848c65d92408af0372bbd30b8a6728d9bcee2/sparsediffpy-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a28052dc02a9424b84406a0ac8c573f19471ccbf3f0ee4331588549e4de5dbe", size = 12099943, upload-time = "2026-05-14T06:57:44.524Z" }, + { url = "https://files.pythonhosted.org/packages/20/ce/57472c85900e2937b921ccc1aa492e9449a1babf8b4c6a228f892f5d3b7f/sparsediffpy-0.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:2c13dd751963611419273d7cf99c9f7e0c7793c7edba2941a57847cc62f3aa18", size = 132153, upload-time = "2026-05-14T06:57:46.598Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -705,6 +997,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] +[[package]] +name = "unopy" +version = "0.4.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/0a/6a61fce9fb2c67b46407de14056844e04620ac9f08d1ffabe341c55c563b/unopy-0.4.10.tar.gz", hash = "sha256:7ace03edffd9a3082b718043c98f343e68d181a716afa25476dcbd1be16726ed", size = 1864237, upload-time = "2026-06-03T09:40:40.774Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a8/b5ad46c0cc4797d5a26b93b69cc09196dd42b6477b61aad0673096e3d58e/unopy-0.4.10-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:3c27c5d6c8534782dadcd82bd26392079ffd59bc53750d37de46b3dbd5eda1cf", size = 5149734, upload-time = "2026-06-03T09:39:52.852Z" }, + { url = "https://files.pythonhosted.org/packages/07/9e/e4a8f9b3acbfdf3cc7ab9d0c9c35b66f42ad1b2850aec6d0f428c55d96b1/unopy-0.4.10-cp313-cp313-macosx_15_0_x86_64.whl", hash = "sha256:ae31bc8c3e9c176b7e291cc9ae58c47128c366b78986dacffc73204d7aaa0c70", size = 6116768, upload-time = "2026-06-03T09:39:54.718Z" }, + { url = "https://files.pythonhosted.org/packages/c2/82/296c77c8eec269b103560144336eaa0224580f99405aa061e4bf04ddc1cc/unopy-0.4.10-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5779e6d65e3cd15f5069157a5e52f5dc90af6dea9d41b3d02f4900216193da12", size = 5152761, upload-time = "2026-06-03T09:39:56.504Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/f81e5099809e2a23864e9323b6cb50b9abfd5741d5191fc80874e1f42e26/unopy-0.4.10-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:063d7c6923ca040003bffffb6bb46908494919e39593e8b67e9e800cbc623711", size = 6035593, upload-time = "2026-06-03T09:39:58.197Z" }, + { url = "https://files.pythonhosted.org/packages/30/22/a0a85c4342feed3818abbd5daa216591cbb3a387f4a530581ee25f5f2ea1/unopy-0.4.10-cp313-cp313-win_amd64.whl", hash = "sha256:3d76fddcf1edb0acaea87cb5fe3213976d6c576a87b20101c5f2685271dfa10b", size = 7351272, upload-time = "2026-06-03T09:40:00.176Z" }, + { url = "https://files.pythonhosted.org/packages/35/ba/ddde971c473c71f186c72dce10f2cae0ca3fa7b65af015f956221b9a1818/unopy-0.4.10-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:d40dc4d1327978c71a0f39e12609392eaaf4d17bb2a4b261efd2261027267993", size = 5149755, upload-time = "2026-06-03T09:40:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8f/9fbd6cf5f1da4eca3acd8d5a2bca61d3c68194975e86c36864090879228d/unopy-0.4.10-cp314-cp314-macosx_15_0_x86_64.whl", hash = "sha256:faf27310d3351f0fcb466794ddf06a3659c7b0f2a8f7b4727e0792e0bcc96198", size = 6117036, upload-time = "2026-06-03T09:40:03.848Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c1/446324394dc23165feb4b96f261bdd974b6067148fe5c1d9858ed5f36f04/unopy-0.4.10-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:862b5e4ed5ba3dc6ac7c6858d9363d20ec43cacef63467f9401256e195fb539b", size = 5153357, upload-time = "2026-06-03T09:40:05.435Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2c/d99c5a21628088f77ebfb817807de9459fdc8bed207341bcfecbf8d92b7b/unopy-0.4.10-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78d2dae5dc47f3c7372dc267a3a49dc4faaebbd8ec0d12149c4c7505e71cb199", size = 6036541, upload-time = "2026-06-03T09:40:07.27Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d8/e61b05aff2b81c76c06d39108aa2b737cbc00fb3d54846bca3479da30464/unopy-0.4.10-cp314-cp314-win_amd64.whl", hash = "sha256:c219e31fa234074debb3bffbe9c6efbe2328db5a0cb7bff04edb0ad7d0990a89", size = 7351304, upload-time = "2026-06-03T09:40:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4c/c4b740e1530349f0c452646bb39b54d2d122833ab5a86a9ec239b21f555b/unopy-0.4.10-cp314-cp314t-macosx_15_0_arm64.whl", hash = "sha256:c7b6c3e27391335dd0a0c24feb05b631f27f99583f6f9e6fd7c47ea9e7515f1a", size = 5162250, upload-time = "2026-06-03T09:40:11.079Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a1/10cdcb38492c946168522e7bb7fa7b80e29c732fa68d1e3b143f8bb603ae/unopy-0.4.10-cp314-cp314t-macosx_15_0_x86_64.whl", hash = "sha256:b4a49fd69fdd81751bc958876c7957da181d0fc94e036b87c8123797c52d4a4f", size = 6127829, upload-time = "2026-06-03T09:40:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/7f/61/b19f9fec6c4adeebe48160338bde019e0ee0100c36c53ff862853697eece/unopy-0.4.10-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d5fd3e349a76536e1787818e4a4956571a4c053287e484494ec26088fe68a322", size = 5157481, upload-time = "2026-06-03T09:40:15.132Z" }, + { url = "https://files.pythonhosted.org/packages/01/6b/bb7467242d24c69216902652ff1a5c2fc1d7799eff04f5cb6d836474f7e6/unopy-0.4.10-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70a087afd2e864e29e2ceb89a925443f1f6fb89abd21c806a67cdb9e51cd1a11", size = 6043429, upload-time = "2026-06-03T09:40:16.903Z" }, +] + [[package]] name = "wadler-lindig" version = "0.1.7"