From d77b8baff890090a6d5af6d19de5fa7956a93ace Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Wed, 31 Jan 2024 13:49:30 -0500 Subject: [PATCH 01/52] style: same style for all case studies --- CvxLean/Examples/CovarianceEstimation.lean | 26 +++++++----- CvxLean/Examples/FittingSphere.lean | 16 ++++++-- CvxLean/Examples/HypersonicShapeDesign.lean | 42 +++++++++++++------- CvxLean/Examples/TrussDesign.lean | 32 +++++++++++++++ CvxLean/Examples/VehicleSpeedScheduling.lean | 19 ++++++++- CvxLean/Examples/fitting_sphere.py | 4 +- CvxLean/Examples/hypersonic_shape_design.py | 18 ++++----- CvxLean/Examples/truss_design.py | 13 ------ CvxLean/Examples/vehicle_speed_scheduling.py | 10 ++--- 9 files changed, 120 insertions(+), 60 deletions(-) diff --git a/CvxLean/Examples/CovarianceEstimation.lean b/CvxLean/Examples/CovarianceEstimation.lean index b5c55a1e..b56854f9 100644 --- a/CvxLean/Examples/CovarianceEstimation.lean +++ b/CvxLean/Examples/CovarianceEstimation.lean @@ -4,15 +4,15 @@ namespace CovarianceEstimation open CvxLean Minimization Real BigOperators Matrix -noncomputable def problem (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → Fin n → ℝ) := +noncomputable def covEstimation (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → Fin n → ℝ) := optimization (R : Matrix (Fin n) (Fin n) ℝ) maximize (∏ i, gaussianPdf R (y i)) subject to c_pos_def : R.PosDef c_sparse : R⁻¹.abs.sum ≤ α -reduction reduction₁₂/problem₂ (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → Fin n → ℝ) : - problem n N α y := by +reduction red/covEstimationConvex (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → Fin n → ℝ) : + covEstimation n N α y := by -- Change objective function. reduction_step => apply Reduction.map_objFun_of_order_reflecting (g := fun x => -log (-x)) @@ -52,15 +52,23 @@ reduction reduction₁₂/problem₂ (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N intro hR rw [nonsing_inv_nonsing_inv R hR.isUnit_det] -#print problem₂ +#print covEstimationConvex +-- optimization (R : Matrix (Fin n) (Fin n) ℝ) +-- minimize +-- -(-(N • log (sqrt ((2 * π) ^ n)) + N • (-log (det R) / 2)) + +-- -(↑N * trace ((covarianceMatrix fun x => y x) * Rᵀ) / 2)) +-- subject to +-- c_pos_def : PosDef R +-- c_sparse : sum (Matrix.abs R) ≤ α set_option maxHeartbeats 20000000 -solve problem₂ 2 4 1 ![![0,2],![2,0],![-2,0],![0,-2]] -#print problem₂.reduced +solve covEstimationConvex 2 4 1 ![![0,2],![2,0],![-2,0],![0,-2]] -#eval problem₂.status -- "PRIMAL_AND_DUAL_FEASIBLE" -#eval problem₂.value -- 14.124098 -#eval problem₂.solution -- ![![0.499903, 0.000000], ![0.000000, 0.499905]] +#print covEstimationConvex.reduced + +#eval covEstimationConvex.status -- "PRIMAL_AND_DUAL_FEASIBLE" +#eval covEstimationConvex.value -- 14.124098 +#eval covEstimationConvex.solution -- ![![0.499903, 0.000000], ![0.000000, 0.499905]] end CovarianceEstimation diff --git a/CvxLean/Examples/FittingSphere.lean b/CvxLean/Examples/FittingSphere.lean index 0ab715b9..8eab3936 100644 --- a/CvxLean/Examples/FittingSphere.lean +++ b/CvxLean/Examples/FittingSphere.lean @@ -107,10 +107,14 @@ equivalence' eqv/fittingSphereT (n m : ℕ) (x : Fin m → Fin n → ℝ) : fitt rename_vars [c, t] #print fittingSphereT +-- optimization (c : Fin n → ℝ) (t : ℝ) +-- minimize Vec.sum ((Vec.norm x ^ 2 - 2 * mulVec x c - Vec.const m t) ^ 2) +-- subject to +-- h₁ : 0 < sqrt (t + ‖c‖ ^ 2) -- Next, we proceed to remove the non-convex constraint by arguing that any (non-trivial) point that --- minimizes the objective function wihtout the constraint, also satisfies the constraint. We define --- the problem directly, bot note that we could also remove the constraint using the `relaxation` +-- minimizes the objective function without the constraint, also satisfies the constraint. We define +-- the problem directly, but note that we could also remove the constraint using the `relaxation` -- command. def fittingSphereConvex (n m : ℕ) (x : Fin m → Fin n → ℝ) := @@ -199,6 +203,8 @@ def red (hm : 0 < m) (hx : ∃ i j, x i ≠ x j) : exact optimal_convex_implies_optimal_t n m x hm c t h_nontrivial h_opt } #print fittingSphereConvex +-- optimization (c : Fin n → ℝ) (t : ℝ) +-- minimize Vec.sum ((Vec.norm x ^ 2 - 2 * mulVec x c - Vec.const m t) ^ 2) -- We proceed to solve the problem on a concrete example. -- https://github.com/cvxgrp/cvxbook_additional_exercises/blob/main/python/sphere_fit_data.py @@ -228,7 +234,11 @@ solve fittingSphereConvex nₚ mₚ xₚ def sol := eqv.backward_map nₚ mₚ xₚ.float fittingSphereConvex.solution -#eval sol -- (![1.664863, 0.031932], 1.159033) +def cₚ_opt := sol.1 +def rₚ_opt := sol.2 + +#eval cₚ_opt -- ![1.664863, 0.031932] +#eval rₚ_opt -- 1.159033 end FittingSphere diff --git a/CvxLean/Examples/HypersonicShapeDesign.lean b/CvxLean/Examples/HypersonicShapeDesign.lean index 05bb3cac..52d0f378 100644 --- a/CvxLean/Examples/HypersonicShapeDesign.lean +++ b/CvxLean/Examples/HypersonicShapeDesign.lean @@ -26,6 +26,12 @@ equivalence' eqv₁/hypersonicShapeDesignConvex (a b : ℝ) (ha : 0 ≤ a) (hb pre_dcp #print hypersonicShapeDesignConvex +-- optimization (Δx : ℝ) +-- minimize Δx ^ (-2) - 1 +-- subject to +-- h₁ : 1 / 100000 ≤ Δx +-- h₂ : Δx ≤ 1 +-- h₃ : sqrt a ^ 2 / Δx / (1 - b) ≤ sqrt (1 - Δx ^ 2) @[optimization_param] def aₚ : ℝ := 0.05 @@ -52,22 +58,22 @@ time_cmd solve hypersonicShapeDesignConvex aₚ bₚ aₚ_nonneg bₚ_nonneg b #print hypersonicShapeDesignConvex.reduced -- Final width of wedge. -def width := eqv₁.backward_map aₚ.float bₚ.float hypersonicShapeDesignConvex.solution +def wₚ_opt := eqv₁.backward_map aₚ.float bₚ.float hypersonicShapeDesignConvex.solution -#eval width -- 0.989524 +#eval wₚ_opt -- 0.989524 -#eval aₚ.float * (1 / width) - (1 - bₚ.float) * Float.sqrt (1 - width ^ 2) ≤ 0 -#eval aₚ.float * (1 / width) - (1 - bₚ.float) * Float.sqrt (1 - width ^ 2) ≤ 0.000001 +#eval aₚ.float * (1 / wₚ_opt) - (1 - bₚ.float) * Float.sqrt (1 - wₚ_opt ^ 2) ≤ 0 +#eval aₚ.float * (1 / wₚ_opt) - (1 - bₚ.float) * Float.sqrt (1 - wₚ_opt ^ 2) ≤ 0.000001 -- Final height of wedge. -def height := Float.sqrt (1 - width ^ 2) +def hₚ_opt := Float.sqrt (1 - wₚ_opt ^ 2) -#eval height -- 0.144368 +#eval hₚ_opt -- 0.144368 -- Final L/D ratio. -def ldRatio := 1 / (Float.sqrt ((1 / width ^ 2) - 1)) +def ldRatioₚ := 1 / (Float.sqrt ((1 / wₚ_opt ^ 2) - 1)) -#eval ldRatio -- 6.854156 +#eval ldRatioₚ -- 6.854156 -- While the above is good enough, we simplify the problem further by performing a change of -- variables and simplifying appropriately. @@ -97,29 +103,35 @@ equivalence' eqv₂/hypersonicShapeDesignSimpler (a b : ℝ) (ha : 0 ≤ a) (hb rfl #print hypersonicShapeDesignSimpler +-- optimization (z : ℝ) +-- minimize z⁻¹ - 1 +-- subject to +-- h₁ : 1 / 10000000000 ≤ z +-- h₂ : z ≤ 1 +-- h₃ : a ^ 2 * z⁻¹ ≤ (1 - b) ^ 2 * (1 - z) time_cmd solve hypersonicShapeDesignSimpler aₚ bₚ aₚ_nonneg bₚ_nonneg bₚ_lt_one #print hypersonicShapeDesignSimpler.reduced -- Final width of wedge. -def width' := +def wₚ'_opt := eqv₁.backward_map aₚ.float bₚ.float <| eqv₂.backward_map aₚ.float bₚ.float hypersonicShapeDesignSimpler.solution -#eval width' -- 0.989524 +#eval wₚ'_opt -- 0.989524 -#eval aₚ.float * (1 / width') - (1 - bₚ.float) * Float.sqrt (1 - width' ^ 2) ≤ 0 +#eval aₚ.float * (1 / wₚ'_opt) - (1 - bₚ.float) * Float.sqrt (1 - wₚ'_opt ^ 2) ≤ 0 -- Final height of wedge. -def height' := Float.sqrt (1 - width' ^ 2) +def hₚ'_opt := Float.sqrt (1 - wₚ'_opt ^ 2) -#eval height' -- 0.144371 +#eval hₚ'_opt -- 0.144371 -- Final L/D ratio. -def ldRatio' := 1 / (Float.sqrt ((1 / width' ^ 2) - 1)) +def ldRatioₚ' := 1 / (Float.sqrt ((1 / wₚ'_opt ^ 2) - 1)) -#eval ldRatio' -- 6.854031 +#eval ldRatioₚ' -- 6.854031 end HypersonicShapeDesign diff --git a/CvxLean/Examples/TrussDesign.lean b/CvxLean/Examples/TrussDesign.lean index d5910f95..678d3658 100644 --- a/CvxLean/Examples/TrussDesign.lean +++ b/CvxLean/Examples/TrussDesign.lean @@ -107,6 +107,17 @@ equivalence' eqv₁/trussDesignGP (hmin hmax wmin wmax Rmax σ F₁ F₂ : ℝ) rename_constrs [c_r, c_F₁, c_F₂, c_hmin, c_hmax, c_wmin, c_wmax, c_A_lb, c_A_ub] #print trussDesignGP +-- minimize 2 * A * sqrt (w ^ 2 + h ^ 2) +-- subject to +-- c_r : 0 < r +-- c_F₁ : F₁ * sqrt (w ^ 2 + h ^ 2) / (2 * h) ≤ σ * A +-- c_F₂ : F₂ * sqrt (w ^ 2 + h ^ 2) / (2 * w) ≤ σ * A +-- c_hmin : hmin ≤ h +-- c_hmax : h ≤ hmax +-- c_wmin : wmin ≤ w +-- c_wmax : w ≤ wmax +-- c_A_lb : 0.21 * r ^ 2 ≤ A / (2 * π) +-- c_A_ub : sqrt (A / (2 * π) + r ^ 2) ≤ Rmax instance : ChangeOfVariables fun ((h', w', r', A') : ℝ × ℝ × ℝ × ℝ) => (exp h', exp w', exp r', exp A') := @@ -135,6 +146,17 @@ equivalence' eqv₂/trussDesignConvex (hmin hmax : ℝ) (hmin_pos : 0 < hmin) remove_trivial_constrs #print trussDesignConvex +-- optimization (h' : ℝ) (w' : ℝ) (r' : ℝ) (A' : ℝ) +-- minimize 2 * rexp A' * sqrt (rexp w' ^ 2 + rexp h' ^ 2) +-- subject to +-- c_F₁ : F₁ * sqrt (rexp w' ^ 2 + rexp h' ^ 2) / (2 * rexp h') ≤ σ * rexp A' +-- c_F₂ : F₂ * sqrt (rexp w' ^ 2 + rexp h' ^ 2) / (2 * rexp w') ≤ σ * rexp A' +-- c_hmin : hmin ≤ rexp h' +-- c_hmax : rexp h' ≤ hmax +-- c_wmin : wmin ≤ rexp w' +-- c_wmax : rexp w' ≤ wmax +-- c_A_lb : 0.21 * rexp r' ^ 2 ≤ rexp A' / (2 * π) +-- c_A_ub : sqrt (rexp A' / (2 * π) + rexp r' ^ 2) ≤ Rmax -- We split these two steps, to make speed up backward map creation as there are ~80 pre-DCP steps -- which need to be simplified into a single map (which should be just `id`). @@ -148,6 +170,16 @@ equivalence eqv₃/trussDesignDCP (hmin hmax : ℝ) (hmin_pos : 0 < hmin) (hmin_ pre_dcp #print trussDesignDCP +-- minimize log (rexp (2 * h') + rexp (2 * w')) + 2 * (log 2 + A') +-- subject to +-- c_F₁ : 1 / 2 * log (rexp (2 * h') + rexp (2 * w')) ≤ log σ + A' - (log (F₁ / 2) - h') +-- c_F₂ : 1 / 2 * log (rexp (2 * h') + rexp (2 * w')) ≤ log σ + (w' + A') - log (F₂ / 2) +-- c_hmin : log hmin ≤ h' +-- c_hmax : rexp h' ≤ hmax +-- c_wmin : log wmin ≤ w' +-- c_wmax : rexp w' ≤ wmax +-- c_A_lb : log (21 / 100) + 2 * r' ≤ A' - log (2 * π) +-- c_A_ub : rexp A' ≤ (Rmax * Rmax - rexp (2 * r')) * (2 * π) -- We provide concrete values and solve the problem. diff --git a/CvxLean/Examples/VehicleSpeedScheduling.lean b/CvxLean/Examples/VehicleSpeedScheduling.lean index 15377f31..e9340ec8 100644 --- a/CvxLean/Examples/VehicleSpeedScheduling.lean +++ b/CvxLean/Examples/VehicleSpeedScheduling.lean @@ -82,6 +82,13 @@ equivalence' eqv₁/vehSpeedSchedConvex (n : ℕ) (d : Fin n → ℝ) rename_constrs [c_smin, c_smax, c_τmin, c_τmax] #print vehSpeedSchedConvex +-- optimization (t : Fin n → ℝ) +-- minimize Vec.sum (t * Vec.map F (d / t)) +-- subject to +-- c_smin : smin ≤ d / t +-- c_smax : d / t ≤ smax +-- c_τmin : τmin ≤ Vec.cumsum t +-- c_τmax : Vec.cumsum t ≤ τmax #check eqv₁.backward_map @@ -130,6 +137,14 @@ equivalence' eqv₂/vehSpeedSchedQuadratic (n : ℕ) (d : Fin n → ℝ) -- Finally, we can apply `dcp`! (or we can call `solve`, as we do below). #print vehSpeedSchedQuadratic +-- optimization (t : Fin n → ℝ) +-- minimize Vec.sum (a • d ^ 2 * (1 / t) + b • d + c • t) +-- subject to +-- c_t : StrongLT 0 t +-- c_smin : smin * t ≤ d +-- c_smax : d ≤ smax * t +-- c_τmin : τmin ≤ Vec.cumsum t +-- c_τmax : Vec.cumsum t ≤ τmax #check eqv₂.backward_map @@ -215,9 +230,9 @@ def eqv₂.backward_mapₚ := eqv₂.backward_map nₚ dₚ.float τminₚ.float -- Finally, we can obtain the solution to the original problem. -def sol := eqv₁.backward_mapₚ (eqv₂.backward_mapₚ p.solution) +def sₚ_opt := eqv₁.backward_mapₚ (eqv₂.backward_mapₚ p.solution) -#eval sol +#eval sₚ_opt -- ![0.955578, 0.955548, 0.955565, 0.955532, 0.955564, 0.955560, 0.912362, 0.960401, 0.912365, -- 0.912375] diff --git a/CvxLean/Examples/fitting_sphere.py b/CvxLean/Examples/fitting_sphere.py index 5c945902..0d735dc7 100644 --- a/CvxLean/Examples/fitting_sphere.py +++ b/CvxLean/Examples/fitting_sphere.py @@ -25,10 +25,8 @@ ), []) p.solve(solver=cp.MOSEK, verbose=True) -# Backward map from change of variables. r = np.sqrt(t.value + (np.linalg.norm(c.value) ** 2)) -print("t* = ", t.value) print("c* = ", c.value) print("r* = ", r) @@ -46,4 +44,4 @@ def plot_circle_and_points(center, radius, points): plt.show() plt.savefig('plots/fitting_sphere.png') -plot_circle_and_points(c.value, r, x) +# plot_circle_and_points(c.value, r, x) diff --git a/CvxLean/Examples/hypersonic_shape_design.py b/CvxLean/Examples/hypersonic_shape_design.py index a7e072b6..3ee417ff 100644 --- a/CvxLean/Examples/hypersonic_shape_design.py +++ b/CvxLean/Examples/hypersonic_shape_design.py @@ -1,9 +1,9 @@ import cvxpy as cp import numpy as np -a = .05 # height of rectangle +a = .05 -b = .65 # width of rectangle +b = .65 x = cp.Variable(pos=True) @@ -16,9 +16,9 @@ p.solve(qcp=True, verbose=True) -print('QCP Final L/D Ratio = ', 1 / obj.value) -print('QCP Final width of wedge = ', x.value) -print('QCP Final height of wedge = ', np.sqrt(1 - x.value ** 2)) +print('QCP w* = ', x.value) +print('QCP h* = ', np.sqrt(1 - x.value ** 2)) +print('QCP L/D Ratio = ', 1 / obj.value) x = cp.Variable(pos=True) @@ -31,8 +31,6 @@ p.solve(verbose=True) -print('DCP Final L/D Ratio = ', 1 / np.sqrt(obj.value)) -print('DCP Final width of wedge = ', x.value) -print('DCP Final height of wedge = ', np.sqrt(1 - x.value ** 2)) - - +print('DCP w* = ', x.value) +print('DCP h* = ', np.sqrt(1 - x.value ** 2)) +print('DCP L/D Ratio = ', 1 / np.sqrt(obj.value)) diff --git a/CvxLean/Examples/truss_design.py b/CvxLean/Examples/truss_design.py index 4beb1e53..84170738 100644 --- a/CvxLean/Examples/truss_design.py +++ b/CvxLean/Examples/truss_design.py @@ -15,19 +15,6 @@ r = cp.Variable(pos=True) A = cp.Variable(pos=True) -# optimization (h : ℝ) (w : ℝ) (r : ℝ) (A : ℝ) -# minimize 2 * A * sqrt (w ^ 2 + h ^ 2) -# subject to -# c_r : 0 < r -# c_F₁ : F₁ * sqrt (w ^ 2 + h ^ 2) / (2 * h) ≤ σ * A -# c_F₂ : F₂ * sqrt (w ^ 2 + h ^ 2) / (2 * w) ≤ σ * A -# c_hmin : hmin ≤ h -# c_hmax : h ≤ hmax -# c_wmin : wmin ≤ w -# c_wmax : w ≤ wmax -# c_A_lb : 0.21 * r ^ 2 ≤ A / (2 * π) -# c_A_ub : sqrt (A / (2 * π) + r ^ 2) ≤ Rmax - p = cp.Problem( cp.Minimize(2 * A * cp.sqrt(cp.square(w) + cp.square(h))), [ f1 * cp.sqrt(cp.square(w) + cp.square(h)) / (2 * h) <= sigma * A, diff --git a/CvxLean/Examples/vehicle_speed_scheduling.py b/CvxLean/Examples/vehicle_speed_scheduling.py index 63a36653..72ba5a35 100644 --- a/CvxLean/Examples/vehicle_speed_scheduling.py +++ b/CvxLean/Examples/vehicle_speed_scheduling.py @@ -36,14 +36,14 @@ s = d / t.value -def plot(s): +print("t* = ", t.value) +print("s* = ", s) + +def plot_speed(s): plt.step(np.arange(n), s) plt.xlabel('$i$') plt.ylabel('$s_i$') plt.savefig("plots/vehicle_speed_scheduling.png") plt.show() -print("t* = ", t.value) -print("s* = ", s) - -plot(s) +# plot_speed(s) From 7197860be832bd968a749b6270422c6a5067174f Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Wed, 31 Jan 2024 14:38:45 -0500 Subject: [PATCH 02/52] chore: move result about `Fin` --- CvxLean/Lib/Math/Data/Fin.lean | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CvxLean/Lib/Math/Data/Fin.lean b/CvxLean/Lib/Math/Data/Fin.lean index 46ebae3e..f3104659 100644 --- a/CvxLean/Lib/Math/Data/Fin.lean +++ b/CvxLean/Lib/Math/Data/Fin.lean @@ -6,4 +6,7 @@ variable {n : ℕ} instance [i : Fact (0 < n)] : OfNat (Fin n) 0 := ⟨⟨0, i.out⟩⟩ +instance {n m : ℕ} : OfNat (Fin n.succ ⊕ Fin m.succ) (x) where + ofNat := if x <= n then Sum.inl (Fin.ofNat x) else Sum.inr (Fin.ofNat (x - n.succ)) + end Fin From c67bda4b8d584b56ecb75e2ebe96ee659f42fb05 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Wed, 31 Jan 2024 14:38:57 -0500 Subject: [PATCH 03/52] feat: custom errors and debugging --- CvxLean/Meta/Util/Debug.lean | 11 +++++++++++ CvxLean/Meta/Util/Error.lean | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100644 CvxLean/Meta/Util/Debug.lean create mode 100644 CvxLean/Meta/Util/Error.lean diff --git a/CvxLean/Meta/Util/Debug.lean b/CvxLean/Meta/Util/Debug.lean new file mode 100644 index 00000000..f0807c98 --- /dev/null +++ b/CvxLean/Meta/Util/Debug.lean @@ -0,0 +1,11 @@ +import Lean + +/-! +Custom debug trace classes. +-/ + +open Lean Meta + +builtin_initialize + registerTraceClass `CvxLean + registerTraceClass `CvxLean.debug diff --git a/CvxLean/Meta/Util/Error.lean b/CvxLean/Meta/Util/Error.lean new file mode 100644 index 00000000..c11ae78a --- /dev/null +++ b/CvxLean/Meta/Util/Error.lean @@ -0,0 +1,10 @@ +import Lean + +/-! +Custom error messages. +-/ + +syntax "throwCoeffsError " (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwCoeffsError $msg:interpolatedStr) => `(throwError ("`coeffs` error: " ++ (m! $msg))) From 539a21cfadfa675005e19d67359ba0d6c9a99d2a Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Wed, 31 Jan 2024 14:39:41 -0500 Subject: [PATCH 04/52] doc: `Coeffs.lean` --- CvxLean/Command/Solve/Float/Coeffs.lean | 110 +++++++++++------------- 1 file changed, 51 insertions(+), 59 deletions(-) diff --git a/CvxLean/Command/Solve/Float/Coeffs.lean b/CvxLean/Command/Solve/Float/Coeffs.lean index a7f26e2d..1c8dce59 100644 --- a/CvxLean/Command/Solve/Float/Coeffs.lean +++ b/CvxLean/Command/Solve/Float/Coeffs.lean @@ -1,18 +1,22 @@ -import CvxLean.Syntax.Minimization +import CvxLean.Lib.Math.Data.Fin import CvxLean.Lib.Cones.All +import CvxLean.Meta.Util.Error +import CvxLean.Meta.Util.Debug +import CvxLean.Syntax.Minimization import CvxLean.Command.Solve.Float.ProblemData import CvxLean.Command.Solve.Float.RealToFloat - /-! # Extract coefficients from problem to generate problem data -TODO +This file defines `determineCoeffsFromExpr`, which takes a `MinimizationExpr` and returns +`ProblemData`. This procedure is used by `Commands/Solve.lean` to be able to call an external +solver. ## TODO -* This is probably a big source of inefficency for the `solve` command. We should come up with - a better way to extract the numerical values from the Lean expressions. +* This is probably a big source of inefficency for the `solve` command. We should come up with a + better way to extract the numerical values from the Lean expressions. * A first step is to not `unrollVectors` and turn thos expressions into floats directly. -/ @@ -20,8 +24,7 @@ namespace CvxLean open Lean Meta Elab Tactic -/- Generate Float expression from natural number. -TODO: Duplicate? Move? -/ +/- Generate Float expression from natural number. -/ def mkFloat (n : Nat) : Expr := mkApp3 (mkConst ``OfNat.ofNat [levelZero]) (mkConst ``Float) (mkNatLit n) (mkApp (mkConst ``instOfNatFloat) (mkNatLit n)) @@ -38,7 +41,6 @@ def mkOfNatExpr (i : Nat) (ty : Expr) : MetaM Expr := do /- Evaluate floating point expressions. -/ unsafe def evalFloat (e : Expr) : MetaM Float := do - check e evalExpr Float (mkConst ``Float) e /-- Generate an array of elements of a finite type -/ @@ -56,16 +58,17 @@ unsafe def elemsOfFintype (ty : Expr) : MetaM (Array Expr) := do let elemsr := (← elemsOfFintype tyr).map fun e => mkAppN (mkConst ``Sum.inr lvl) #[tyl, tyr, e] return elemsl ++ elemsr - | _ => throwError "Unsupported finite type: {ty}" + | _ => throwCoeffsError "unsupported finite type ({ty})." /- Evaluate floating point matrix expressions. -/ unsafe def evalFloatMatrix (e : Expr) : MetaM (Array (Array Float)) := do - let (tyn, tym) ← do (match (← inferType e) with - | .forallE _ tyn (.forallE _ tym (.const ``Float _) _) _ => - return (tyn, tym) - | .app (.app (.app (.const ``Matrix _) tyn) tym) (.const ``Float _) => - return (tyn, tym) - | _ => throwError "Not a float matrix: {e} {e.ctorName}.") + let (tyn, tym) ← + match (← inferType e) with + | .forallE _ tyn (.forallE _ tym (.const ``Float _) _) _ => + pure (tyn, tym) + | .app (.app (.app (.const ``Matrix _) tyn) tym) (.const ``Float _) => + pure (tyn, tym) + | _ => throwCoeffsError "not a float matrix ({e})." let elemsn ← elemsOfFintype tyn let elemsm ← elemsOfFintype tym let mut res := #[] @@ -77,9 +80,8 @@ unsafe def evalFloatMatrix (e : Expr) : MetaM (Array (Array Float)) := do res := res.push row return res -/- Create an expression that consists of an array of zeros of the given type -shape. Used to evaluate the constant term in a constraint or objective function. --/ +/- Create an expression that consists of an array of zeros of the given type shape. Used to evaluate +the constant term in a constraint or objective function. -/ partial def generateZerosOfShape (ty : Expr) : MetaM Expr := match ty.consumeMData with -- 1-dimensional variables. @@ -87,38 +89,34 @@ partial def generateZerosOfShape (ty : Expr) : MetaM Expr := return (mkFloat 0) -- Vectors. | .forallE _ ty (.const ``Float _) _ => - return (mkLambda `_ Lean.BinderInfo.default ty (mkFloat 0)) + return (mkLambda `_ BinderInfo.default ty (mkFloat 0)) -- Matrices. | .app (.app (.app (.const ``Matrix _) tyn) tym) (.const ``Float _) => do - return (mkLambda `_ Lean.BinderInfo.default tyn - ((mkLambda `_ Lean.BinderInfo.default tym) (mkFloat 0))) + return (mkLambda `_ BinderInfo.default tyn ((mkLambda `_ BinderInfo.default tym) (mkFloat 0))) -- Products. | .app (.app (.const ``Prod _) tyl) tyr => do let l ← generateZerosOfShape tyl let r ← generateZerosOfShape tyr return ← mkAppM ``Prod.mk #[l, r] - | _ => throwError "Unsupported type: {ty}" + | _ => throwCoeffsError "unsupported type ({ty})." -/- Create an array of expressions where each expression is an array of the given -type shape with zeros everywhere except one place. Serves as a basis and is used -to evaluate the coefficients in a constraint or objective function. Two arrays -are returned, one for scalar variables and one for matrix variables. -/ +/- Create an array of expressions where each expression is an array of the given type shape with +zeros everywhere except one place. Serves as a basis and is used to evaluate the coefficients in a +constraint or objective function. Two arrays are returned, one for scalar variables and one for +matrix variables. -/ unsafe def generateBasisOfShape (ty : Expr) : MetaM (Array Expr × Array Expr) := match ty.consumeMData with -- 1-dimensional variables. | .const ``Float _ => return (#[], #[mkFloat 1]) -- Vectors. - | .forallE _ - tyn - (.const ``Float _) _ => do + | .forallE _ tyn (.const ``Float _) _ => do let mut res := #[] for i in ← elemsOfFintype tyn do let b ← withLocalDeclD `i' tyn fun i' => do - let ite ← mkAppM ``ite - #[← mkEq i' i, mkFloat 1, mkFloat 0] - return ← mkLambdaFVars #[i'] $ ite + let ite ← mkAppM ``ite #[← mkEq i' i, mkFloat 1, mkFloat 0] + mkLambdaFVars #[i'] ite res := res.push b return (#[], res) -- Matrices. @@ -129,11 +127,10 @@ unsafe def generateBasisOfShape (ty : Expr) : MetaM (Array Expr × Array Expr) : for j in ← elemsOfFintype tym do let b ← withLocalDeclD `i' tyn fun i' => do withLocalDeclD `j' tym fun j' => do - let ite ← mkAppM ``ite - #[mkAnd (← mkEq i' i) (← mkEq j' j), - mkFloat 1, mkFloat 0] - return ← mkLambdaFVars #[i', j'] $ ite + let ite ← mkAppM ``ite #[mkAnd (← mkEq i' i) (← mkEq j' j), mkFloat 1, mkFloat 0] + mkLambdaFVars #[i', j'] ite res := res.push b + -- TODO: For now we're treating matrices as a bunch of scalars. return (#[], res) -- Products. @@ -141,8 +138,6 @@ unsafe def generateBasisOfShape (ty : Expr) : MetaM (Array Expr × Array Expr) : let r₀ ← generateZerosOfShape tyr let l₀ ← generateZerosOfShape tyl - -- TODO: This might be wrong. We want all the basis together but identify - -- when we put ones on the matrices. let (sls₁, mls₁) ← generateBasisOfShape tyl let sls ← sls₁.mapM fun l => mkAppM ``Prod.mk #[l, r₀] let mls ← mls₁.mapM fun l => mkAppM ``Prod.mk #[l, r₀] @@ -152,7 +147,7 @@ unsafe def generateBasisOfShape (ty : Expr) : MetaM (Array Expr × Array Expr) : let mrs ← mrs₁.mapM fun r => mkAppM ``Prod.mk #[l₀, r] return (sls ++ srs, mls ++ mrs) - | _ => throwError "Unsupported type: {ty}" + | _ => throwCoeffsError "unsupported type ({ty})." /- Generates list of constraints with all the vectors unrolled. -/ unsafe def unrollVectors (constraints : Expr) : MetaM (Array Expr) := do @@ -209,9 +204,8 @@ unsafe def unrollVectors (constraints : Expr) : MetaM (Array Expr) := do return res -/- Given an expression (scalar constraint or objective function) and a variable -`p` with type corresponding to the domain, return the coefficients and the -constant term. -/ +/- Given an expression (scalar constraint or objective function) and a variable `p` with type +corresponding to the domain, return the coefficients and the constant term. -/ unsafe def determineScalarCoeffsAux (e : Expr) (p : Expr) (fty : Expr) : MetaM (Array Float × Float) := do -- Constant part. @@ -227,7 +221,7 @@ unsafe def determineScalarCoeffsAux (e : Expr) (p : Expr) (fty : Expr) : coeffs := coeffs.push ((← evalFloat coeff) - const) return (coeffs, const) -/- Same as above, but for matrix affine constraints. -/ +/- Same as `determineScalarCoeffsAux`, but for matrix affine constraints. -/ unsafe def determineMatrixCoeffsAux (e : Expr) (p : Expr) (fty : Expr) : MetaM (Array (Array (Array Float)) × Array (Array Float)) := do -- Constant part. @@ -249,15 +243,16 @@ unsafe def determineMatrixCoeffsAux (e : Expr) (p : Expr) (fty : Expr) : coeffs := coeffs.push floatCoeff return (coeffs, const) -instance {n m : ℕ} : OfNat (Fin n.succ ⊕ Fin m.succ) (x) where - ofNat := if x <= n then Sum.inl (Fin.ofNat x) else Sum.inr (Fin.ofNat (x - n.succ)) - -/-- Indices to group constraints together and tag cones with the correct -dimension when translating the data into CBF format. This happens with the -exponential cone, quadratic cone and rotated quadratic cone, for instance. -/ +/-- Indices to group constraints together and tag cones with the correct dimension when translating +the data into CBF format. This happens with the exponential cone, quadratic cone and rotated +quadratic cone, for instance. -/ def ScalarAffineSections : Type := Array Nat -unsafe def determineCoeffsFromExpr (minExpr : Meta.MinimizationExpr) : +/-- Given a `MinimizationExpr`, representing a problem, assuming that it is in conic form, generate +a `ProblemData`. The expression is first translated to floats, then we find the coefficients of all +the affine terms involved in the cone membership constraints by plugging in the appropriate basis +vectors and matrices and computing. This function also keeps track of how the cones are split. -/ +unsafe def determineCoeffsFromExpr (minExpr : MinimizationExpr) : MetaM (ProblemData × ScalarAffineSections) := do let floatDomain ← realToFloat minExpr.domain @@ -277,7 +272,7 @@ unsafe def determineCoeffsFromExpr (minExpr : Meta.MinimizationExpr) : -- Coefficients for constraints. let mut idx := 0 for c in cs do - trace[Meta.debug] "Coeffs going through constraint {c}." + trace[CvxLean.debug] "`coeffs` going through constraint {c}." let mut isTrivial := false match Expr.consumeMData c with | .const ``True _ => do @@ -289,7 +284,6 @@ unsafe def determineCoeffsFromExpr (minExpr : Meta.MinimizationExpr) : idx := idx + 1 | .app (.const ``Real.posOrthCone _) e => do let e ← realToFloat e - trace[Meta.debug] "Coeffs going through posOrthCone {e}." let res ← determineScalarCoeffsAux e p floatDomain data := data.addPosOrthConstraint res.1 res.2 idx := idx + 1 @@ -297,8 +291,8 @@ unsafe def determineCoeffsFromExpr (minExpr : Meta.MinimizationExpr) : let res ← #[a, b, c].mapM fun e => do let e ← realToFloat e return ← determineScalarCoeffsAux e p floatDomain - -- NOTE: The order here is important. In MOSEK, x and z are swapped in - -- the definition of the EXP cone. + -- NOTE: The order here is important. In MOSEK, x and z are swapped in the definition of + -- the EXP cone. data := data.addExpConstraint res[2]!.1 res[2]!.2 data := data.addExpConstraint res[1]!.1 res[1]!.2 data := data.addExpConstraint res[0]!.1 res[0]!.2 @@ -307,8 +301,7 @@ unsafe def determineCoeffsFromExpr (minExpr : Meta.MinimizationExpr) : (.app (.const ``Fin _) n)) _) v) w) x => do let n : Nat ← evalExpr Nat (mkConst ``Nat) n -- TODO: This is a common issue with all vectors. - let xis ← (Array.range n).mapM - (fun i => return (mkApp x (← mkFinIdxExpr i n))) + let xis ← (Array.range n).mapM (fun i => return (mkApp x (← mkFinIdxExpr i n))) for e in (#[v, w] ++ xis) do let e ← realToFloat e let (ea, eb) ← determineScalarCoeffsAux e p floatDomain @@ -318,8 +311,7 @@ unsafe def determineCoeffsFromExpr (minExpr : Meta.MinimizationExpr) : (.app (.const ``Fin _) n)) _) t) x => do let n : Nat ← evalExpr Nat (mkConst ``Nat) n -- TODO: This is a common issue with all vectors. - let xis ← (Array.range n).mapM - (fun i => return (mkApp x (← mkFinIdxExpr i n))) + let xis ← (Array.range n).mapM (fun i => return (mkApp x (← mkFinIdxExpr i n))) for e in (#[t] ++ xis) do let e ← realToFloat e let (ea, eb) ← determineScalarCoeffsAux e p floatDomain @@ -356,7 +348,7 @@ unsafe def determineCoeffsFromExpr (minExpr : Meta.MinimizationExpr) : let (a, b) ← determineScalarCoeffsAux e p floatDomain data := data.addZeroConstraint a b idx := idx + 1 - | _ => throwError "No match: {c}." + | _ => throwCoeffsError "no match ({c})." -- New group, add idx. if !isTrivial then sections := sections.push idx From e128b0e444e27373e713907de51272aa272998a4 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Wed, 31 Jan 2024 16:13:57 -0500 Subject: [PATCH 05/52] fix: use "positive" and "nonneg" correctly --- CvxLean/Examples/TrussDesign.lean | 14 +++++++------- CvxLean/Examples/VehicleSpeedScheduling.lean | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CvxLean/Examples/TrussDesign.lean b/CvxLean/Examples/TrussDesign.lean index 678d3658..380c6588 100644 --- a/CvxLean/Examples/TrussDesign.lean +++ b/CvxLean/Examples/TrussDesign.lean @@ -163,8 +163,8 @@ equivalence' eqv₂/trussDesignConvex (hmin hmax : ℝ) (hmin_pos : 0 < hmin) equivalence eqv₃/trussDesignDCP (hmin hmax : ℝ) (hmin_pos : 0 < hmin) (hmin_le_hmax : hmin ≤ hmax) (wmin wmax : ℝ) (wmin_pos : 0 < wmin) (wmin_le_wmax : wmin ≤ wmax) (Rmax : ℝ) - (Rmax_nonneg : 0 < Rmax) (σ : ℝ) (σ_nonneg : 0 < σ) (F₁ : ℝ) (F₁_nonneg : 0 < F₁) (F₂ : ℝ) - (F₂_nonneg : 0 < F₂) : trussDesignConvex hmin hmax hmin_pos hmin_le_hmax wmin wmax wmin_pos + (Rmax_pos : 0 < Rmax) (σ : ℝ) (σ_pos : 0 < σ) (F₁ : ℝ) (F₁_pos : 0 < F₁) (F₂ : ℝ) + (F₂_pos : 0 < F₂) : trussDesignConvex hmin hmax hmin_pos hmin_le_hmax wmin wmax wmin_pos wmin_le_wmax Rmax σ F₁ F₂ := by -- Apply pre-DCP. pre_dcp @@ -213,32 +213,32 @@ lemma wminₚ_le_wmaxₚ : wminₚ ≤ wmaxₚ := by def Rmaxₚ : ℝ := 10 @[simp high] -lemma Rmaxₚ_nonneg : 0 < Rmaxₚ := by +lemma Rmaxₚ_pos : 0 < Rmaxₚ := by unfold Rmaxₚ; norm_num @[optimization_param] def σₚ : ℝ := 0.5 @[simp high] -lemma σₚ_nonneg : 0 < σₚ := by +lemma σₚ_pos : 0 < σₚ := by unfold σₚ; norm_num @[optimization_param] def F₁ₚ : ℝ := 10 @[simp high] -lemma F₁ₚ_nonneg : 0 < F₁ₚ := by +lemma F₁ₚ_pos : 0 < F₁ₚ := by unfold F₁ₚ; norm_num @[optimization_param] def F₂ₚ : ℝ := 20 @[simp high] -lemma F₂ₚ_nonneg : 0 < F₂ₚ := by +lemma F₂ₚ_pos : 0 < F₂ₚ := by unfold F₂ₚ; norm_num solve trussDesignDCP hminₚ hmaxₚ hminₚ_pos hminₚ_le_hmaxₚ wminₚ wmaxₚ wminₚ_pos wminₚ_le_wmaxₚ Rmaxₚ - Rmaxₚ_nonneg σₚ σₚ_nonneg F₁ₚ F₁ₚ_nonneg F₂ₚ F₂ₚ_nonneg + Rmaxₚ_pos σₚ σₚ_pos F₁ₚ F₁ₚ_pos F₂ₚ F₂ₚ_pos -- There are two non-trivial backward maps here, one from `eqv₁` and one from `eqv₂`, so we need to -- apply both of them. diff --git a/CvxLean/Examples/VehicleSpeedScheduling.lean b/CvxLean/Examples/VehicleSpeedScheduling.lean index e9340ec8..b66d8a37 100644 --- a/CvxLean/Examples/VehicleSpeedScheduling.lean +++ b/CvxLean/Examples/VehicleSpeedScheduling.lean @@ -109,7 +109,7 @@ equivalence' eqv₂/vehSpeedSchedQuadratic (n : ℕ) (d : Fin n → ℝ) cases div_pos_iff.mp h_di_div_ti_pos with | inl h_pos => exact h_pos.2 | inr h_neg => have d_pos_i := h_d_pos i; simp at d_pos_i ⊢; linarith [h_neg.1, d_pos_i] - -- Add constraint to tell the system that `t` is `ε`-nonnegative. + -- Add constraint to tell the system that `t` is positive. equivalence_step => apply Equivalence.add_constraint (cs' := fun t => StrongLT 0 t) . rintro t ⟨c_smin, _⟩ i From 90572b1eb2e9abf8a71f39fc7390ccfff31ccb08 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Thu, 1 Feb 2024 15:48:03 -0500 Subject: [PATCH 06/52] doc: `Command/Solve/Float` --- CvxLean/Command/Equivalence.lean | 2 +- CvxLean/Command/Solve/Conic.lean | 2 +- CvxLean/Command/Solve/Float/Coeffs.lean | 2 +- CvxLean/Command/Solve/Float/FloatToReal.lean | 13 +- CvxLean/Command/Solve/Float/ProblemData.lean | 113 ++++++++-------- .../Command/Solve/Float/RealToFloatCmd.lean | 110 +++++++++++++++ .../Command/Solve/Float/RealToFloatExt.lean | 27 ++-- ...alToFloat.lean => RealToFloatLibrary.lean} | 125 ++---------------- CvxLean/Lib/Math/Data/Matrix.lean | 3 + CvxLean/Lib/Math/Data/Vec.lean | 7 +- CvxLean/Meta/Util/Error.lean | 11 +- CvxLean/Test/Solve/RealToFloat.lean | 2 +- 12 files changed, 226 insertions(+), 191 deletions(-) create mode 100644 CvxLean/Command/Solve/Float/RealToFloatCmd.lean rename CvxLean/Command/Solve/Float/{RealToFloat.lean => RealToFloatLibrary.lean} (56%) diff --git a/CvxLean/Command/Equivalence.lean b/CvxLean/Command/Equivalence.lean index 8a489eec..2a824905 100644 --- a/CvxLean/Command/Equivalence.lean +++ b/CvxLean/Command/Equivalence.lean @@ -4,7 +4,7 @@ import CvxLean.Syntax.Minimization import CvxLean.Meta.Util.Expr import CvxLean.Meta.Equivalence import CvxLean.Meta.TacticBuilder -import CvxLean.Command.Solve.Float.RealToFloat +import CvxLean.Command.Solve.Float.RealToFloatLibrary import CvxLean.Tactic.Basic.ChangeOfVariables namespace CvxLean diff --git a/CvxLean/Command/Solve/Conic.lean b/CvxLean/Command/Solve/Conic.lean index 38478209..87a30266 100644 --- a/CvxLean/Command/Solve/Conic.lean +++ b/CvxLean/Command/Solve/Conic.lean @@ -148,7 +148,7 @@ unsafe def exprFromSol (minExpr : MinimizationExpr) (sol : Sol.Result) : MetaM E -- Generate solution of the correct shape. let solPointExprArrayRaw : Array Expr := - Array.mk <| sol.vars.map (fun v => floatToRealExpr v.activity) + Array.mk <| sol.vars.map (fun v => floatToReal v.activity) -- Vectors and matrices as functions. let mut solPointExprArray : Array Expr := #[] diff --git a/CvxLean/Command/Solve/Float/Coeffs.lean b/CvxLean/Command/Solve/Float/Coeffs.lean index 1c8dce59..a89b7060 100644 --- a/CvxLean/Command/Solve/Float/Coeffs.lean +++ b/CvxLean/Command/Solve/Float/Coeffs.lean @@ -4,7 +4,7 @@ import CvxLean.Meta.Util.Error import CvxLean.Meta.Util.Debug import CvxLean.Syntax.Minimization import CvxLean.Command.Solve.Float.ProblemData -import CvxLean.Command.Solve.Float.RealToFloat +import CvxLean.Command.Solve.Float.RealToFloatLibrary /-! # Extract coefficients from problem to generate problem data diff --git a/CvxLean/Command/Solve/Float/FloatToReal.lean b/CvxLean/Command/Solve/Float/FloatToReal.lean index ef0aaf9a..4799d5f2 100644 --- a/CvxLean/Command/Solve/Float/FloatToReal.lean +++ b/CvxLean/Command/Solve/Float/FloatToReal.lean @@ -1,10 +1,17 @@ -import Lean import Mathlib.Data.Real.Basic import CvxLean.Lib.Math.Data.Real +/-! +Conversion used in `Solve/Conic.lean` to read the solver's output into an expression to which we can +apply the backward map. +-/ + +namespace CvxLean + open Lean -def floatToRealExpr (f : Float) : Expr := +/-- Convert a `Float` to an `Expr` of type `Real`. -/ +def floatToReal (f : Float) : Expr := let divisionRingToOfScientific := mkApp2 (mkConst ``DivisionRing.toOfScientific ([levelZero] : List Level)) (mkConst ``Real) @@ -25,3 +32,5 @@ def floatToRealExpr (f : Float) : Expr := | Parsec.ParseResult.error _ _ => mkApp3 realOfScientific (mkConst ``Int.zero) (toExpr true) (mkNatLit 1) + +end CvxLean diff --git a/CvxLean/Command/Solve/Float/ProblemData.lean b/CvxLean/Command/Solve/Float/ProblemData.lean index 1510c019..30c5929b 100644 --- a/CvxLean/Command/Solve/Float/ProblemData.lean +++ b/CvxLean/Command/Solve/Float/ProblemData.lean @@ -1,3 +1,14 @@ +/-! +# Representation of numerical problem data + +This file defines an intermediate representation of a problem in conic form at the level of floats. +We provide an interface to build this data by adding any of the possible types of constraints. This +data can then be transformed in a straightforward manner into Conic Benchmark Format, for example. + +The procedure that creates `ProblemData` from an optimization problem can be found in +`Command/Solve/Coeffs.lean`. +-/ + namespace CvxLean /-- Cones admitting scalar affine constraints. Note that the only cone that admits matrix affine @@ -9,15 +20,15 @@ namespace ScalarConeType instance : ToString ScalarConeType where toString - | Zero => "Zero" - | PosOrth => "PosOrth" - | Exp => "Exp" - | Q => "Q" - | QR => "QR" + | Zero => "Zero" + | PosOrth => "PosOrth" + | Exp => "Exp" + | Q => "Q" + | QR => "QR" end ScalarConeType -/-- Encoding the constraint ⟨X, A⟩ + ∑ i, aᵢ xᵢ + b. -/ +/-- Encodes the expression `⟨X, A⟩ + ∑ i, aᵢ xᵢ + b`. -/ structure ScalarAffine := (n m : Nat) (A : Array (Array Float)) @@ -31,7 +42,7 @@ instance : ToString ScalarAffine where end ScalarAffine -/-- Encoding the constraint ∑ i, xᵢ • Hᵢ + D -/ +/-- Encodies the expression `∑ i, xᵢ • Hᵢ + D`. -/ structure MatrixAffine := (n : Nat) (H : Array (Array (Array Float))) @@ -40,12 +51,12 @@ structure MatrixAffine := namespace MatrixAffine instance : ToString MatrixAffine where - toString sa := - s!"MatrixAffine [{sa.n}, {sa.H}, {sa.D}]" + toString sa := s!"MatrixAffine [{sa.n}, {sa.H}, {sa.D}]" end MatrixAffine -/-- Coeffs generates this from the problem goal. -/ +/-- Data structure storing the floating-point coefficient of the objective function and constraints +of a problem in conic form. -/ structure ProblemData := (objective : Option ScalarAffine) (scalarAffineConstraints : Array (ScalarAffine × ScalarConeType)) @@ -70,75 +81,65 @@ def empty : ProblemData := instance : Inhabited ProblemData where default := empty -/-- Set full objective function. -/ -def setObjective (data : ProblemData) - (A : Array (Array Float)) (a : Array Float) (b : Float) - : ProblemData := +/-- Set full objective function of the form `⟨X, A⟩ + ∑ i, aᵢ xᵢ + b`. -/ +def setObjective (data : ProblemData) (A : Array (Array Float)) (a : Array Float) (b : Float) : + ProblemData := { data with objective := ScalarAffine.mk A.size a.size A a b } -/-- Same but only ∑ i, aᵢxᵢ + b. -/ -def setObjectiveOnlyVector (data : ProblemData) - (a : Array Float) (b : Float) - : ProblemData := +/-- Same as `setObjective` if the objective is of the form `∑ i, aᵢxᵢ + b`. -/ +def setObjectiveOnlyVector (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := data.setObjective #[] a b -/-- Same but only ⟨A, X⟩ + b. -/ -def setObjectiveOnlyMatrix (data : ProblemData) - (A : Array (Array Float)) (b : Float) - : ProblemData := +/-- Same as `setObjective` if the objective is of the form `⟨A, X⟩ + b`. -/ +def setObjectiveOnlyMatrix (data : ProblemData) (A : Array (Array Float)) (b : Float) : + ProblemData := data.setObjective A #[] b -/-- Add a full scalar affine constraint to the problem data. -/ -def addScalarAffineConstraint (data : ProblemData) - (A : Array (Array Float)) (a : Array Float) (b : Float) (sct : ScalarConeType) - : ProblemData := +/-- Add a scalar affine constraint of the form ``⟨X, A⟩ + ∑ i, aᵢ xᵢ + b ∈ 𝒦`, where `𝒦` is a cone +of type `sct`. -/ +def addScalarAffineConstraint (data : ProblemData) (A : Array (Array Float)) (a : Array Float) + (b : Float) (sct : ScalarConeType) : ProblemData := let constraint := ScalarAffine.mk A.size a.size A a b { data with scalarAffineConstraints := data.scalarAffineConstraints.push ⟨constraint, sct⟩ } -/-- Add a scalar affine constraint without the ⟨A, X⟩ part. -/ -def addScalarAffineConstraintOnlyVector (data : ProblemData) - (a : Array Float) (b : Float) (sct : ScalarConeType) - : ProblemData := +/-- Same as `addScalarAffineConstraint` if the constraint is of the form `∑ i, aᵢ xᵢ + b ∈ 𝒦`. -/ +def addScalarAffineConstraintOnlyVector (data : ProblemData) (a : Array Float) (b : Float) + (sct : ScalarConeType) : ProblemData := data.addScalarAffineConstraint #[] a b sct -/-- Specialized to the zero cone. -/ -def addZeroConstraint (data : ProblemData) - (a : Array Float) (b : Float) - : ProblemData := +/-- Add zero cone constraint `∑ i, aᵢxᵢ + b ∈ 0` to problem data. -/ +def addZeroConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := data.addScalarAffineConstraintOnlyVector a b ScalarConeType.Zero -/-- Specialized to the exponential cone. -/ -def addExpConstraint (data : ProblemData) - (a : Array Float) (b : Float) - : ProblemData := +/-- Add exponential cone constraint `∑ i, aᵢxᵢ + b ∈ 𝒦ₑ` to problem data. Note that the +second-order cone is `3`-dimensional, so to capture `(x, y, z) ∈ 𝒦ₑ` we do `x ∈ 𝒦ₑ`, `y ∈ 𝒦ₑ`, and +`z ∈ 𝒦ₑ` consecutively. We keep track of how to group consecutive constraints in +`Command/Solve/Float/Coeffs.lean`. -/ +def addExpConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := data.addScalarAffineConstraintOnlyVector a b ScalarConeType.Exp -/-- Specialized to the positive orthant cone. -/ -def addPosOrthConstraint (data : ProblemData) - (a : Array Float) (b : Float) - : ProblemData := +/-- Add positive orthant cone constraint `∑ i, aᵢxᵢ + b ∈ ℝ₊` to problem data. -/ +def addPosOrthConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := data.addScalarAffineConstraintOnlyVector a b ScalarConeType.PosOrth -/- Specialized to the second-order cone. -/ -def addSOConstraint (data : ProblemData) - (a : Array Float) (b : Float) - : ProblemData := +/-- Add second-order cone constraint `∑ i, aᵢxᵢ + b ∈ Q` to problem data. Note that the second-order +cone is `n+1`-dimensional. The same remark on grouping constraints in `addExpConstraint` applies. -/ +def addSOConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := data.addScalarAffineConstraintOnlyVector a b ScalarConeType.Q -/- Specialized to the quadratic rotated cone. -/ -def addRotatedSOConstraint (data : ProblemData) - (a : Array Float) (b : Float) - : ProblemData := +/- Add second-order cone constraint `∑ i, aᵢxᵢ + b ∈ Qᵣ` to problem data. Note that the rotated +second-order cone is `n+2`-dimensional. The same remark on grouping constraints in +`addExpConstraint` applies. -/ +def addRotatedSOConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := data.addScalarAffineConstraintOnlyVector a b ScalarConeType.QR -/-- Add a full matrix affine constraint to the problem data. -/ -def addMatrixAffineConstraint (data : ProblemData) - (H : Array (Array (Array Float))) (D : Array (Array Float)) - : ProblemData := +/-- Add a matrix affine constraint `∑ i, xᵢ • Hᵢ + D ∈ 𝒮₊ⁿ` to problem data. The only matrix cone +we consider is the PSD cone. -/ +def addMatrixAffineConstraint (data : ProblemData) (H : Array (Array (Array Float))) + (D : Array (Array Float)) : ProblemData := let constraint := MatrixAffine.mk D.size H D - { data with matrixAffineConstraints := - data.matrixAffineConstraints.push constraint } + { data with matrixAffineConstraints := data.matrixAffineConstraints.push constraint } end ProblemData diff --git a/CvxLean/Command/Solve/Float/RealToFloatCmd.lean b/CvxLean/Command/Solve/Float/RealToFloatCmd.lean new file mode 100644 index 00000000..90cb1f95 --- /dev/null +++ b/CvxLean/Command/Solve/Float/RealToFloatCmd.lean @@ -0,0 +1,110 @@ +import CvxLean.Lib.Math.Data.Real +import CvxLean.Lib.Math.Data.Vec +import CvxLean.Lib.Math.Data.Matrix +import CvxLean.Lib.Math.CovarianceEstimation +import CvxLean.Lib.Math.LogDet +import CvxLean.Lib.Cones.All +import CvxLean.Meta.Util.Expr +import CvxLean.Meta.Util.Error +import CvxLean.Meta.Minimization +import CvxLean.Syntax.OptimizationParam +import CvxLean.Command.Solve.Float.RealToFloatExt + +/-! +# Conversion of Real to Float (command) + +We define the `realToFloat` function to go from real `Expr`s to float `Expr`s as well as the +`addRealToFloat` command to add new translations and the `#realToFloat` command to test the +translation. +-/ + +namespace CvxLean + +open Lean Lean.Elab Lean.Meta Lean.Elab.Command Lean.Elab.Term + +syntax (name := addRealToFloatCommand) + "addRealToFloat" Lean.Parser.Term.funBinder* ":" term ":=" term : command + +/-- Traverse expression recursively and if a match is found in the real-to-float library return the +float version. The "correctness" of this function depends on the translations defined in the +library. -/ +partial def realToFloat (e : Expr) : MetaM Expr := do + let e ← e.removeMData + let discrTree ← getRealToFloatDiscrTree + let translations ← discrTree.getMatch e + for translation in translations do + let (mvars, _, pattern) ← lambdaMetaTelescope translation.real + if ← isDefEq pattern e then + -- TODO: Search for conditions. + let args ← mvars.mapM instantiateMVars + return mkAppNBeta translation.float args + else + throwRealToFloatError "no match for \n{pattern} \n{e}" + match e with + | Expr.app a b => return mkApp (← realToFloat a) (← realToFloat b) + | Expr.lam n ty b d => do + withLocalDecl n d (← realToFloat ty) fun fvar => do + let b := b.instantiate1 fvar + let bF ← realToFloat b + mkLambdaFVars #[fvar] bF + | Expr.forallE n ty b d => do + withLocalDecl n d (← realToFloat ty) fun fvar => do + let b := b.instantiate1 fvar + let bF ← realToFloat b + mkForallFVars #[fvar] bF + | Expr.mdata m e => return mkMData m (← realToFloat e) + | Expr.letE n ty t b _ => return mkLet n (← realToFloat ty) (← realToFloat t) (← realToFloat b) + | Expr.proj typeName idx struct => return mkProj typeName idx (← realToFloat struct) + | e@(Expr.const n _) => do + if ← isOptimizationParam n then + let paramExpr ← getOptimizationParamExpr n e + let paramExprF ← realToFloat paramExpr + -- Also add the float the definition of the parameter to the the environment if it is not + -- there already. + try + let nF := n ++ `float + if !(← getEnv).contains nF then + simpleAddAndCompileDefn nF paramExprF + catch e => + throwRealToFloatError "failed to create `{n}.float`.\n{e.toMessageData}" + return paramExprF + else + return e + | _ => return e + +/-- The `addRealToFloat` command, which simply adds the user-defined expressions to the environment +extension. -/ +@[command_elab addRealToFloatCommand] +def elabAddRealToFloatCommand : CommandElab +| `(addRealToFloat : $real := $float) => + liftTermElabM do + let real ← elabTermAndSynthesize real.raw none + let float ← elabTermAndSynthesize float.raw none + addRealToFloatData { real := real, float := float } +| _ => throwUnsupportedSyntax + +macro_rules +| `(addRealToFloat $idents:funBinder* : $real := $float) => do + if idents.size != 0 then + `(addRealToFloat : fun $idents:funBinder* => $real := fun $idents:funBinder* => $float) + else + Macro.throwUnsupported + +syntax (name:=realToFloatCommand) + "#realToFloat" term : command + +/-- Transform the given expression to its float version and log the result. -/ +@[command_elab realToFloatCommand] +unsafe def elabRealToFloatCommand : CommandElab +| `(#realToFloat $stx) => + liftTermElabM do + let e ← elabTermAndSynthesize stx.raw none + let res ← realToFloat e + check res + logInfo m!"{res}" + if Expr.isConstOf (← inferType res) ``Float then + let res ← Meta.evalExpr Float (mkConst ``Float) res + logInfo m!"{res}" +| _ => throwUnsupportedSyntax + +end CvxLean diff --git a/CvxLean/Command/Solve/Float/RealToFloatExt.lean b/CvxLean/Command/Solve/Float/RealToFloatExt.lean index 4ca6fa02..cc693918 100644 --- a/CvxLean/Command/Solve/Float/RealToFloatExt.lean +++ b/CvxLean/Command/Solve/Float/RealToFloatExt.lean @@ -1,46 +1,49 @@ import CvxLean.Tactic.DCP.DiscrTree +/-! +# Conversion of Real to Float (environment extension) + +This files defines the persistent environment extension used to store the real-to-float translation. +-/ + namespace CvxLean -open Lean Lean.Meta +open Lean Meta -/-- Data structure to store information about registered translation. -/ +/-- Data structure to store the real-to-float translation. -/ structure RealToFloatData where real : Expr float : Expr deriving BEq, Inhabited -instance : ToMessageData RealToFloatData := { +instance : ToMessageData RealToFloatData where toMessageData := fun d => m!"real: {d.real} float: {d.float}" -} +/-- Type of persistent environment extension for real-to-float. We use a discrimination tree. -/ def RealToFloatExtension := PersistentEnvExtension (Array Key × RealToFloatData) (Array Key × RealToFloatData) (DiscrTree RealToFloatData) deriving Inhabited initialize realToFloatExtension : RealToFloatExtension ← do - let realToFloatExtension ← registerPersistentEnvExtension { + registerPersistentEnvExtension { name := `realToFloatExtension mkInitial := return {} addImportedFn := fun as _ctx => - return mkStateFromImportedEntries - (fun (s : DiscrTree RealToFloatData) (d : Array Key × RealToFloatData) => - s.insertCore d.1 d.2) {} as, + return mkStateFromImportedEntries (fun s d => s.insertCore d.1 d.2) {} as, addEntryFn := fun s d => s.insertCore d.1 d.2, exportEntriesFn := fun s => s.toArray } - return realToFloatExtension -/-- Add a new translatio. -/ +/-- Add a new real-to-float translation to the environment. -/ def addRealToFloatData (data : RealToFloatData) : MetaM Unit := do let (_, _, expr) ← lambdaMetaTelescope data.real let keys ← DiscrTree.mkPath expr - setEnv $ realToFloatExtension.addEntry (← getEnv) (keys, data) + setEnv <| realToFloatExtension.addEntry (← getEnv) (keys, data) -/-- -/ +/-- Get the discrimination tree of all real-to-float translations. -/ def getRealToFloatDiscrTree : MetaM (DiscrTree RealToFloatData) := do return realToFloatExtension.getState (← getEnv) diff --git a/CvxLean/Command/Solve/Float/RealToFloat.lean b/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean similarity index 56% rename from CvxLean/Command/Solve/Float/RealToFloat.lean rename to CvxLean/Command/Solve/Float/RealToFloatLibrary.lean index c8981bdd..1ca7830c 100644 --- a/CvxLean/Command/Solve/Float/RealToFloat.lean +++ b/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean @@ -1,111 +1,16 @@ -import CvxLean.Lib.Math.Data.Real -import CvxLean.Lib.Math.Data.Vec -import CvxLean.Lib.Math.Data.Matrix -import CvxLean.Lib.Cones.All -import CvxLean.Lib.Math.CovarianceEstimation -import CvxLean.Lib.Math.LogDet -import CvxLean.Syntax.OptimizationParam -import CvxLean.Meta.Util.Expr -import CvxLean.Meta.Minimization -import CvxLean.Command.Solve.Float.RealToFloatExt +import CvxLean.Command.Solve.Float.RealToFloatCmd -namespace CvxLean +/-! +# Conversion of Real to Float (library) -open Lean Lean.Elab Lean.Meta Lean.Elab.Command Lean.Elab.Term - -syntax (name := addRealToFloatCommand) - "addRealToFloat" Lean.Parser.Term.funBinder* ":" term ":=" term : command - -/-- -/ -partial def realToFloat (e : Expr) : MetaM Expr := do - let e ← e.removeMData - let discrTree ← getRealToFloatDiscrTree - let translations ← discrTree.getMatch e - for translation in translations do - let (mvars, _, pattern) ← lambdaMetaTelescope translation.real - if ← isDefEq pattern e then - -- TODO: Search for conditions. - let args ← mvars.mapM instantiateMVars - return mkAppNBeta translation.float args - else - trace[Meta.debug] "`real-to-float` error: no match for \n{pattern} \n{e}" - match e with - | Expr.app a b => return mkApp (← realToFloat a) (← realToFloat b) - | Expr.lam n ty b d => do - withLocalDecl n d (← realToFloat ty) fun fvar => do - let b := b.instantiate1 fvar - let bF ← realToFloat b - mkLambdaFVars #[fvar] bF - -- return mkLambda n d (← realToFloat ty) (← realToFloat b) - | Expr.forallE n ty b d => do - withLocalDecl n d (← realToFloat ty) fun fvar => do - let b := b.instantiate1 fvar - let bF ← realToFloat b - mkForallFVars #[fvar] bF - -- return mkForall n d (← realToFloat ty) (← realToFloat b) - | Expr.mdata m e => return mkMData m (← realToFloat e) - | Expr.letE n ty t b _ => return mkLet n (← realToFloat ty) (← realToFloat t) (← realToFloat b) - | Expr.proj typeName idx struct => return mkProj typeName idx (← realToFloat struct) - | e@(Expr.const n _) => do - if ← isOptimizationParam n then - let paramExpr ← getOptimizationParamExpr n e - let paramExprF ← realToFloat paramExpr - -- Also add the float the definition of the parameter to the the environment if it is not - -- there already. - try - let nF := n ++ `float - if !(← getEnv).contains nF then - Lean.simpleAddAndCompileDefn (n ++ `float) paramExprF - catch e => - trace[Meta.debug] (s!"`real-to-float` error: failed to create `{n}.float`.\n" ++ - m!"{e.toMessageData}") - return paramExprF - else - return e - | _ => return e - -/- -/ -def realSolutionToFloat (s : Meta.SolutionExpr) : MetaM Meta.SolutionExpr := do - let fDomain ← realToFloat s.domain - let fCodomain ← realToFloat s.codomain - let fCodomainPreorder ← realToFloat s.codomainPreorder - let fP ← realToFloat s.p - return Meta.SolutionExpr.mk fDomain fCodomain fCodomainPreorder fP - -@[macro addRealToFloatCommand] partial def AddRealToFloatCommand : Macro -| `(addRealToFloat $idents:funBinder* : $real := $float) => do - if idents.size != 0 then - let c ← `(addRealToFloat : fun $idents:funBinder* => $real := fun $idents:funBinder* => $float) - return c.raw - else - Macro.throwUnsupported -| _ => Macro.throwUnsupported - -@[command_elab addRealToFloatCommand] -def elabAddRealToFloatCommand : CommandElab -| `(addRealToFloat : $real := $float) => - liftTermElabM do - let real ← elabTermAndSynthesize real.raw none - let float ← elabTermAndSynthesize float.raw none - addRealToFloatData {real := real, float := float} -| _ => throwUnsupportedSyntax - -syntax (name:=realToFloatCommand) - "#realToFloat" term : command - -@[command_elab realToFloatCommand] -unsafe def elabRealToFloatCommand : CommandElab -| `(#realToFloat $stx) => - liftTermElabM do - let e ← elabTermAndSynthesize stx.raw none - let res ← realToFloat e - check res - logInfo m!"{res}" - if Expr.isConstOf (← inferType res) ``Float then - let res ← Meta.evalExpr Float (mkConst ``Float) res - logInfo m!"{res}" -| _ => throwUnsupportedSyntax +We define all the specific real-to-float translations here. Any issues with real-to-float are +likely due to missing translations. Users need to make sure that the types of the translations +correspond to the required types. For example, an expression of type `ℝ` needs to map to an +expression of type `Float`, and an expression of type `Fin n → ℝ → ℝ` needs to map to an expression +of type `Fin n → Float → Float`. +-/ +namespace CvxLean section Basic @@ -214,11 +119,8 @@ addRealToFloat : Real.pi := addRealToFloat : @Real.exp := Float.exp -def Float.Vec.exp.{u} {m : Type u} (x : m → Float) : m → Float := - fun i => Float.exp (x i) - addRealToFloat : @Vec.exp.{0} := - @Float.Vec.exp.{0} + @Vec.Computable.exp.{0} addRealToFloat : @Real.sqrt := Float.sqrt @@ -226,11 +128,8 @@ addRealToFloat : @Real.sqrt := addRealToFloat : @Real.log := Float.log -def Float.norm {n : ℕ} (x : Fin n → Float) : Float := - Float.sqrt (Vec.Computable.sum (fun i => (Float.pow (x i) 2))) - addRealToFloat (n) (i) : @Norm.norm.{0} (Fin n → ℝ) i := - @Float.norm n + @Vec.Computable.norm n addRealToFloat (i) : @OfScientific.ofScientific Real i := Float.ofScientific diff --git a/CvxLean/Lib/Math/Data/Matrix.lean b/CvxLean/Lib/Math/Data/Matrix.lean index c567bf31..31f563a7 100644 --- a/CvxLean/Lib/Math/Data/Matrix.lean +++ b/CvxLean/Lib/Math/Data/Matrix.lean @@ -101,6 +101,9 @@ def fromBlocks {l : Type} {m : Type} {n : Type} {o : Type} {α : Type} : def toUpperTri (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float := fun i j => if i ≤ j then A i j else 0 +def norm {n m : ℕ} (A : Matrix (Fin n) (Fin m) Float) : Fin n → Float := + fun i => Vec.Computable.norm (A i) + end Computable end Matrix diff --git a/CvxLean/Lib/Math/Data/Vec.lean b/CvxLean/Lib/Math/Data/Vec.lean index b4604092..c43956ae 100644 --- a/CvxLean/Lib/Math/Data/Vec.lean +++ b/CvxLean/Lib/Math/Data/Vec.lean @@ -130,8 +130,11 @@ def sum (x : Fin n → Float) : Float := def cumsum (x : Fin n → Float) : Fin n → Float := fun i => (((toArray x).toList.take (i.val + 1)).foldl Float.add 0) -def norm {n m : ℕ} (x : Fin n → Fin m → Float) : Fin n → Float := - fun i => Float.sqrt (sum (Vec.map (Float.pow · 2) (x i))) +def norm {n : ℕ} (x : Fin n → Float) : Float := + Float.sqrt (sum (fun i => (Float.pow (x i) 2))) + +def exp {m} (x : m → Float) : m → Float := + fun i => Float.exp (x i) end Computable diff --git a/CvxLean/Meta/Util/Error.lean b/CvxLean/Meta/Util/Error.lean index c11ae78a..7bd5a23a 100644 --- a/CvxLean/Meta/Util/Error.lean +++ b/CvxLean/Meta/Util/Error.lean @@ -4,7 +4,14 @@ import Lean Custom error messages. -/ -syntax "throwCoeffsError " (interpolatedStr(term) <|> term) : term +syntax "throwCoeffsError " interpolatedStr(term) : term macro_rules - | `(throwCoeffsError $msg:interpolatedStr) => `(throwError ("`coeffs` error: " ++ (m! $msg))) + | `(throwCoeffsError $msg:interpolatedStr) => + `(throwError ("`coeffs` error: " ++ (m! $msg))) + +syntax "throwRealToFloatError " interpolatedStr(term) : term + +macro_rules + | `(throwRealToFloatError $msg:interpolatedStr) => + `(throwError ("`real-to-float` error: " ++ (m! $msg))) diff --git a/CvxLean/Test/Solve/RealToFloat.lean b/CvxLean/Test/Solve/RealToFloat.lean index bc6e3e0e..b51c0145 100644 --- a/CvxLean/Test/Solve/RealToFloat.lean +++ b/CvxLean/Test/Solve/RealToFloat.lean @@ -1,6 +1,6 @@ import CvxLean.Lib.Minimization import CvxLean.Syntax.OptimizationParam -import CvxLean.Command.Solve.Float.RealToFloat +import CvxLean.Command.Solve.Float.RealToFloatLibrary section RealToFloat From 3d9d82675d05f7aa4926de6cc83ccf036002e733 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Thu, 1 Feb 2024 16:04:45 -0500 Subject: [PATCH 07/52] doc: `Command/Solve/Mosek` --- CvxLean/Command/Solve/Mosek/CBF.lean | 181 +++++++++++--------------- CvxLean/Command/Solve/Mosek/Sol.lean | 184 +++++++++++++-------------- 2 files changed, 167 insertions(+), 198 deletions(-) diff --git a/CvxLean/Command/Solve/Mosek/CBF.lean b/CvxLean/Command/Solve/Mosek/CBF.lean index affeb318..ad684b96 100644 --- a/CvxLean/Command/Solve/Mosek/CBF.lean +++ b/CvxLean/Command/Solve/Mosek/CBF.lean @@ -1,15 +1,15 @@ -/- --/ - /-! # Conic Benchmark Format See . + +The main definition is `CBF.Problem`, the rest is a series of functions to make an element of such +type. -/ namespace CBF -/-- Objective sense. Either a maximisation or minimisation problem. -/ +/-- Objective sense. Either a maximization or minimization problem. -/ inductive ObjSense | MAX | MIN @@ -22,8 +22,10 @@ instance : ToString ObjSense where end ObjSense -/-- Cone type: free, positive orthant, negative orthant, fixpoint zero (for -equality), quadratic, quadratic rotated or exponential. -/ +/-- Cone type: free, positive orthant, negative orthant, fixpoint zero (for equality), quadratic, +quadratic rotated or exponential. The quadratic cone is called second-order in other parts of the +project. Note that this definition is independent from the cone types in +`Command/Solve/Float/ProblemData.lean`. -/ inductive ConeType | F | LPos | LNeg | LEq | Q | QR | EXP deriving DecidableEq @@ -54,8 +56,8 @@ instance : ToString Cone where end Cone -/-- Holds the total dimension (n), number of cones (k), and a list of cones -([t₁, d₁], ..., [tₖ, dₖ]). We must have that ∑dᵢ = n. -/ +/-- Holds the total dimension (`n`), number of cones (`k`), and a list of cones +(`[t₁, d₁], ..., [tₖ, dₖ]`). We must have that `∑dᵢ = n`. -/ structure ConeProduct where n : Nat k : Nat @@ -121,7 +123,7 @@ instance : Decidable (isEmpty ev) := decEq _ _ end EncodedValue -/-- Represents the value aᵢ. -/ +/-- Represents the value `aᵢ`. -/ structure EncodedVectorEntry where i : Nat value : Float @@ -136,7 +138,7 @@ def fromIndexAndValue (i : Nat) (v : Float) : EncodedVectorEntry := end EncodedVectorEntry -/-- Represents the vector a = (aᵢ) where n entries are non-zero. -/ +/-- Represents the vector `a = (aᵢ)` where `n` entries are non-zero. -/ structure EncodedVector where n : Nat values : List EncodedVectorEntry @@ -155,7 +157,7 @@ def isEmpty (ev : EncodedVector) : Prop := ev.n = 0 instance : Decidable (isEmpty ev) := Nat.decEq _ _ def addEntry (ev : EncodedVector) (eve : EncodedVectorEntry) : EncodedVector := - if eve.value < 0 ∨ eve.value > 0 then + if eve.value < 0 || eve.value > 0 then EncodedVector.mk (ev.n + 1) (ev.values.concat eve) else ev @@ -164,7 +166,7 @@ def stack (ev1 ev2 : EncodedVector) : EncodedVector := EncodedVector.mk (ev1.n + ev2.n) (ev1.values ++ ev2.values) def fromIndexAndValue (i : Nat) (v : Float) : EncodedVector := - if v > 0 ∨ v < 0 then + if v > 0 || v < 0 then EncodedVector.mk 1 [EncodedVectorEntry.fromIndexAndValue i v] else EncodedVector.mk 0 [] @@ -172,13 +174,13 @@ def fromIndexAndValue (i : Nat) (v : Float) : EncodedVector := def fromArray (a : Array Float) : EncodedVector := Id.run <| do let mut ev := empty for (i, ai) in a.data.enum do - if ai > 0 ∨ ai < 0 then + if ai > 0 || ai < 0 then ev := ev.addEntry (EncodedVectorEntry.mk i ai) return ev end EncodedVector -/-- Represents the vlaue aᵢⱼ. -/ +/-- Represents the vlaue `aᵢⱼ`. -/ structure EncodedMatrixEntry where i : Nat j : Nat @@ -192,13 +194,12 @@ instance : ToString EncodedMatrixEntry where (toString eme.j) ++ " " ++ (toString eme.value) -def fromIndexAndEncodedVectorEntry (i : Nat) (eve : EncodedVectorEntry) - : EncodedMatrixEntry := +def fromIndexAndEncodedVectorEntry (i : Nat) (eve : EncodedVectorEntry) : EncodedMatrixEntry := EncodedMatrixEntry.mk i eve.i eve.value end EncodedMatrixEntry -/-- Represents the matrix A = (aᵢⱼ) where n entries are non-zero. -/ +/-- Represents the matrix `A = (aᵢⱼ)` where `n` entries are non-zero. -/ structure EncodedMatrix where n : Nat values : List EncodedMatrixEntry @@ -217,15 +218,15 @@ def isEmpty (em : EncodedMatrix) : Prop := em.n = 0 instance : Decidable (isEmpty em) := Nat.decEq _ _ def addEntry (em : EncodedMatrix) (eme : EncodedMatrixEntry) : EncodedMatrix := - if /- eme.i >= eme.j ∧ -/ (eme.value < 0 ∨ eme.value > 0) then + if eme.value < 0 || eme.value > 0 then EncodedMatrix.mk (em.n + 1) (em.values.concat eme) - else em + else + em def stack (em1 em2 : EncodedMatrix) : EncodedMatrix := EncodedMatrix.mk (em1.n + em2.n) (em1.values ++ em2.values) -def fromIndexAndEncodedVector (i : Nat) (ev : EncodedVector) - : EncodedMatrix := Id.run <| do +def fromIndexAndEncodedVector (i : Nat) (ev : EncodedVector) : EncodedMatrix := Id.run <| do let mut em := empty for eve in ev.values do let eme := EncodedMatrixEntry.fromIndexAndEncodedVectorEntry i eve @@ -236,13 +237,13 @@ def fromArray (A : Array (Array Float)) : EncodedMatrix := Id.run <| do let mut em := empty for (i, ai) in A.data.enum do for (j, aij) in ai.data.enum do - if i >= j ∧ (aij > 0 ∨ aij < 0) then + if i >= j && (aij > 0 || aij < 0) then em := em.addEntry (EncodedMatrixEntry.mk i j aij) return em end EncodedMatrix -/-- Represents the entry Aⱼₖ in the ith matrix in the list. -/ +/-- Represents the entry `Aⱼₖ` in the ith matrix in the list. -/ structure EncodedMatrixListEntry where i : Nat j : Nat @@ -258,14 +259,13 @@ instance : ToString EncodedMatrixListEntry where (toString emle.k) ++ " " ++ (toString emle.value) -def fromIndexAndEncodedMatrixEntry (i : Nat) (eme : EncodedMatrixEntry) - : EncodedMatrixListEntry := +def fromIndexAndEncodedMatrixEntry (i : Nat) (eme : EncodedMatrixEntry) : EncodedMatrixListEntry := EncodedMatrixListEntry.mk i eme.i eme.j eme.value end EncodedMatrixListEntry -/-- Represents L = [A₁, ...] where the total number of non-zero entries of all -the matrices Aᵢ is n. -/ +/-- Represents `L = [A₁, ...]` where the total number of non-zero entries of all the matrices `Aᵢ` +is `n`. -/ structure EncodedMatrixList where n : Nat values : List EncodedMatrixListEntry @@ -283,37 +283,34 @@ def isEmpty (eml : EncodedMatrixList) : Prop := eml.n = 0 instance : Decidable (isEmpty eml) := Nat.decEq _ _ -def addEntry (eml : EncodedMatrixList) (emle : EncodedMatrixListEntry) - : EncodedMatrixList := - if /- emle.j >= emle.k ∧ -/ (emle.value > 0 ∨ emle.value < 0) then +def addEntry (eml : EncodedMatrixList) (emle : EncodedMatrixListEntry) : EncodedMatrixList := + if emle.value > 0 || emle.value < 0 then EncodedMatrixList.mk (eml.n + 1) (eml.values.concat emle) - else eml + else + eml -def stack (eml1 eml2 : EncodedMatrixList) - : EncodedMatrixList := +def stack (eml1 eml2 : EncodedMatrixList) : EncodedMatrixList := EncodedMatrixList.mk (eml1.n + eml2.n) (eml1.values ++ eml2.values) -def fromIndexAndEncodedMatrix (i : Nat) (em : EncodedMatrix) - : EncodedMatrixList := Id.run <| do +def fromIndexAndEncodedMatrix (i : Nat) (em : EncodedMatrix) : EncodedMatrixList := Id.run <| do let mut eml := empty for eme in em.values do let emle := EncodedMatrixListEntry.fromIndexAndEncodedMatrixEntry i eme eml := eml.addEntry emle eml -def fromArray (A : Array (Array (Array Float))) - : EncodedMatrixList := Id.run <| do +def fromArray (A : Array (Array (Array Float))) : EncodedMatrixList := Id.run <| do let mut eml := empty for (i, ai) in A.data.enum do for (j, aij) in ai.data.enum do for (k, aijk) in aij.data.enum do - if j >= k ∧ (aijk > 0 ∨ aijk < 0) then + if j >= k && (aijk > 0 || aijk < 0) then eml := eml.addEntry (EncodedMatrixListEntry.mk i j k aijk) return eml end EncodedMatrixList -/-- Represents the entry Aₖₗ in the jth matrix of the ith list. -/ +/-- Represents the entry `Aₖₗ` in the `j`-th matrix of the `i`-th list. -/ structure EncodedMatrixListListEntry where i : Nat j : Nat @@ -331,14 +328,14 @@ instance : ToString EncodedMatrixListListEntry where (toString emlle.l) ++ " " ++ (toString emlle.value) -def fromIndexAndEncodedMatrixList (i : Nat) (emle : EncodedMatrixListEntry) - : EncodedMatrixListListEntry := +def fromIndexAndEncodedMatrixList (i : Nat) (emle : EncodedMatrixListEntry) : + EncodedMatrixListListEntry := EncodedMatrixListListEntry.mk i emle.i emle.j emle.k emle.value end EncodedMatrixListListEntry -/-- Represents C = [L₁, ...] where each Lᵢ is a list of matrices and n is the -total number of non-zero entries. -/ +/-- Represents `C = [L₁, ...]` where each `Lᵢ` is a list of matrices and `n` is the total number of +non-zero entries. -/ structure EncodedMatrixListList where n : Nat values : List EncodedMatrixListListEntry @@ -356,39 +353,38 @@ def isEmpty (emll : EncodedMatrixListList) : Prop := emll.n = 0 instance : Decidable (isEmpty emll) := Nat.decEq _ _ -def addEntry (emll : EncodedMatrixListList) (emlle : EncodedMatrixListListEntry) - : EncodedMatrixListList := - if /- emlle.k >= emlle.l ∧ -/ (emlle.value > 0 ∨ emlle.value < 0) then +def addEntry (emll : EncodedMatrixListList) (emlle : EncodedMatrixListListEntry) : + EncodedMatrixListList := + if emlle.value > 0 || emlle.value < 0 then EncodedMatrixListList.mk (emll.n + 1) (emll.values.concat emlle) else emll def stack (eml1 eml2 : EncodedMatrixListList) : EncodedMatrixListList := EncodedMatrixListList.mk (eml1.n + eml2.n) (eml1.values ++ eml2.values) -def fromIndexAndEncodedMatrixList (i : Nat) (eml : EncodedMatrixList) - : EncodedMatrixListList := Id.run <| do +def fromIndexAndEncodedMatrixList (i : Nat) (eml : EncodedMatrixList) : + EncodedMatrixListList := Id.run <| do let mut emll := empty for emle in eml.values do let emlle := EncodedMatrixListListEntry.fromIndexAndEncodedMatrixList i emle emll := emll.addEntry emlle emll --- TODO: why k >= l -def fromArray (A : Array (Array (Array (Array Float)))) - : EncodedMatrixListList := Id.run <| do +def fromArray (A : Array (Array (Array (Array Float)))) : + EncodedMatrixListList := Id.run <| do let mut emll := empty for (i, ai) in A.data.enum do for (j, aij) in ai.data.enum do for (k, aijk) in aij.data.enum do for (l, aijkl) in aijk.data.enum do - if k >= l ∧ (aijkl > 0 ∨ aijkl < 0) then + if k >= l && (aijkl > 0 || aijkl < 0) then emll := emll.addEntry (EncodedMatrixListListEntry.mk i j k l aijkl) return emll end EncodedMatrixListList -/-- This is the main definition. It represents a full problem in conic benchmark -format. The problem is defined as follows: +/-- This is the main definition. It represents a full problem in conic benchmark format. The problem +is defined as follows: min/max g^obj s.t. g_i ∈ K_i for i ∈ I --> Scalar constraints @@ -415,8 +411,7 @@ It contains the following headers (and their counterparts): ACOORD scalarConstraintsScalarVariablesCoord a BCOORD scalarConstraintsShiftCoord b HCOORD PSDConstraintsScalarVariablesCoord H - DCOORD PSDConstraintsShiftCoord D - -/ + DCOORD PSDConstraintsShiftCoord D -/ structure Problem where version : Nat objSense : ObjSense @@ -497,62 +492,46 @@ def empty : Problem := { /- Setters. -/ -def setVersion (p : Problem) (v : Nat) - : Problem := +def setVersion (p : Problem) (v : Nat) : Problem := { p with version := v } -def setObjSense (p : Problem) (os : ObjSense) - : Problem := +def setObjSense (p : Problem) (os : ObjSense) : Problem := { p with objSense := os } -def setScalarVariables (p : Problem) (cp : ConeProduct) - : Problem := +def setScalarVariables (p : Problem) (cp : ConeProduct) : Problem := { p with scalarVariables := cp } -def setPSDVariables (p : Problem) (dl : DimensionList) - : Problem := +def setPSDVariables (p : Problem) (dl : DimensionList) : Problem := { p with PSDVariables := dl } -def setScalarConstraints (p : Problem) (cp : ConeProduct) - : Problem := +def setScalarConstraints (p : Problem) (cp : ConeProduct) : Problem := { p with scalarConstraints := cp } -def setPSDConstraints (p : Problem) (dl : DimensionList) - : Problem := +def setPSDConstraints (p : Problem) (dl : DimensionList) : Problem := { p with PSDConstraints := dl } -def setObjectivePSDVariablesCoord (p : Problem) (eml : EncodedMatrixList) - : Problem := +def setObjectivePSDVariablesCoord (p : Problem) (eml : EncodedMatrixList) : Problem := { p with objectivePSDVariablesCoord := eml } -def setObjectiveScalarVariablesCoord (p : Problem) (ev : EncodedVector) - : Problem := +def setObjectiveScalarVariablesCoord (p : Problem) (ev : EncodedVector) : Problem := { p with objectiveScalarVariablesCoord := ev } def setObjectiveShiftCoord (p : Problem) (ev : EncodedValue) : Problem := { p with objectiveShiftCoord := ev } -def setScalarConstraintsPSDVariablesCoord - (p : Problem) (emll : EncodedMatrixListList) - : Problem := +def setScalarConstraintsPSDVariablesCoord (p : Problem) (emll : EncodedMatrixListList) : Problem := { p with scalarConstraintsPSDVariablesCoord := emll } -def setScalarConstraintsScalarVariablesCoord - (p : Problem) (em : EncodedMatrix) - : Problem := +def setScalarConstraintsScalarVariablesCoord (p : Problem) (em : EncodedMatrix) : Problem := { p with scalarConstraintsScalarVariablesCoord := em } -def setScalarConstraintsShiftCoord (p : Problem) (ev : EncodedVector) - : Problem := +def setScalarConstraintsShiftCoord (p : Problem) (ev : EncodedVector) : Problem := { p with scalarConstraintsShiftCoord := ev } -def setPSDConstraintsScalarVariablesCoord - (p : Problem) (emll : EncodedMatrixListList) - : Problem := +def setPSDConstraintsScalarVariablesCoord (p : Problem) (emll : EncodedMatrixListList) : Problem := { p with PSDConstraintsScalarVariablesCoord := emll } -def setPSDConstraintsShiftCoord (p : Problem) (eml : EncodedMatrixList) - : Problem := +def setPSDConstraintsShiftCoord (p : Problem) (eml : EncodedMatrixList) : Problem := { p with PSDConstraintsShiftCoord := eml } /- Simple adders. -/ @@ -571,23 +550,19 @@ def addPSDConstraint (p : Problem) (d : Nat) : Problem := /- Stack adders. -/ -def addScalarConstraintsPSDVariablesCoord - (p : Problem) (i : Nat) (eml : EncodedMatrixList) - : Problem := +def addScalarConstraintsPSDVariablesCoord (p : Problem) (i : Nat) (eml : EncodedMatrixList) : + Problem := let emll := EncodedMatrixListList.fromIndexAndEncodedMatrixList i eml { p with scalarConstraintsPSDVariablesCoord := p.scalarConstraintsPSDVariablesCoord.stack emll } -def addScalarConstraintsScalarVariablesCoord - (p : Problem) (i : Nat) (ev : EncodedVector) - : Problem := +def addScalarConstraintsScalarVariablesCoord (p : Problem) (i : Nat) (ev : EncodedVector) : + Problem := let em := EncodedMatrix.fromIndexAndEncodedVector i ev { p with scalarConstraintsScalarVariablesCoord := p.scalarConstraintsScalarVariablesCoord.stack em } -def addScalarConstraintsShiftCoord - (p : Problem) (i : Nat) (v : Float) - : Problem := +def addScalarConstraintsShiftCoord (p : Problem) (i : Nat) (v : Float) : Problem := let ev := EncodedVector.fromIndexAndValue i v { p with scalarConstraintsShiftCoord := p.scalarConstraintsShiftCoord.stack ev} @@ -599,19 +574,16 @@ def addPSDConstraintsScalarVariablesCoord { p with PSDConstraintsScalarVariablesCoord := p.PSDConstraintsScalarVariablesCoord.stack emll } -def addPSDConstraintsShiftCoord - (p : Problem) (i : Nat) (em : EncodedMatrix) - : Problem := +def addPSDConstraintsShiftCoord (p : Problem) (i : Nat) (em : EncodedMatrix) : Problem := let eml := EncodedMatrixList.fromIndexAndEncodedMatrix i em { p with PSDConstraintsShiftCoord := p.PSDConstraintsShiftCoord.stack eml } /- Combined adders for scalar and matrix affine constraints. -/ -/- Adds constraint g_k = ∑ ⟨F_ki, X_i⟩ + ∑ a_ki x_i + b_k ∈ c. -/ -def addScalarValuedAffineConstraint (p : Problem) (c : Cone) - (Fk : EncodedMatrixList) (ak : EncodedVector) (bk : Float) - : Problem := +/- Add constraint `gₖ = ∑ ⟨Fₖᵢ, Xᵢ⟩ + ∑ aₖᵢ xᵢ + bₖ ∈ 𝒦`. -/ +def addScalarValuedAffineConstraint (p : Problem) (c : Cone) (Fk : EncodedMatrixList) + (ak : EncodedVector) (bk : Float) : Problem := Id.run <| do let k := p.scalarConstraints.k let mut newP := p @@ -621,10 +593,9 @@ def addScalarValuedAffineConstraint (p : Problem) (c : Cone) newP := newP.addScalarConstraintsShiftCoord k bk newP -/- Adds constraint G_k = ∑ x_i • H_ki + D_k ∈ P_d. -/ -def addMatrixValuedAffineConstraint (p : Problem) (d : Nat) - (Hk : EncodedMatrixList) (Dk : EncodedMatrix) - : Problem := +/- Add constraint `G_k = ∑ x_i • H_ki + D_k ∈ 𝒮₊ⁿ`. -/ +def addMatrixValuedAffineConstraint (p : Problem) (d : Nat) (Hk : EncodedMatrixList) + (Dk : EncodedMatrix) : Problem := Id.run <| do let k := p.PSDConstraints.n let mut newP := p diff --git a/CvxLean/Command/Solve/Mosek/Sol.lean b/CvxLean/Command/Solve/Mosek/Sol.lean index 09fc1de8..b75fa012 100644 --- a/CvxLean/Command/Solve/Mosek/Sol.lean +++ b/CvxLean/Command/Solve/Mosek/Sol.lean @@ -4,13 +4,13 @@ import Lean.Data.Json.Parser /-! # Solution format definition and parser -See . --/ +See . +-/ namespace Sol -/-- Stores general information about the solution, crucially the optimality -status of the problem and the attained values. -/ +/-- Stores general information about the solution, crucially the optimality status of the problem +and the attained values. -/ structure Summary where name : String problemStatus : String @@ -19,11 +19,11 @@ structure Summary where primalObjective : Float dualObjective : Float -namespace Summary +namespace Summary -instance : ToString Summary where +instance : ToString Summary where toString s := - s!"NAME : {s.name} \n" ++ + s!"NAME : {s.name} \n" ++ s!"PROBLEM STATUS : {s.problemStatus} \n" ++ s!"SOLUTION STATUS : {s.solutionStatus} \n" ++ s!"OBJECTIVE NAME : {s.objectiveName} \n" ++ @@ -41,12 +41,12 @@ UL : At the upper limit (bound). EQ : Lower limit is identical to upper limit. ** : Infeasible i.e. the lower limit is greater than the upper limit. -/ inductive StatusKey -| UN | BS | SB | LL | UL | EQ | IN + | UN | BS | SB | LL | UL | EQ | IN namespace StatusKey -instance : ToString StatusKey where - toString +instance : ToString StatusKey where + toString | StatusKey.UN => "UN" | StatusKey.BS => "BS" | StatusKey.SB => "SB" @@ -70,9 +70,9 @@ structure Constraint where namespace Constraint -instance : ToString Constraint where +instance : ToString Constraint where toString c := - s!"{c.index} | " ++ + s!"{c.index} | " ++ s!"{c.name} | " ++ s!"{c.status} | " ++ s!"{c.activity} | " ++ @@ -89,8 +89,8 @@ structure Variable extends Constraint where namespace Variable -instance : ToString Variable where - toString v := +instance : ToString Variable where + toString v := (toString v.toConstraint) ++ (s!" | {v.conicDual}") end Variable @@ -106,9 +106,9 @@ structure SymmMatrixVariable where namespace SymmMatrixVariable -instance : ToString SymmMatrixVariable where - toString smv := - s!"{smv.index} | " ++ +instance : ToString SymmMatrixVariable where + toString smv := + s!"{smv.index} | " ++ s!"{smv.name} | " ++ s!"{smv.I} | " ++ s!"{smv.J} | " ++ @@ -128,41 +128,40 @@ namespace Result instance : ToString Result where toString res := - (toString res.summary) ++ "\n" ++ + (toString res.summary) ++ "\n" ++ "CONSTRAINTS \n" ++ "INDEX | NAME | AT | ACTIVITY | LOWER LIMIT | UPPER LIMIT | DUAL LOWER | DUAL UPPER \n" ++ - (res.constraints.foldl (fun acc s => acc ++ (toString s) ++ "\n") "") ++ "\n" ++ + (res.constraints.foldl (fun acc s => acc ++ (toString s) ++ "\n") "") ++ "\n" ++ "VARIABLES \n" ++ "INDEX | NAME | AT | ACTIVITY | LOWER LIMIT | UPPER LIMIT | DUAL LOWER | DUAL UPPER | CONIC DUAL \n" ++ - (res.vars.foldl (fun acc s => acc ++ (toString s) ++ "\n") "") ++ "\n" ++ - "SYMMETRIC MATRIX VARIABLES \n" ++ - "INDEX | NAME | I | J | PRIMAL | DUAL \n" ++ + (res.vars.foldl (fun acc s => acc ++ (toString s) ++ "\n") "") ++ "\n" ++ + "SYMMETRIC MATRIX VARIABLES \n" ++ + "INDEX | NAME | I | J | PRIMAL | DUAL \n" ++ (res.symmMatrixVars.foldl (fun acc s => acc ++ (toString s) ++ "\n") "") ++ "\n" end Result /-- Either a full result or a MOSEK error code. -/ inductive Response -| success (res : Result) : Response -| failure (code : Nat) : Response +| success (res : Result) : Response +| failure (code : Nat) : Response namespace Response instance : ToString Response where - toString + toString | success res => toString res | failure code => s!"MOSEK failed with code {code}." end Response -namespace Parser +namespace Parser open Lean Parsec /-- Variation of 'ws' not skipping line breaks. -/ -def ws' : Parsec Unit := fun it => - Lean.Parsec.ParseResult.success (skipWs' it) () -where +def ws' : Parsec Unit := fun it => Lean.Parsec.ParseResult.success (skipWs' it) () +where skipWs' (it : String.Iterator) : String.Iterator := if it.hasNext then let c := it.curr @@ -170,7 +169,7 @@ where else it /-- End of line (or file). -/ -private def endOfLine : Parsec Unit := +private def endOfLine : Parsec Unit := skipChar '\u000a' <|> eof /-- Parse only valid characters. -/ @@ -178,7 +177,7 @@ private def char : Parsec Char := asciiLetter <|> hexDigit <|> pchar '_' <|> fail "Invalid character." /-- Parse string containing only valid characters as in `char`. -/ -private def string : Parsec String := +private def string : Parsec String := many1Chars char /-- Parse a `Nat` using the `JsonNumber` parser. -/ @@ -194,51 +193,50 @@ private def float : Parsec Float := fun it₁ => | ParseResult.error it₂ err => ParseResult.error it₂ err /-- Use `float` or return none if the string is `"NONE"`. -/ -private def optionFloat : Parsec (Option Float) := +private def optionFloat : Parsec (Option Float) := (some <$> float) <|> (skipString "NONE" *> pure none) section Summary /-- Skip the beginning of a summary line. -/ -private def summaryIdentifier (id : String) : Parsec Unit := +private def summaryIdentifier (id : String) : Parsec Unit := skipString id *> ws' *> skipChar ':' *> ws' /-- Generic function to parse summary lines. -/ private def summaryLine (id : String) (p : Parsec α) : Parsec α := - summaryIdentifier id *> p <* ws' <* endOfLine + summaryIdentifier id *> p <* ws' <* endOfLine /-- Specialize `summaryLine` to strings. -/ -private def stringSummaryLine (id : String) : Parsec String := +private def stringSummaryLine (id : String) : Parsec String := summaryLine id string /-- Specialize `summaryLine` to floats. -/ -private def floatSummaryLine (id : String) : Parsec Float := +private def floatSummaryLine (id : String) : Parsec Float := summaryLine id float /-- Parse the whole `Sol.summary` section. -/ private def summary : Parsec Summary := - Summary.mk <$> - ((skipString "NAME" *> ws' *> skipChar ':' *> ws') *> pure "anonymous" <* endOfLine) <*> - -- stringSummaryLine "NAME" <*> - stringSummaryLine "PROBLEM STATUS" <*> - stringSummaryLine "SOLUTION STATUS" <*> - stringSummaryLine "OBJECTIVE NAME" <*> - floatSummaryLine "PRIMAL OBJECTIVE" <*> + Summary.mk <$> + ((skipString "NAME" *> ws' *> skipChar ':' *> ws') *> pure "anonymous" <* endOfLine) <*> + stringSummaryLine "PROBLEM STATUS" <*> + stringSummaryLine "SOLUTION STATUS" <*> + stringSummaryLine "OBJECTIVE NAME" <*> + floatSummaryLine "PRIMAL OBJECTIVE" <*> floatSummaryLine "DUAL OBJECTIVE" <* ws' <* endOfLine -end Summary +end Summary -section Constraints +section Constraints /-- Skip the title in the constraints section. -/ -private def constraintsTitle : Parsec Unit := +private def constraintsTitle : Parsec Unit := skipString "CONSTRAINTS" <* ws' <* endOfLine /-- Skip the headers in the constraints section. -/ -private def constraintsHeaders : Parsec Unit := - skipString "INDEX" <* ws' <* - skipString "NAME" <* ws' <* - skipString "AT" <* ws' <* +private def constraintsHeaders : Parsec Unit := + skipString "INDEX" <* ws' <* + skipString "NAME" <* ws' <* + skipString "AT" <* ws' <* skipString "ACTIVITY" <* ws' <* skipString "LOWER LIMIT" <* ws' <* skipString "UPPER LIMIT" <* ws' <* @@ -246,7 +244,7 @@ private def constraintsHeaders : Parsec Unit := skipString "DUAL UPPER" <* ws' <* endOfLine /-- Parse a `StatusKey`. -/ -private def statusKey : Parsec StatusKey := +private def statusKey : Parsec StatusKey := (skipString "UN" *> pure StatusKey.UN) <|> (skipString "BS" *> pure StatusKey.BS) <|> (skipString "SB" *> pure StatusKey.SB) <|> @@ -257,97 +255,97 @@ private def statusKey : Parsec StatusKey := (fail "Invalid status key.") /-- Generic function to parse constraint elements, handling whitespaces. -/ -private def constraintElem (p : Parsec α) : Parsec α := +private def constraintElem (p : Parsec α) : Parsec α := ws' *> p /-- Parse a `Constraint` line. -/ -private def constraint : Parsec Constraint := - Constraint.mk <$> - constraintElem nat <*> +private def constraint : Parsec Constraint := + Constraint.mk <$> + constraintElem nat <*> constraintElem string <*> - constraintElem statusKey <*> - constraintElem float <*> - constraintElem optionFloat <*> - constraintElem optionFloat <*> - constraintElem optionFloat <*> + constraintElem statusKey <*> + constraintElem float <*> + constraintElem optionFloat <*> + constraintElem optionFloat <*> + constraintElem optionFloat <*> constraintElem optionFloat <* ws' <* endOfLine /-- Parse the whole `Sol.Result.constraints` section. -/ -private def constraints : Parsec (List Constraint) := +private def constraints : Parsec (List Constraint) := constraintsTitle *> constraintsHeaders *> Array.data <$> many constraint -end Constraints +end Constraints section Variables /-- Skip the title in the variables section. -/ -private def varsTitle : Parsec Unit := +private def varsTitle : Parsec Unit := skipString "VARIABLES" <* endOfLine /-- Skip the headers in the variables section. Handle CONIC DUAL case. -/ -private def varsHeaders : Parsec Unit := - skipString "INDEX" <* ws' <* - skipString "NAME" <* ws' <* - skipString "AT" <* ws' <* +private def varsHeaders : Parsec Unit := + skipString "INDEX" <* ws' <* + skipString "NAME" <* ws' <* + skipString "AT" <* ws' <* skipString "ACTIVITY" <* ws' <* skipString "LOWER LIMIT" <* ws' <* skipString "UPPER LIMIT" <* ws' <* skipString "DUAL LOWER" <* ws' <* - skipString "DUAL UPPER" <* ws' <* + skipString "DUAL UPPER" <* ws' <* (skipString "CONIC DUAL" <|> pure ()) <* ws' <* endOfLine /-- Parse a `Variable` line. -/ private def var : Parsec Variable := let noBreakConstraint := Constraint.mk <$> - constraintElem nat <*> + constraintElem nat <*> constraintElem string <*> - constraintElem statusKey <*> - constraintElem float <*> - constraintElem optionFloat <*> - constraintElem optionFloat <*> + constraintElem statusKey <*> + constraintElem float <*> + constraintElem optionFloat <*> + constraintElem optionFloat <*> constraintElem optionFloat <*> constraintElem optionFloat - Variable.mk <$> - noBreakConstraint <*> + Variable.mk <$> + noBreakConstraint <*> (constraintElem (optionFloat <|> pure none)) <* ws' <* endOfLine /-- Parse the whole `Sol.Result.variables` section. -/ -private def vars : Parsec (List Variable) := +private def vars : Parsec (List Variable) := varsTitle *> varsHeaders *> Array.data <$> many var -end Variables +end Variables section SymmMatrixVariable /-- Skip the title in the symmetric matrix variables section. -/ -private def symmMatrixVarsTitle : Parsec Unit := +private def symmMatrixVarsTitle : Parsec Unit := skipString "SYMMETRIC MATRIX VARIABLES" <* endOfLine /-- Skip the headers in the symmetric matrix variables section. -/ -private def symmMatrixVarsHeaders : Parsec Unit := - skipString "INDEX" <* ws' <* - skipString "NAME" <* ws' <* - skipString "I" <* ws' <* +private def symmMatrixVarsHeaders : Parsec Unit := + skipString "INDEX" <* ws' <* + skipString "NAME" <* ws' <* + skipString "I" <* ws' <* skipString "J" <* ws' <* skipString "PRIMAL" <* ws' <* skipString "DUAL" <* ws' <* endOfLine /-- Parse a `SymmMatrixVariable` line. -/ -private def symmMatrixVar : Parsec SymmMatrixVariable := - SymmMatrixVariable.mk <$> - constraintElem nat <*> - constraintElem string <*> - constraintElem nat <*> - constraintElem nat <*> - constraintElem optionFloat <*> +private def symmMatrixVar : Parsec SymmMatrixVariable := + SymmMatrixVariable.mk <$> + constraintElem nat <*> + constraintElem string <*> + constraintElem nat <*> + constraintElem nat <*> + constraintElem optionFloat <*> constraintElem optionFloat <* ws' <* endOfLine /-- Parse the whole `Sol.Result.symmMatrixVariables` section. -/ -private def symmMatrixVars : Parsec (List SymmMatrixVariable) := +private def symmMatrixVars : Parsec (List SymmMatrixVariable) := (symmMatrixVarsTitle *> symmMatrixVarsHeaders *> Array.data <$> many symmMatrixVar) <|> pure [] @@ -355,7 +353,7 @@ private def symmMatrixVars : Parsec (List SymmMatrixVariable) := end SymmMatrixVariable /-- Parse the whole `Sol.Result` object. -/ -def result : Parsec Result := +def result : Parsec Result := Result.mk <$> summary <* ws <*> constraints <* ws <*> @@ -366,9 +364,9 @@ def result : Parsec Result := def parse (s : String) : Except String Result := match result s.mkIterator with | Parsec.ParseResult.success _ res => Except.ok res - | Parsec.ParseResult.error it err => + | Parsec.ParseResult.error it err => Except.error s!"Error at offset {it.i.byteIdx}. Error: {err}.}" -end Parser +end Parser end Sol From caa5bb40f7e05474380a7ad0c4f82c914a275f5e Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Thu, 1 Feb 2024 17:29:38 -0500 Subject: [PATCH 08/52] doc: `Command/Solve` --- CvxLean/Command/Solve.lean | 8 +- CvxLean/Command/Solve/Conic.lean | 207 +++++++----------- CvxLean/Command/Solve/Float/Coeffs.lean | 5 - CvxLean/Command/Solve/Float/ProblemData.lean | 8 +- CvxLean/Command/Solve/Float/SolutionData.lean | 53 +++++ CvxLean/Command/Solve/InferDimension.lean | 40 ---- .../Command/Solve/Mosek/CBFOfProblemData.lean | 132 +++++++++++ .../Solve/Mosek/SolToSolutionData.lean | 27 +++ CvxLean/Meta/Minimization.lean | 8 +- CvxLean/Meta/Util/Error.lean | 15 +- 10 files changed, 316 insertions(+), 187 deletions(-) create mode 100644 CvxLean/Command/Solve/Float/SolutionData.lean delete mode 100644 CvxLean/Command/Solve/InferDimension.lean create mode 100644 CvxLean/Command/Solve/Mosek/CBFOfProblemData.lean create mode 100644 CvxLean/Command/Solve/Mosek/SolToSolutionData.lean diff --git a/CvxLean/Command/Solve.lean b/CvxLean/Command/Solve.lean index 782b458a..f682e16a 100644 --- a/CvxLean/Command/Solve.lean +++ b/CvxLean/Command/Solve.lean @@ -1,6 +1,12 @@ import CvxLean.Tactic.DCP.AtomLibrary.All import CvxLean.Command.Solve.Conic +/-! +# The `solve` command + + +-/ + namespace CvxLean open Lean Lean.Elab Lean.Elab.Term Lean.Elab.Command Lean.Meta @@ -28,7 +34,7 @@ def getReducedProblemAndBwdMap (prob : Expr) : MetaM (Meta.MinimizationExpr × E syntax (name := solve) "solve " term : command -set_option maxHeartbeats 1000000 +-- set_option maxHeartbeats 1000000 @[command_elab «solve»] unsafe def evalSolve : CommandElab := fun stx => diff --git a/CvxLean/Command/Solve/Conic.lean b/CvxLean/Command/Solve/Conic.lean index 87a30266..3d23ea2d 100644 --- a/CvxLean/Command/Solve/Conic.lean +++ b/CvxLean/Command/Solve/Conic.lean @@ -1,161 +1,105 @@ import CvxLean.Lib.Minimization +import CvxLean.Lib.Math.Data.Real import CvxLean.Lib.Math.Data.Array import CvxLean.Lib.Math.Data.Matrix -import CvxLean.Lib.Math.Data.Real import CvxLean.Meta.Util.Expr import CvxLean.Meta.Util.ToExpr +import CvxLean.Meta.Util.Error +import CvxLean.Meta.Util.Debug import CvxLean.Meta.Minimization import CvxLean.Syntax.Minimization import CvxLean.Command.Solve.Float.Coeffs import CvxLean.Command.Solve.Float.FloatToReal -import CvxLean.Command.Solve.Mosek.Sol -import CvxLean.Command.Solve.InferDimension -import CvxLean.Command.Solve.Mosek.CBF +import CvxLean.Command.Solve.Mosek.SolToSolutionData +import CvxLean.Command.Solve.Mosek.CBFOfProblemData import CvxLean.Command.Solve.Mosek.Path -namespace CvxLean - -namespace Meta - -open Lean Lean.Meta Minimization - -def translateCone : ScalarConeType → CBF.ConeType - | ScalarConeType.Zero => CBF.ConeType.LEq - | ScalarConeType.PosOrth => CBF.ConeType.LPos - | ScalarConeType.Exp => CBF.ConeType.EXP - | ScalarConeType.Q => CBF.ConeType.Q - | ScalarConeType.QR => CBF.ConeType.QR - -def groupCones (sections : ScalarAffineSections) (l : List CBF.Cone) : - MetaM (List CBF.Cone) := do - let l := l.toArray - let mut res := [] - let mut currIdx := 0 - for idx in sections.data do - let group := l[currIdx:idx] - if h : group.size > 0 then - let c := group.get ⟨0, h⟩ - let coneType := c.type - for c' in group do - if !(c'.type = coneType) then - throwError "Only cones of the same type can be grouped." - let totalDim := group.foldl (fun acc c => acc + c.dim) 0 - currIdx := idx - res := res ++ [CBF.Cone.mk coneType totalDim] - else - throwError "Incorrect sections, could not group cones." - - return res +/-! +# Procedure to solve a minimization problem -/-- -/ -def getVars (minExpr : MinimizationExpr) : MetaM (List (Lean.Name × Expr)) := do - decomposeDomain (← instantiateMVars minExpr.domain) +This file defines how to solve an optimization problem using an external solver. The key function is +`solutionDataFromProblemData`. -/-- -/ -unsafe def getTotalDim (minExpr : MinimizationExpr) : MetaM Nat := do - let vars ← getVars minExpr +Currently, we only support MOSEK, but when more solvers are added, this code will still be the entry +point of the `solve` command and it will be here where the solver is called. +-/ - let mut totalDim := 0 - for (_, varTy) in vars do - let dims ← inferDimension varTy - for (n, m) in dims do - totalDim := totalDim + n * m +namespace CvxLean.Meta - return totalDim +open Lean Meta Minimization -/-- -/ -unsafe def conicSolverFromValues (minExpr : MinimizationExpr) (data : ProblemData) - (sections : ScalarAffineSections) : MetaM Sol.Response := do - let totalDim ← getTotalDim minExpr - - let mut cbf := CBF.Problem.empty - cbf := cbf.addScalarVariable (CBF.Cone.mk CBF.ConeType.F totalDim) - - if h : data.objective.isSome then - let sa := data.objective.get h - let AEnc := CBF.EncodedMatrixList.fromArray #[sa.A] - let aEnc := CBF.EncodedVector.fromArray sa.a - let bEnc := CBF.EncodedValue.mk (some sa.b) - cbf := cbf.setObjectivePSDVariablesCoord AEnc - cbf := cbf.setObjectiveScalarVariablesCoord aEnc - cbf := cbf.setObjectiveShiftCoord bEnc - - for (sa, sct) in data.scalarAffineConstraints do - let coneType := translateCone sct - let cone := CBF.Cone.mk coneType 1 - let AEnc := CBF.EncodedMatrixList.fromArray #[sa.A] - let aEnc := CBF.EncodedVector.fromArray sa.a - let bEnc := sa.b - cbf := cbf.addScalarValuedAffineConstraint cone AEnc aEnc bEnc - - for ma in data.matrixAffineConstraints do - let HEnc := CBF.EncodedMatrixList.fromArray ma.H - let DEnc := CBF.EncodedMatrix.fromArray ma.D - cbf := cbf.addMatrixValuedAffineConstraint ma.n HEnc DEnc - - -- Group cones appropriately, adjusting their dimensions. - let n := cbf.scalarConstraints.n - let cones := cbf.scalarConstraints.cones - let groupedCones ← groupCones sections cones - cbf := cbf.setScalarConstraints - (CBF.ConeProduct.mk n groupedCones.length groupedCones) +/-- From a minimization expression with given problem data, proceed as follows: +1. Convert `ProblemData` to CBF format. +2. Call MOSEK by writing to a `.cbf` file. +3. Read the solution from the resulting `.sol` file. +4. Return the solution as `SolutionData`. -/ +unsafe def solutionDataFromProblemData (minExpr : MinimizationExpr) (data : ProblemData) + (sections : ScalarAffineSections) : MetaM SolutionData := do + -- Create CBF problem. + let cbf ← CBF.ofProblemData minExpr data sections + -- Create files and run solver. The names are randomized to avoid race conditions when running the + -- tests. let r ← IO.rand 0 (2 ^ 32 - 1) let outputPath := s!"solver/problem{r}.sol" let inputPath := s!"solver/problem{r}.cbf" IO.FS.writeFile inputPath "" IO.FS.writeFile outputPath "" - IO.FS.withFile outputPath IO.FS.Mode.read fun outHandle => do - IO.FS.withFile inputPath IO.FS.Mode.write fun inHandle => do - -- Write input. - inHandle.putStr (ToString.toString cbf) - - -- Adjust path to MOSEK. - let p := if let some p' := ← IO.getEnv "PATH" then - if mosekBinPath != "" then p' ++ ":" ++ mosekBinPath else p' - else - mosekBinPath + let res : Except String SolutionData ← + IO.FS.withFile outputPath IO.FS.Mode.read fun outHandle => do + IO.FS.withFile inputPath IO.FS.Mode.write fun inHandle => do + -- Write input. + inHandle.putStr (ToString.toString cbf) + + -- Adjust path to MOSEK. + let p := if let some p' := ← IO.getEnv "PATH" then + if mosekBinPath != "" then p' ++ ":" ++ mosekBinPath else p' + else + mosekBinPath + + -- Run solver. + let out ← IO.Process.output { + cmd := "mosek", + args := #[inputPath], + env := #[("PATH", p)] } + + if out.exitCode != 0 then + return Except.error ("MOSEK exited with code " ++ ToString.toString out.exitCode) + + -- Always show MOSEK's output. + let res := out.stdout + dbg_trace res + + -- Read output. + let output ← outHandle.readToEnd + + -- Remove temporary files. + IO.FS.removeFile inputPath + IO.FS.removeFile outputPath + + match Sol.Parser.parse output with + | Except.ok res => + return Except.ok <| Sol.Result.toSolutionData res + | Except.error err => + return Except.error ("MOSEK output parsing failed. " ++ err) + + match res with + | Except.ok res => return res + | Except.error err => throwSolveError err - -- Run solver. - let out ← IO.Process.output { - cmd := "mosek", - args := #[inputPath], - env := #[("PATH", p)] } - - if out.exitCode != 0 then - dbg_trace ("MOSEK exited with code " ++ ToString.toString out.exitCode) - return Sol.Response.failure out.exitCode.toNat - - let res := out.stdout - IO.println res - - -- Read output. - let output ← outHandle.readToEnd - - -- Remove temporary files. - IO.FS.removeFile inputPath - IO.FS.removeFile outputPath - - match Sol.Parser.parse output with - | Except.ok res => return Sol.Response.success res - | Except.error err => - dbg_trace ("MOSEK output parsing failed. " ++ err) - return Sol.Response.failure 1 - -/-- TODO: Move to Generation? -/ -unsafe def exprFromSol (minExpr : MinimizationExpr) (sol : Sol.Result) : MetaM Expr := do - let vars ← getVars minExpr +/-- -/ +unsafe def exprFromSolutionData (minExpr : MinimizationExpr) (solData : SolutionData) : MetaM Expr := do + let vars ← decomposeDomainInstantiating minExpr -- Generate solution of the correct shape. let solPointExprArrayRaw : Array Expr := - Array.mk <| sol.vars.map (fun v => floatToReal v.activity) + Array.mk <| solData.varsSols.map (fun v => floatToReal v.value) -- Vectors and matrices as functions. let mut solPointExprArray : Array Expr := #[] - -- TODO(RFM): This won't work in general, need to take into account the - -- associativity of the variables if there are products. Infer dimension might - -- need to return a tree. + -- TODO: This won't work in general, need to take into account the associativity of the variables + -- if there are products, `inferDimension` might need to return a tree. let mut i : ℕ := 0 for (_, varTy) in vars do let dims ← inferDimension varTy @@ -169,7 +113,7 @@ unsafe def exprFromSol (minExpr : MinimizationExpr) (sol : Sol.Result) : MetaM E -- Vector. let exprs := (solPointExprArrayRaw.drop i).take n - -- TODO: Many copies of this in the function + -- TODO: Code repetition. let arrayExpr ← Lean.Expr.mkArray (mkConst ``Real) exprs let arrayList ← mkAppM ``Array.toList #[arrayExpr] let v ← withLocalDeclD `i' (← mkAppM ``Fin #[toExpr n]) fun i' => do @@ -206,9 +150,6 @@ unsafe def exprFromSol (minExpr : MinimizationExpr) (sol : Sol.Result) : MetaM E i := i + n * m let solPointExpr : Expr ← Lean.Expr.mkProd solPointExprArray - return solPointExpr -end Meta - -end CvxLean +end CvxLean.Meta diff --git a/CvxLean/Command/Solve/Float/Coeffs.lean b/CvxLean/Command/Solve/Float/Coeffs.lean index a89b7060..2ab81d45 100644 --- a/CvxLean/Command/Solve/Float/Coeffs.lean +++ b/CvxLean/Command/Solve/Float/Coeffs.lean @@ -243,11 +243,6 @@ unsafe def determineMatrixCoeffsAux (e : Expr) (p : Expr) (fty : Expr) : coeffs := coeffs.push floatCoeff return (coeffs, const) -/-- Indices to group constraints together and tag cones with the correct dimension when translating -the data into CBF format. This happens with the exponential cone, quadratic cone and rotated -quadratic cone, for instance. -/ -def ScalarAffineSections : Type := Array Nat - /-- Given a `MinimizationExpr`, representing a problem, assuming that it is in conic form, generate a `ProblemData`. The expression is first translated to floats, then we find the coefficients of all the affine terms involved in the cone membership constraints by plugging in the appropriate basis diff --git a/CvxLean/Command/Solve/Float/ProblemData.lean b/CvxLean/Command/Solve/Float/ProblemData.lean index 30c5929b..d7c675b9 100644 --- a/CvxLean/Command/Solve/Float/ProblemData.lean +++ b/CvxLean/Command/Solve/Float/ProblemData.lean @@ -123,12 +123,12 @@ def addExpConstraint (data : ProblemData) (a : Array Float) (b : Float) : Proble def addPosOrthConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := data.addScalarAffineConstraintOnlyVector a b ScalarConeType.PosOrth -/-- Add second-order cone constraint `∑ i, aᵢxᵢ + b ∈ Q` to problem data. Note that the second-order +/-- Add second-order cone constraint `∑ i, aᵢxᵢ + b ∈ 𝒬` to problem data. Note that the second-order cone is `n+1`-dimensional. The same remark on grouping constraints in `addExpConstraint` applies. -/ def addSOConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := data.addScalarAffineConstraintOnlyVector a b ScalarConeType.Q -/- Add second-order cone constraint `∑ i, aᵢxᵢ + b ∈ Qᵣ` to problem data. Note that the rotated +/- Add second-order cone constraint `∑ i, aᵢxᵢ + b ∈ 𝒬ᵣ` to problem data. Note that the rotated second-order cone is `n+2`-dimensional. The same remark on grouping constraints in `addExpConstraint` applies. -/ def addRotatedSOConstraint (data : ProblemData) (a : Array Float) (b : Float) : ProblemData := @@ -143,4 +143,8 @@ def addMatrixAffineConstraint (data : ProblemData) (H : Array (Array (Array Floa end ProblemData +/-- Indices to group constraints together and tag cones with the correct dimension when translating +problem data to solver formats. -/ +def ScalarAffineSections : Type := Array Nat + end CvxLean diff --git a/CvxLean/Command/Solve/Float/SolutionData.lean b/CvxLean/Command/Solve/Float/SolutionData.lean new file mode 100644 index 00000000..03969a4f --- /dev/null +++ b/CvxLean/Command/Solve/Float/SolutionData.lean @@ -0,0 +1,53 @@ +/-! +# Representation of numerical solution + +This file defines a generic format for numerical solutions. It is currently a simplified version of +the full solution data that we obtain from solvers. +-/ + +namespace CvxLean + +structure SimpleVarSol where + name : String + value : Float + +namespace SimpleVarSol + +instance : ToString SimpleVarSol where + toString v := + v.name ++ "* = " ++ toString v.value + +end SimpleVarSol + +structure SimpleMatrixVarSol where + name : String + I : Nat + J : Nat + value : Option Float + +namespace SimpleMatrixVarSol + +instance : ToString SimpleMatrixVarSol where + toString v := + match v.value with + | some x => v.name ++ "[" ++ toString v.I ++ ", " ++ toString v.J ++ "]* = " ++ toString x + | none => "none" + +end SimpleMatrixVarSol + +structure SolutionData where + status : String + varsSols : List SimpleVarSol + matrixVarsSols : List SimpleMatrixVarSol + +namespace SolutionData + +instance : ToString SolutionData where + toString s := + "Status: " ++ s.status ++ "\n" ++ + "Variables: (" ++ ", ".intercalate (s.varsSols.map toString) ++ ")\n" ++ + "Matrix variables: (" ++ ", ".intercalate (s.matrixVarsSols.map toString) ++ ")" + +end SolutionData + +end CvxLean diff --git a/CvxLean/Command/Solve/InferDimension.lean b/CvxLean/Command/Solve/InferDimension.lean deleted file mode 100644 index 21b30c8c..00000000 --- a/CvxLean/Command/Solve/InferDimension.lean +++ /dev/null @@ -1,40 +0,0 @@ -import CvxLean.Lib.Math.Data.Real -import CvxLean.Lib.Math.Data.Matrix -import CvxLean.Meta.Util.Expr -import CvxLean.Meta.Util.Meta - -namespace CvxLean - -open Lean Lean.Meta - -unsafe def inferDimension (ty : Expr) : MetaM (List (Nat × Nat)) := - match ty.consumeMData with - | Expr.const ``Real _ => - return [(1, 1)] - | Expr.forallE _ (Expr.app (Expr.const ``Fin _) nExpr) e _ => - match e with - | Expr.const ``Real _ => do - let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr - return [(n, 1)] - | Expr.forallE _ (Expr.app (Expr.const ``Fin _) mExpr) (Expr.const ``Real _) _ => do - let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr - let m : Nat ← evalExpr Nat (mkConst ``Nat) mExpr - return [(n, m)] - | _ => throwError "Unsupported type: {ty}" - | Expr.app (Expr.app (Expr.app M FinN) FinM) R => do - match (M, FinN, FinM, R) with - | (Expr.const ``Matrix _, - Expr.app (Expr.const ``Fin _) nExpr, - Expr.app (Expr.const ``Fin _) mExpr, - Expr.const ``Real _) => - let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr - let m : Nat ← evalExpr Nat (mkConst ``Nat) mExpr - return [(n, m)] - | _ => throwError "Unsupported type: {ty}" - | Expr.app (Expr.app (Expr.const ``Prod _) tyl) tyr => do - let l ← inferDimension tyl - let r ← inferDimension tyr - return (l ++ r) - | _ => throwError "Unsupported type: {ty}" - -end CvxLean diff --git a/CvxLean/Command/Solve/Mosek/CBFOfProblemData.lean b/CvxLean/Command/Solve/Mosek/CBFOfProblemData.lean new file mode 100644 index 00000000..d6d83f8f --- /dev/null +++ b/CvxLean/Command/Solve/Mosek/CBFOfProblemData.lean @@ -0,0 +1,132 @@ +import CvxLean.Lib.Math.Data.Real +import CvxLean.Meta.Minimization +import CvxLean.Meta.Util.Error +import CvxLean.Command.Solve.Float.ProblemData +import CvxLean.Command.Solve.Mosek.CBF + +/-! +Convert a `CBF.Problem` to a `ProblemData`. The main definition is `CBF.ofProblemData`. +-/ + +namespace CvxLean + +open Lean Meta + +/-- Given thet type of the domain return the dimensions of each component. For example, for +`ℝ × (Fin n → ℝ) × (Matrix (Fin m) (Fin k) ℝ)`, return `[(1, 1), (n, 1), (m, k)]` -/ +unsafe def inferDimension (ty : Expr) : MetaM (List (Nat × Nat)) := + match ty.consumeMData with + | .const ``Real _ => + return [(1, 1)] + | .forallE _ (.app (.const ``Fin _) nExpr) e _ => + match e with + | .const ``Real _ => do + let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr + return [(n, 1)] + | .forallE _ (.app (.const ``Fin _) mExpr) (.const ``Real _) _ => do + let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr + let m : Nat ← evalExpr Nat (mkConst ``Nat) mExpr + return [(n, m)] + | _ => throwSolveError "could not infer dimension of {ty}." + | .app (.app (.app M FinN) FinM) R => do + match (M, FinN, FinM, R) with + | (.const ``Matrix _, .app (.const ``Fin _) nExpr, .app (.const ``Fin _) mExpr, + .const ``Real _) => + let n : Nat ← evalExpr Nat (mkConst ``Nat) nExpr + let m : Nat ← evalExpr Nat (mkConst ``Nat) mExpr + return [(n, m)] + | _ => throwSolveError "could not infer dimension of {ty}." + | .app (.app (.const ``Prod _) tyl) tyr => do + let l ← inferDimension tyl + let r ← inferDimension tyr + return (l ++ r) + | _ => throwSolveError "could not infer dimension of {ty}." + +/-- Total dimension of a problem. For example, `ℝ × (Fin n → ℝ) × (Matrix (Fin m) (Fin k) ℝ)` has +total dimension `1 + n + mk`. We use this to know how many optimization variables to generate. -/ +unsafe def getTotalDim (minExpr : MinimizationExpr) : MetaM Nat := do + let vars ← decomposeDomainInstantiating minExpr + + let mut totalDim := 0 + for (_, varTy) in vars do + let dims ← inferDimension varTy + for (n, m) in dims do + totalDim := totalDim + n * m + + return totalDim + +namespace CBF + +/-- Translate generic cone types from `Solve/Float/ProblemData.lean` to MOSEK cones. -/ +def translateCone : ScalarConeType → CBF.ConeType + | ScalarConeType.Zero => CBF.ConeType.LEq + | ScalarConeType.PosOrth => CBF.ConeType.LPos + | ScalarConeType.Exp => CBF.ConeType.EXP + | ScalarConeType.Q => CBF.ConeType.Q + | ScalarConeType.QR => CBF.ConeType.QR + +/-- Constraints in non-matrix cones with intrinsic dimension (`𝒦ₑ`, `𝒬ⁿ⁺¹`, and `𝒬ᵣⁿ⁺²`) are +defined by a series of constraints. These need to be grouped appropriately. In +`Command/Solve/Float/Coeffs.lean` we keep track of these groups as "sections". These are used here +to correctly write the problem in CBF. -/ +def groupCones (sections : ScalarAffineSections) (l : List CBF.Cone) : MetaM (List CBF.Cone) := do + let l := l.toArray + let mut res := [] + let mut currIdx := 0 + for idx in sections.data do + let group := l[currIdx:idx] + if h : group.size > 0 then + let c := group.get ⟨0, h⟩ + let coneType := c.type + for c' in group do + if !(c'.type = coneType) then + throwSolveError "only cones of the same type can be grouped." + let totalDim := group.foldl (fun acc c => acc + c.dim) 0 + currIdx := idx + res := res ++ [CBF.Cone.mk coneType totalDim] + else + throwSolveError "incorrect sections, could not group cones." + + return res + +/-- Convert numerical problem data of a problem into CBF format to be solved by MOSEK. -/ +unsafe def ofProblemData (minExpr : MinimizationExpr) (data : ProblemData) + (sections : ScalarAffineSections) : MetaM CBF.Problem := do + let totalDim ← getTotalDim minExpr + + let mut cbf := CBF.Problem.empty + cbf := cbf.addScalarVariable (CBF.Cone.mk CBF.ConeType.F totalDim) + + if h : data.objective.isSome then + let sa := data.objective.get h + let AEnc := CBF.EncodedMatrixList.fromArray #[sa.A] + let aEnc := CBF.EncodedVector.fromArray sa.a + let bEnc := CBF.EncodedValue.mk (some sa.b) + cbf := cbf.setObjectivePSDVariablesCoord AEnc + cbf := cbf.setObjectiveScalarVariablesCoord aEnc + cbf := cbf.setObjectiveShiftCoord bEnc + + for (sa, sct) in data.scalarAffineConstraints do + let coneType := translateCone sct + let cone := CBF.Cone.mk coneType 1 + let AEnc := CBF.EncodedMatrixList.fromArray #[sa.A] + let aEnc := CBF.EncodedVector.fromArray sa.a + let bEnc := sa.b + cbf := cbf.addScalarValuedAffineConstraint cone AEnc aEnc bEnc + + for ma in data.matrixAffineConstraints do + let HEnc := CBF.EncodedMatrixList.fromArray ma.H + let DEnc := CBF.EncodedMatrix.fromArray ma.D + cbf := cbf.addMatrixValuedAffineConstraint ma.n HEnc DEnc + + -- Group cones appropriately, adjusting their dimensions. + let n := cbf.scalarConstraints.n + let cones := cbf.scalarConstraints.cones + let groupedCones ← groupCones sections cones + cbf := cbf.setScalarConstraints (CBF.ConeProduct.mk n groupedCones.length groupedCones) + + return cbf + +end CBF + +end CvxLean diff --git a/CvxLean/Command/Solve/Mosek/SolToSolutionData.lean b/CvxLean/Command/Solve/Mosek/SolToSolutionData.lean new file mode 100644 index 00000000..922e5f08 --- /dev/null +++ b/CvxLean/Command/Solve/Mosek/SolToSolutionData.lean @@ -0,0 +1,27 @@ +import CvxLean.Meta.Util.Error +import CvxLean.Command.Solve.Float.SolutionData +import CvxLean.Command.Solve.Mosek.Sol + +/-! +Convert a `Sol.Result` to a `SolutionData`. The main definition is `Sol.toSolutionData`. +-/ + +namespace CvxLean.Sol + +def Variable.toSimpleVarSol (v : Sol.Variable) : SimpleVarSol := + { name := v.name, + value := v.activity } + +def SymmMatrixVariable.toSimpleMatrixVarSol (v : Sol.SymmMatrixVariable) : + SimpleMatrixVarSol := + { name := v.name, + I := v.I, + J := v.J, + value := v.primal } + +def Result.toSolutionData (sol : Sol.Result) : SolutionData := + { status := sol.summary.problemStatus, + varsSols := sol.vars.map Variable.toSimpleVarSol, + matrixVarsSols := sol.symmMatrixVars.map SymmMatrixVariable.toSimpleMatrixVarSol } + +end CvxLean.Sol diff --git a/CvxLean/Meta/Minimization.lean b/CvxLean/Meta/Minimization.lean index 9102e7f1..d86130a9 100644 --- a/CvxLean/Meta/Minimization.lean +++ b/CvxLean/Meta/Minimization.lean @@ -92,14 +92,18 @@ where | _, _, [] => return none | _, _, _ :: _ => return none -/-- Determine a list of variables described by a `domain`. - Returns a list of variables, consisting of their name and type. -/ +/-- Determine a list of variables described by a `domain`. Returns a list of variables, consisting +of their name and type. -/ def decomposeDomain (domain : Expr) : m (List (Name × Expr)) := do match domain with | Expr.app (Expr.app (Expr.const `Prod _) ty1) ty2 => do return (← decomposeLabel ty1) :: (← decomposeDomain ty2) | _ => do return [← decomposeLabel domain] +/-- Same as `decomposeDomain` but also try to instantiate meta-variables. -/ +def decomposeDomainInstantiating (minExpr : MinimizationExpr) : MetaM (List (Name × Expr)) := do + decomposeDomain (← instantiateMVars minExpr.domain) + /-- Get a HashSet of variable names in a given domain. -/ def getVariableNameSet (domain : Expr) : m (HashSet Name) := do let mut res : HashSet Name := {} diff --git a/CvxLean/Meta/Util/Error.lean b/CvxLean/Meta/Util/Error.lean index 7bd5a23a..10f210d7 100644 --- a/CvxLean/Meta/Util/Error.lean +++ b/CvxLean/Meta/Util/Error.lean @@ -4,14 +4,21 @@ import Lean Custom error messages. -/ -syntax "throwCoeffsError " interpolatedStr(term) : term +syntax "throwCoeffsError " (interpolatedStr(term) <|> term) : term macro_rules - | `(throwCoeffsError $msg:interpolatedStr) => - `(throwError ("`coeffs` error: " ++ (m! $msg))) + | `(throwCoeffsError $msg:interpolatedStr) => `(throwError ("`coeffs` error: " ++ (m! $msg))) + | `(throwCoeffsError $msg:term) => `(throwError ("`coeffs` error: " ++ $msg)) -syntax "throwRealToFloatError " interpolatedStr(term) : term +syntax "throwRealToFloatError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwRealToFloatError $msg:interpolatedStr) => `(throwError ("`real-to-float` error: " ++ (m! $msg))) + | `(throwRealToFloatError $msg:term) => `(throwError ("`real-to-float` error: " ++ $msg)) + +syntax "throwSolveError " (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwSolveError $msg:interpolatedStr) => `(throwError ("`solve` error: " ++ (m! $msg))) + | `(throwSolveError $msg:term) => `(throwError ("`solve` error: " ++ $msg)) From 662ad7a230434fa6fa9b3a9e734817c94b7c9f12 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Thu, 1 Feb 2024 17:40:24 -0500 Subject: [PATCH 09/52] doc: `Command/Solve.lean` --- CvxLean/Command/Solve.lean | 97 +++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/CvxLean/Command/Solve.lean b/CvxLean/Command/Solve.lean index f682e16a..23fce36c 100644 --- a/CvxLean/Command/Solve.lean +++ b/CvxLean/Command/Solve.lean @@ -4,20 +4,24 @@ import CvxLean.Command.Solve.Conic /-! # The `solve` command - +Given a problem `p : Minimization D R` a user can call `solve p` to call an external solver and +obtain a result. If successful, three new definitions are added to the environment: +* `p.reduced : Minimization (D × E) R` is the problem in conic form. +* `p.status : String` is the status of the solution. +* `p.solution : D` is the solution to the problem. +* `p.value : R` is the value of the solution. -/ namespace CvxLean -open Lean Lean.Elab Lean.Elab.Term Lean.Elab.Command Lean.Meta - -open Minimization +open Lean Meta Elab Term Command Minimization /-- Get problem name. Used to add information about the solution to the environment. -/ -def getProblemName (term : Syntax) : MetaM Lean.Name := do - -- TODO: Full name with parameters. - let idStx := match term with - | Syntax.ident _ _ _ _ => term +def getProblemName (stx : Syntax) : MetaM Name := do + -- TODO: Full name with parameters? + let idStx := + match stx with + | Syntax.ident _ _ _ _ => stx | Syntax.node _ _ args => args.getD 0 Syntax.missing | _ => Syntax.missing if ¬ idStx.getId.isStr then @@ -25,17 +29,21 @@ def getProblemName (term : Syntax) : MetaM Lean.Name := do return idStx.getId -/-- -/ -def getReducedProblemAndBwdMap (prob : Expr) : MetaM (Meta.MinimizationExpr × Expr) := do - let ogProb ← Meta.MinimizationExpr.fromExpr prob +/-- Call DCP and get the problem in conic form as well as `ψ`, the backward map from the +equivalence. -/ +def getReducedProblemAndBwdMap (prob : Expr) : MetaM (MinimizationExpr × Expr) := do + let ogProb ← MinimizationExpr.fromExpr prob let (redProb, eqvProof) ← DCP.canonize ogProb let backwardMap ← mkAppM ``Minimization.Equivalence.psi #[eqvProof] return (redProb, backwardMap) syntax (name := solve) "solve " term : command --- set_option maxHeartbeats 1000000 - +/-- The `solve` command. It works as follows: +1. Reduce optimization problem to conic form. +2. Extract problem data using `determineCoeffsFromExpr`. +3. Obtain a solution using `solutionDataFromProblemData`, which calls an external solver. +4. Store the result in the enviroment. -/ @[command_elab «solve»] unsafe def evalSolve : CommandElab := fun stx => match stx with @@ -62,53 +70,44 @@ unsafe def evalSolve : CommandElab := fun stx => -- Call the solver on prob.reduced and get a point in E. let (coeffsData, sections) ← determineCoeffsFromExpr redProb - trace[Meta.debug] "coeffsData: {coeffsData}" - - let solPointResponse ← Meta.conicSolverFromValues redProb coeffsData sections - trace[Meta.debug] "solPointResponse: {solPointResponse}" + trace[CvxLean.debug] "Coeffs data:\n{coeffsData}" - match solPointResponse with - | Sol.Response.failure code => - trace[Meta.debug] "MOSEK failed with code {code}" - pure () - -- Mosek finished successfully. - | Sol.Response.success solPoint => - trace[Meta.debug] "solPoint.summary: {solPoint.summary}" + let solData ← solutionDataFromProblemData redProb coeffsData sections + trace[CvxLean.debug] "Solution data:\n{solData}" - -- Add status to the environment. - simpleAddAndCompileDefn (probName ++ `status) (mkStrLit solPoint.summary.problemStatus) + -- Add status to the environment. + simpleAddAndCompileDefn (probName ++ `status) (mkStrLit solData.status) - -- TODO: For now, we are only handling this case. - if solPoint.summary.problemStatus != "PRIMAL_AND_DUAL_FEASIBLE" then - pure () + -- TODO: For now, we are only handling this case. + if solData.status != "PRIMAL_AND_DUAL_FEASIBLE" then + pure () - -- Solution makes sense, handle the numerical solution. - let solPointExpr ← Meta.exprFromSol redProb solPoint - trace[Meta.debug] "solPointExpr: {solPointExpr}" + -- Solution makes sense, handle the numerical solution. + let solPointExpr ← exprFromSolutionData redProb solData + trace[CvxLean.debug] "Solution point (reduced problem): {solPointExpr}" - let backwardMapFloat ← realToFloat <| ← whnf backwardMap - let solPointExprFloat ← realToFloat solPointExpr + let backwardMapFloat ← realToFloat <| ← whnf backwardMap + let solPointExprFloat ← realToFloat solPointExpr - let probSolPointFloat ← whnf <| mkAppN backwardMapFloat #[solPointExprFloat] - trace[Meta.debug] "probSolPointFloat: {probSolPointFloat}" + let probSolPointFloat ← whnf <| mkAppN backwardMapFloat #[solPointExprFloat] + trace[CvxLean.debug] "Float solution point (original problem): {probSolPointFloat}" - -- Add the solution point to the environment. - simpleAddAndCompileDefn (probName ++ `solution) probSolPointFloat + -- Add the solution point to the environment. + simpleAddAndCompileDefn (probName ++ `solution) probSolPointFloat - -- Also add value of optimal point. - let probSolValue := mkApp redProb.objFun solPointExpr - let probSolValueFloat ← realToFloat probSolValue - trace[Meta.debug] "probSolValueFloat {probSolValueFloat}" - check probSolValueFloat + -- Also add value of optimal point. + let probSolValue := mkApp redProb.objFun solPointExpr + let probSolValueFloat ← realToFloat probSolValue + check probSolValueFloat - let mut probSolValueFloat := Expr.headBeta probSolValueFloat - trace[Meta.debug] "probSolValueFloat reduced: {probSolValueFloat}" + let mut probSolValueFloat := Expr.headBeta probSolValueFloat + trace[CvxLean.debug] "Float problem value (original problem): {probSolValueFloat}" - if probSolValueFloat.getAppFn.isConstOf `CvxLean.maximizeNeg then - probSolValueFloat := probSolValueFloat.getAppArgs[2]! - simpleAddAndCompileDefn (probName ++ `value) probSolValueFloat + if probSolValueFloat.getAppFn.isConstOf `CvxLean.maximizeNeg then + probSolValueFloat := probSolValueFloat.getAppArgs[2]! + simpleAddAndCompileDefn (probName ++ `value) probSolValueFloat - pure () + pure () | _ => throwUnsupportedSyntax end CvxLean From 7b4ee65d7a88d3a1bffba861db4e296a4d79fb3e Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Thu, 1 Feb 2024 18:13:45 -0500 Subject: [PATCH 10/52] feat: `@[strong_equiv]`, `@[equiv]`, `@[red]`, and `@[rel]` --- CvxLean/Command/Equivalence.lean | 9 +++-- CvxLean/Command/Reduction.lean | 8 ++++- CvxLean/Command/Relaxation.lean | 10 ++++-- CvxLean/Lib/Equivalence.lean | 61 ++++++++++++++++++++++++++++++++ CvxLean/Lib/Reduction.lean | 31 ++++++++++++++++ CvxLean/Lib/Relaxation.lean | 6 ++++ CvxLean/Meta/Attributes.lean | 20 +++++++++++ CvxLean/Meta/Equivalence.lean | 8 +++-- 8 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 CvxLean/Meta/Attributes.lean diff --git a/CvxLean/Command/Equivalence.lean b/CvxLean/Command/Equivalence.lean index 2a824905..f0bff599 100644 --- a/CvxLean/Command/Equivalence.lean +++ b/CvxLean/Command/Equivalence.lean @@ -1,4 +1,3 @@ -import Lean import CvxLean.Lib.Equivalence import CvxLean.Syntax.Minimization import CvxLean.Meta.Util.Expr @@ -7,11 +6,17 @@ import CvxLean.Meta.TacticBuilder import CvxLean.Command.Solve.Float.RealToFloatLibrary import CvxLean.Tactic.Basic.ChangeOfVariables +/-! +# The `equivalence` command + + +-/ + namespace CvxLean open Lean Elab Meta Tactic Term Command Minimization -/-- -/ +/-- Run a transformation tactic indicating that an equivalence is expected. -/ partial def runEquivalenceTactic (mvarId : MVarId) (stx : Syntax) : TermElabM Unit := do runTransformationTactic TransformationGoal.Equivalence mvarId stx diff --git a/CvxLean/Command/Reduction.lean b/CvxLean/Command/Reduction.lean index 28ff357b..9729fba2 100644 --- a/CvxLean/Command/Reduction.lean +++ b/CvxLean/Command/Reduction.lean @@ -5,11 +5,17 @@ import CvxLean.Meta.Util.Expr import CvxLean.Meta.Reduction import CvxLean.Meta.TacticBuilder +/-! +# The `reduction` command + + +-/ + namespace CvxLean open Lean Elab Meta Tactic Term Command Minimization -/-- -/ +/-- Run a transformation tactic indicating that a reduction is expected. -/ def runReductionTactic (mvarId : MVarId) (stx : Syntax) : TermElabM Unit := runTransformationTactic TransformationGoal.Reduction mvarId stx diff --git a/CvxLean/Command/Relaxation.lean b/CvxLean/Command/Relaxation.lean index 458f10d1..762434a8 100644 --- a/CvxLean/Command/Relaxation.lean +++ b/CvxLean/Command/Relaxation.lean @@ -5,15 +5,21 @@ import CvxLean.Meta.Util.Expr import CvxLean.Meta.Relaxation import CvxLean.Meta.TacticBuilder +/-! +# The `relaxation` command + + +-/ + namespace CvxLean open Lean Elab Meta Tactic Term Command Minimization -/-- -/ +/-- Run a transformation tactic indicating that a relaxation is expected. -/ def runRelaxationTactic (mvarId : MVarId) (stx : Syntax) : TermElabM Unit := runTransformationTactic TransformationGoal.Relaxation mvarId stx -/-- Run Relaxation tactic and return both the right-hand term (`q`) and the relaxation proof, of +/-- Run relaxation tactic and return both the right-hand term (`q`) and the relaxation proof, of type `Relaxation p q`. -/ def elabRelaxationProof (lhs : Expr) (rhsName : Name) (stx : Syntax) : TermElabM (Expr × Expr) := elabTransformationProof TransformationGoal.Relaxation lhs rhsName stx diff --git a/CvxLean/Lib/Equivalence.lean b/CvxLean/Lib/Equivalence.lean index 37630a8b..e5beecec 100644 --- a/CvxLean/Lib/Equivalence.lean +++ b/CvxLean/Lib/Equivalence.lean @@ -1,5 +1,6 @@ import CvxLean.Lib.Math.Data.Real import CvxLean.Lib.Minimization +import CvxLean.Meta.Attributes /-! # Equivalence of optimization problems @@ -32,18 +33,21 @@ variable {p q r} notation p " ≡ " q => Equivalence p q +@[equiv] def refl : p ≡ p := { phi := id, psi := id, phi_optimality := fun _ hx => hx, psi_optimality := fun _ hx => hx } +@[equiv] def symm (E : p ≡ q) : q ≡ p := { phi := E.psi, psi := E.phi, phi_optimality := E.psi_optimality, psi_optimality := E.phi_optimality } +@[equiv] def trans (E₁ : p ≡ q) (E₂ : q ≡ r) : p ≡ r := { phi := E₂.phi ∘ E₁.phi, psi := E₁.psi ∘ E₂.psi, @@ -85,6 +89,7 @@ variable {p q r} notation p " ≡' " q => StrongEquivalence p q +@[strong_equiv] def refl : p ≡' p := { phi := id, psi := id, @@ -93,6 +98,7 @@ def refl : p ≡' p := phi_optimality := fun _ _ => le_refl _, psi_optimality := fun _ _ => le_refl _ } +@[strong_equiv] def symm (E : p ≡' q) : q ≡' p := { phi := E.psi, psi := E.phi, @@ -101,6 +107,7 @@ def symm (E : p ≡' q) : q ≡' p := phi_optimality := E.psi_optimality, psi_optimality := E.phi_optimality } +@[strong_equiv] def trans (E₁ : p ≡' q) (E₂ : q ≡' r) : p ≡' r := { phi := E₂.phi ∘ E₁.phi, psi := E₁.psi ∘ E₂.psi, @@ -134,6 +141,7 @@ variable {p q : Minimization D R} /-- Equal problems are equivalent. Note that the domain needs to be the same. We intentionally do not define this as `h ▸ Equivalence.refl (p := p)` so that `phi` and `psi` can be easily extracted. -/ +@[equiv] def ofEq (h : p = q) : p ≡ q := { phi := id, psi := id, @@ -144,7 +152,9 @@ end Eq variable {p q} + /-- As expected, an `Equivalence` can be built from a `StrongEquivalence`. -/ +@[equiv] def ofStrongEquivalence (E : p ≡' q) : p ≡ q := { phi := E.phi, psi := E.psi, @@ -196,6 +206,7 @@ whole domain by a function with a right inverse. -/ section Maps /-- See [BV04,p.131] where `g` is `ψ₀`. -/ +@[equiv] def map_objFun {g : R → R} (h : ∀ {r s}, cs r → cs s → (g (f r) ≤ g (f s) ↔ f r ≤ f s)) : ⟨f, cs⟩ ≡ ⟨fun x => g (f x), cs⟩ := { phi := id, @@ -205,12 +216,14 @@ def map_objFun {g : R → R} (h : ∀ {r s}, cs r → cs s → (g (f r) ≤ g (f psi_optimality := fun _ ⟨h_feas_x, h_opt_x⟩ => ⟨h_feas_x, fun y h_feas_y => (h h_feas_x h_feas_y).mp (h_opt_x y h_feas_y)⟩ } +@[equiv] noncomputable def map_objFun_log {f : D → ℝ} (h : ∀ x, cs x → f x > 0) : ⟨f, cs⟩ ≡ ⟨fun x => (Real.log (f x)), cs⟩ := by apply map_objFun intros r s h_feas_r h_feas_s exact Real.log_le_log (h r h_feas_r) (h s h_feas_s) +@[equiv] noncomputable def map_objFun_sq {f : D → ℝ} (h : ∀ x, cs x → f x ≥ 0) : ⟨f, cs⟩ ≡ ⟨fun x => (f x) ^ (2 : ℝ), cs⟩ := by apply map_objFun (g := fun x => x ^ (2 : ℝ)) @@ -218,6 +231,7 @@ noncomputable def map_objFun_sq {f : D → ℝ} (h : ∀ x, cs x → f x ≥ 0) simp [sq_le_sq, abs_of_nonneg (h r h_feas_r), abs_of_nonneg (h s h_feas_s)] /-- This is simply a change of variables, see `ChangeOfVariables.toEquivalence` and [BV04,p.130]. -/ +@[equiv] def map_domain {f : D → R} {cs : D → Prop} {fwd : D → E} {bwd : E → D} (h : ∀ x, cs x → bwd (fwd x) = x) : ⟨f, cs⟩ ≡ ⟨fun x => f (bwd x), fun x => cs (bwd x)⟩ := @@ -262,6 +276,7 @@ macro_rules `(fun x => $c1 x ∧ $c2 x ∧ $c3 x ∧ $c4 x ∧ $c5 x ∧ $c6 x ∧ $c7 x ∧ $c8 x ∧ $c9 x ∧ $c10 x) | `([[$c, $cs,*]]) => `(fun x => $c x ∧ ([[$cs,*]] x)) +@[equiv] def rewrite_objFun (hrw : ∀ x, cs x → f x = g x) : ⟨f, cs⟩ ≡ ⟨g, cs⟩ := Equivalence.ofStrongEquivalence <| { phi := id, @@ -271,43 +286,53 @@ def rewrite_objFun (hrw : ∀ x, cs x → f x = g x) : ⟨f, cs⟩ ≡ ⟨g, cs phi_optimality := fun {x} hx => le_of_eq (hrw x hx).symm psi_optimality := fun {x} hx => le_of_eq (hrw x hx) } +@[equiv] def rewrite_objFun_1 (hrw : ∀ x, c1 x → f x = g x) : ⟨f, c1⟩ ≡ ⟨g, c1⟩ := rewrite_objFun hrw +@[equiv] def rewrite_objFun_2 (hrw : ∀ x, c1 x → c2 x → f x = g x) : ⟨f, [[c1, c2]]⟩ ≡ ⟨g, [[c1, c2]]⟩ := rewrite_objFun (fun x _ => by apply hrw x <;> tauto) +@[equiv] def rewrite_objFun_3 (hrw : ∀ x, c1 x → c2 x → c3 x → f x = g x) : ⟨f, [[c1, c2, c3]]⟩ ≡ ⟨g, [[c1, c2, c3]]⟩ := rewrite_objFun (fun x _ => by apply hrw x <;> tauto) +@[equiv] def rewrite_objFun_4 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → f x = g x) : ⟨f, [[c1, c2, c3, c4]]⟩ ≡ ⟨g, [[c1, c2, c3, c4]]⟩ := rewrite_objFun (fun x _ => by apply hrw x <;> tauto) +@[equiv] def rewrite_objFun_5 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → f x = g x) : ⟨f, [[c1, c2, c3, c4, c5]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5]]⟩ := rewrite_objFun (fun x _ => by apply hrw x <;> tauto) +@[equiv] def rewrite_objFun_6 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → f x = g x) : ⟨f, [[c1, c2, c3, c4, c5, c6]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5, c6]]⟩ := rewrite_objFun (fun x _ => by apply hrw x <;> tauto) +@[equiv] def rewrite_objFun_7 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → f x = g x) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5, c6, c7]]⟩ := rewrite_objFun (fun x _ => by apply hrw x <;> tauto) +@[equiv] def rewrite_objFun_8 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → f x = g x) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5, c6, c7, c8]]⟩ := rewrite_objFun (fun x _ => by apply hrw x <;> tauto) +@[equiv] def rewrite_objFun_9 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → f x = g x) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9]]⟩ ≡ ⟨g, [[c1, c2, c3, c4, c5, c6, c7, c8, c9]]⟩ := rewrite_objFun (fun x _ => by apply hrw x <;> tauto) +@[equiv] def rewrite_objFun_10 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → c10 x → f x = g x) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10]]⟩ ≡ @@ -336,87 +361,107 @@ macro "equivalence_of_rw_constr" hrw:ident : term => end EquivalenceOfConstrRw +@[equiv] def rewrite_constraints (hrw : ∀ x, cs x ↔ cs' x) : ⟨f, [[cs]]⟩ ≡ ⟨f, [[cs']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_1 (hrw : ∀ x, cs x → (c1 x ↔ c1' x)) : ⟨f, [[c1, cs]]⟩ ≡ ⟨f, [[c1', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_1_last (hrw : ∀ x, c1 x ↔ c1' x) : ⟨f, [[c1]]⟩ ≡ ⟨f, [[c1']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_2 (hrw : ∀ x, c1 x → cs x → (c2 x ↔ c2' x)) : ⟨f, [[c1, c2, cs]]⟩ ≡ ⟨f, [[c1, c2', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_2_last (hrw : ∀ x, c1 x → (c2 x ↔ c2' x)) : ⟨f, [[c1, c2]]⟩ ≡ ⟨f, [[c1, c2']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_3 (hrw : ∀ x, c1 x → c2 x → cs x → (c3 x ↔ c3' x)) : ⟨f, [[c1, c2, c3, cs]]⟩ ≡ ⟨f, [[c1, c2, c3', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_3_last (hrw : ∀ x, c1 x → c2 x → (c3 x ↔ c3' x)) : ⟨f, [[c1, c2, c3]]⟩ ≡ ⟨f, [[c1, c2, c3']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_4 (hrw : ∀ x, c1 x → c2 x → c3 x → cs x → (c4 x ↔ c4' x)) : ⟨f, [[c1, c2, c3, c4, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_4_last (hrw : ∀ x, c1 x → c2 x → c3 x → (c4 x ↔ c4' x)) : ⟨f, [[c1, c2, c3, c4]]⟩ ≡ ⟨f, [[c1, c2, c3, c4']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_5 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → cs x → (c5 x ↔ c5' x)) : ⟨f, [[c1, c2, c3, c4, c5, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_5_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → (c5 x ↔ c5' x)) : ⟨f, [[c1, c2, c3, c4, c5]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_6 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → cs x → (c6 x ↔ c6' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_6_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → (c6 x ↔ c6' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_7 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → cs x → (c7 x ↔ c7' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_7_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → (c7 x ↔ c7' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_8 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → cs x → (c8 x ↔ c8' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_8_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → (c8 x ↔ c8' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_9 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → cs x → (c9 x ↔ c9' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, cs]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_9_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → (c9 x ↔ c9' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9]]⟩ ≡ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9']]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_10 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → cs x → (c10 x ↔ c10' x)) : @@ -424,6 +469,7 @@ def rewrite_constraint_10 ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10', cs]]⟩ := equivalence_of_rw_constr hrw +@[equiv] def rewrite_constraint_10_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → (c10 x ↔ c10' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10]]⟩ ≡ @@ -441,6 +487,7 @@ variable {f : D → R} {cs : D → Prop} /-- We can always add a redundant constraint. This might be useful to help the reduction algorithm infer some constraints that cannot be easily infered by `arith`. -/ +@[equiv] def add_constraint {cs' : D → Prop} (h : ∀ x, cs x → cs' x) : ⟨f, cs⟩ ≡ ⟨f, [[cs', cs]]⟩ := Equivalence.ofStrongEquivalence <| { phi := id, @@ -451,18 +498,21 @@ def add_constraint {cs' : D → Prop} (h : ∀ x, cs x → cs' x) : ⟨f, cs⟩ psi_optimality := fun _ _ => le_refl _ } /-- See [BV04,p.131] where `g` is `ψᵢ`. -/ +@[equiv] def map_le_constraint_standard_form [Zero R] {cs' : D → Prop} {fi : D → R} {g : R → R} (hcs : ∀ x, cs x ↔ fi x ≤ 0 ∧ cs' x) (hg : ∀ x, g x ≤ 0 ↔ x ≤ 0) : ⟨f, cs⟩ ≡ ⟨f, fun x => g (fi x) ≤ 0 ∧ cs' x⟩ := by apply rewrite_constraints; intros x; rw [hcs x, hg (fi x)] /-- See [BV04,p.131] where `g` is `ψₘ₊ᵢ`. -/ +@[equiv] def map_eq_constraint_standard_form [Zero R] {cs' : D → Prop} {hi : D → R} {g : R → R} (hcs : ∀ x, cs x ↔ hi x = 0 ∧ cs' x) (hg : ∀ x, g x = 0 ↔ x = 0) : ⟨f, cs⟩ ≡ ⟨f, fun x => g (hi x) = 0 ∧ cs' x⟩ := by apply rewrite_constraints; intros x; rw [hcs x, hg (hi x)] /-- Adding a slack variable [BV04,p.131]. -/ +@[equiv] def add_slack_variable_standard_form {cs' : D → Prop} {fi : D → ℝ} (hcs : ∀ x, cs x ↔ fi x ≤ 0 ∧ cs' x) : ⟨f, cs⟩ ≡ ⟨fun (_, x) => f x, fun (si, x) => 0 ≤ (si : ℝ) ∧ fi x + si = 0 ∧ cs' x⟩ := @@ -477,6 +527,7 @@ def add_slack_variable_standard_form {cs' : D → Prop} {fi : D → ℝ} psi_optimality := fun (_, x) _ => by simp } /-- Eliminate equality constraints [BV04,p.132]. -/ +@[equiv] noncomputable def eliminate_eq_constraint_standard_form [Inhabited E] {cs' : D → Prop} {hi : D → ℝ} {g : E → D} (hcs : ∀ x, cs x ↔ hi x = 0 ∧ cs' x) (hg : ∀ x, hi x = 0 ↔ ∃ z, x = g z) : ⟨f, cs⟩ ≡ ⟨fun x => f (g x), fun x => cs' (g x)⟩ := @@ -501,6 +552,7 @@ noncomputable def eliminate_eq_constraint_standard_form [Inhabited E] {cs' : D psi_optimality := fun x _ => by simp } /-- Decompose constraint by introducing another equality constraint [BV04,p.132]. -/ +@[equiv] def decompose_constraint (g : D → E) (cs' : D → E → Prop) (hc : ∀ x, cs x ↔ cs' x (g x)) : ⟨f, cs⟩ ≡ ⟨fun (x, _) => f x, fun (x, y) => y = g x ∧ cs' x y⟩ := Equivalence.ofStrongEquivalence <| @@ -512,6 +564,7 @@ def decompose_constraint (g : D → E) (cs' : D → E → Prop) (hc : ∀ x, cs psi_optimality := fun {_} _ => le_refl _ } /-- Epigraph form [BV04,p.134]. -/ +@[equiv] def epigraph_form : ⟨f, cs⟩ ≡ ⟨fun (t, _) => t, fun (t, x) => f x ≤ t ∧ cs x⟩ := Equivalence.ofStrongEquivalence <| { phi := fun x => (f x, x), @@ -526,6 +579,7 @@ one-to-one to `(s, y) : S × E`. Assume that `x` is `p`-feasible iff `s = g y` a think of `s` as a new variable. If changing `s` does not change the objective function and the new constraints `c` respect monotonicity in `S`, we have that `p` is equivalent to the problem `⟨f, s ≤ g y ∧ cs' x⟩`. -/ +@[equiv] def eq_to_le_left {S} [Preorder S] (e : D ≃ S × E) (g : E → S) (cs' : D → Prop) (hcs : ∀ {x}, cs x ↔ ((e x).1 = g (e x).2 ∧ cs' x)) (hf : ∀ y r s, f (e.symm (r, y)) = f (e.symm (s, y))) @@ -545,6 +599,7 @@ def eq_to_le_left {S} [Preorder S] (e : D ≃ S × E) (g : E → S) (cs' : D → /-- Similar to `eq_to_le_left` with the monotonicity condition on `c` flipped. In this case we have that `P` is equivalent to `⟨f, g y ≤ s ∧ cs' x⟩`. -/ +@[equiv] def eq_to_le_right {S} [Preorder S] (e : Equiv D (S × E)) (g : E → S) (cs' : D → Prop) (hcs : ∀ {x}, cs x ↔ (g (e x).2 = (e x).1 ∧ cs' x)) (hf : ∀ x r s, f (e.symm ⟨r, x⟩) = f (e.symm ⟨s, x⟩)) @@ -563,6 +618,7 @@ def eq_to_le_right {S} [Preorder S] (e : Equiv D (S × E)) (g : E → S) (cs' : psi_optimality := fun {x} _ => by simp; rw [hf _ _ (e x).1]; simp [le_of_eq] } /-- Changing the domain to an equivalent type yields an equivalent problem. -/ +@[equiv] def domain_equiv (e : E ≃ D) : ⟨f, cs⟩ ≡ ⟨f ∘ e, cs ∘ e⟩ := Equivalence.ofStrongEquivalence <| { phi := e.symm, @@ -575,6 +631,7 @@ def domain_equiv (e : E ≃ D) : ⟨f, cs⟩ ≡ ⟨f ∘ e, cs ∘ e⟩ := /-- Introduce a new variable `s` that replaces occurrences of (non-linear) `g x` in the original problem. The resulting problem has an extra constraint `s ≤ g y`. The objective funciton and the rest of the constraints need to satisfy the appropriate monotonicity conditions [Gra05,4.2.1]. -/ +@[equiv] def linearization_mono {S} [Preorder S] (g : D → S) (c : S → D → Prop) (h : S → D → R) (hf : ∀ x, f x = h (g x) x) (hcs : ∀ x, cs x = c (g x) x) @@ -593,6 +650,7 @@ def linearization_mono {S} [Preorder S] (g : D → S) (c : S → D → Prop) (h /-- Similar to `linearization_mono` with the monotonicity conditions flipped. The resulting problem adds the exactra constraint `g y ≤ s` in this case [Gra05,4.2.1]. -/ +@[equiv] def linearization_antimono {S} [Preorder S] (g : D → S) (c : S → D → Prop) (h : S → D → R) (hf : ∀ x, f x = h (g x) x) (hcs : ∀ x, cs x = c (g x) x) @@ -611,6 +669,7 @@ def linearization_antimono {S} [Preorder S] (g : D → S) (c : S → D → Prop) /-- This can be seen as a generalization of `linearization_mono`, where `d` is the graph implementation of `g`. This is not used by the DCP procedure. -/ +@[equiv] def graph_expansion_greatest {S} [Preorder S] (g : D → S) (c d : S → D → Prop) (h : S → D → R) (hg : ∀ x v, c v x → IsGreatest {y | d y x} (g x)) (hf : ∀ x, f x = h (g x) x) @@ -631,6 +690,7 @@ def graph_expansion_greatest {S} [Preorder S] (g : D → S) (c d : S → D → P /-- Similar to `graph_expansion_greatest` but in the flipped monotonicity context, c.f. `linearization_antimono`. This is not used by the DCP procedure. -/ +@[equiv] def graph_expansion_least {S} [Preorder S] (g : D → S) (c d : S → D → Prop) (h : S → D → R) (hg : ∀ x v, c v x → IsLeast {y | d y x} (g x)) (hf : ∀ x, f x = h (g x) x) @@ -650,6 +710,7 @@ def graph_expansion_least {S} [Preorder S] (g : D → S) (c d : S → D → Prop simp only [hf]; exact h_mono_f y _ _ ((hg y s h_feas_sy.2).2 h_feas_sy.1) } /-- Version of `graph_expansion_least` that works with vectors. -/ +@[equiv] def graph_expansion_least_forall {S I : Type} [Preorder S] [Inhabited I] (g : D → I → S) (c d : S → D → Prop) (hg : ∀ x v i, c v x → IsLeast {y | d y x} (g x i)) (hcs : ∀ x, cs x = ∀ i, c (g x i) x) (h_mono_cs : ∀ x r s, r ≤ s → c s x → c r x) : diff --git a/CvxLean/Lib/Reduction.lean b/CvxLean/Lib/Reduction.lean index 0c39ef16..1d8109a9 100644 --- a/CvxLean/Lib/Reduction.lean +++ b/CvxLean/Lib/Reduction.lean @@ -31,10 +31,12 @@ variable {p q r} notation p " ≼ " q => Reduction p q +@[red] def refl : p ≼ p := { psi := id, psi_optimality := fun _ hy => hy } +@[red] def trans (R₁ : p ≼ q) (R₂ : q ≼ r) : p ≼ r := { psi := R₁.psi ∘ R₂.psi, psi_optimality := fun x h => R₁.psi_optimality (R₂.psi x) (R₂.psi_optimality x h) } @@ -47,6 +49,7 @@ def toBwd (R : p ≼ q) : Solution q → Solution p := { point := R.psi sol.point, isOptimal := R.psi_optimality sol.point sol.isOptimal } +@[red] def ofEquivalence (E : p ≡ q) : p ≼ q := { psi := E.psi, psi_optimality := E.psi_optimality } @@ -62,6 +65,7 @@ section Maps /-- Weaker version of `Equivalence.map_objFun`. For a reduction we only need the map to be order-reflecting on the image of the objective function. Note that for an equivalence we also need it to be order-preserving (order-reflecting + order-preserving = order embedding). -/ +@[red] def map_objFun_of_order_reflecting {D R} [Preorder R] {f : D → R} {g : R → R} {cs : D → Prop} (h : ∀ {r s}, cs r → cs s → g (f r) ≤ g (f s) → f r ≤ f s) : ⟨f, cs⟩ ≼ ⟨fun x => g (f x), cs⟩ := @@ -73,22 +77,26 @@ def map_objFun_of_order_reflecting {D R} [Preorder R] {f : D → R} {g : R → R h h_feas_x h_feas_y h_gfx_le_gfy⟩ } /-- See `Equivalence.map_objFun`. -/ +@[red] def map_objFun {D R} [Preorder R] {f : D → R} {g : R → R} {cs : D → Prop} (h : ∀ {r s}, cs r → cs s → (g (f r) ≤ g (f s) ↔ f r ≤ f s)) : ⟨f, cs⟩ ≼ ⟨fun x => g (f x), cs⟩ := ofEquivalence <| Equivalence.map_objFun h /-- See `Equivalence.map_objFun_log`. -/ +@[red] noncomputable def map_objFun_log {f : D → ℝ} (h : ∀ x, cs x → f x > 0) : ⟨f, cs⟩ ≼ ⟨fun x => (Real.log (f x)), cs⟩ := ofEquivalence <| Equivalence.map_objFun_log h /-- See `Equivalence.map_objFun_sq`. -/ +@[red] noncomputable def map_objFun_sq {f : D → ℝ} (h : ∀ x, cs x → f x ≥ 0) : ⟨f, cs⟩ ≼ ⟨fun x => (f x) ^ (2 : ℝ), cs⟩ := ofEquivalence <| Equivalence.map_objFun_sq h /-- See `Equivalence.map_domain`. -/ +@[red] def map_domain {f : D → R} {cs : D → Prop} {fwd : D → E} {bwd : E → D} (h : ∀ x, cs x → bwd (fwd x) = x) : ⟨f, cs⟩ ≼ ⟨fun x => f (bwd x), (fun x => cs (bwd x))⟩ := @@ -103,90 +111,111 @@ variable {c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 : D → Prop} variable {c1' c2' c3' c4' c5' c6' c7' c8' c9' c10' : D → Prop} variable {cs cs' : D → Prop} +@[red] def rewrite_objFun (hrw : ∀ x, cs x → f x = g x) : ⟨f, cs⟩ ≼ ⟨g, cs⟩ := ofEquivalence <| Equivalence.rewrite_objFun hrw +@[red] def rewrite_constraints (hrw : ∀ x, cs x ↔ cs' x) : ⟨f, [[cs]]⟩ ≼ ⟨f, [[cs']]⟩ := ofEquivalence <| Equivalence.rewrite_constraints hrw +@[red] def rewrite_constraint_1 (hrw : ∀ x, cs x → (c1 x ↔ c1' x)) : ⟨f, [[c1, cs]]⟩ ≼ ⟨f, [[c1', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_1 hrw +@[red] def rewrite_constraint_1_last (hrw : ∀ x, c1 x ↔ c1' x) : ⟨f, [[c1]]⟩ ≼ ⟨f, [[c1']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_1_last hrw +@[red] def rewrite_constraint_2 (hrw : ∀ x, c1 x → cs x → (c2 x ↔ c2' x)) : ⟨f, [[c1, c2, cs]]⟩ ≼ ⟨f, [[c1, c2', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_2 hrw +@[red] def rewrite_constraint_2_last (hrw : ∀ x, c1 x → (c2 x ↔ c2' x)) : ⟨f, [[c1, c2]]⟩ ≼ ⟨f, [[c1, c2']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_2_last hrw +@[red] def rewrite_constraint_3 (hrw : ∀ x, c1 x → c2 x → cs x → (c3 x ↔ c3' x)) : ⟨f, [[c1, c2, c3, cs]]⟩ ≼ ⟨f, [[c1, c2, c3', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_3 hrw +@[red] def rewrite_constraint_3_last (hrw : ∀ x, c1 x → c2 x → (c3 x ↔ c3' x)) : ⟨f, [[c1, c2, c3]]⟩ ≼ ⟨f, [[c1, c2, c3']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_3_last hrw +@[red] def rewrite_constraint_4 (hrw : ∀ x, c1 x → c2 x → c3 x → cs x → (c4 x ↔ c4' x)) : ⟨f, [[c1, c2, c3, c4, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_4 hrw +@[red] def rewrite_constraint_4_last (hrw : ∀ x, c1 x → c2 x → c3 x → (c4 x ↔ c4' x)) : ⟨f, [[c1, c2, c3, c4]]⟩ ≼ ⟨f, [[c1, c2, c3, c4']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_4_last hrw +@[red] def rewrite_constraint_5 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → cs x → (c5 x ↔ c5' x)) : ⟨f, [[c1, c2, c3, c4, c5, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_5 hrw +@[red] def rewrite_constraint_5_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → (c5 x ↔ c5' x)) : ⟨f, [[c1, c2, c3, c4, c5]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_5_last hrw +@[red] def rewrite_constraint_6 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → cs x → (c6 x ↔ c6' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_6 hrw +@[red] def rewrite_constraint_6_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → (c6 x ↔ c6' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_6_last hrw +@[red] def rewrite_constraint_7 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → cs x → (c7 x ↔ c7' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_7 hrw +@[red] def rewrite_constraint_7_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → (c7 x ↔ c7' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_7_last hrw +@[red] def rewrite_constraint_8 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → cs x → (c8 x ↔ c8' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_8 hrw +@[red] def rewrite_constraint_8_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → (c8 x ↔ c8' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_8_last hrw +@[red] def rewrite_constraint_9 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → cs x → (c9 x ↔ c9' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, cs]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_9 hrw +@[red] def rewrite_constraint_9_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → (c9 x ↔ c9' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9]]⟩ ≼ ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9']]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_9_last hrw +@[red] def rewrite_constraint_10 (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → cs x → (c10 x ↔ c10' x)) : @@ -194,6 +223,7 @@ def rewrite_constraint_10 ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10', cs]]⟩ := ofEquivalence <| Equivalence.rewrite_constraint_10 hrw +@[red] def rewrite_constraint_10_last (hrw : ∀ x, c1 x → c2 x → c3 x → c4 x → c5 x → c6 x → c7 x → c8 x → c9 x → (c10 x ↔ c10' x)) : ⟨f, [[c1, c2, c3, c4, c5, c6, c7, c8, c9, c10]]⟩ ≼ @@ -212,6 +242,7 @@ end Reduction namespace Equivalence +@[equiv] def ofReductions (R₁ : p ≼ q) (R₂ : q ≼ p) : p ≡ q := { phi := R₂.psi, psi := R₁.psi, diff --git a/CvxLean/Lib/Relaxation.lean b/CvxLean/Lib/Relaxation.lean index dfbf1a67..26f564c6 100644 --- a/CvxLean/Lib/Relaxation.lean +++ b/CvxLean/Lib/Relaxation.lean @@ -27,11 +27,13 @@ variable {p q r} notation p " ≽' " q => Relaxation p q +@[rel] def refl : p ≽' p := { phi := id, phi_feasibility := fun _ h => h, phi_optimality := fun _ _ => le_refl _ } +@[rel] def trans (Rx₁ : p ≽' q) (Rx₂ : q ≽' r) : p ≽' r := { phi := Rx₂.phi ∘ Rx₁.phi, phi_feasibility := fun x h => Rx₂.phi_feasibility (Rx₁.phi x) (Rx₁.phi_feasibility x h), @@ -78,6 +80,7 @@ namespace StrongEquivalence variable {p q} +@[strong_equiv] def ofRelaxations (Rx₁ : p ≽' q) (Rx₂ : q ≽' p) : p ≡' q := { phi := Rx₁.phi, psi := Rx₂.phi, @@ -92,6 +95,7 @@ namespace Equivalence variable {p q} +@[equiv] def ofRelaxations (Rx₁ : p ≽' q) (Rx₂ : q ≽' p) : p ≡ q := Equivalence.ofStrongEquivalence (StrongEquivalence.ofRelaxations Rx₁ Rx₂) @@ -101,11 +105,13 @@ namespace Relaxation variable {f : D → R} {cs : D → Prop} +@[rel] def remove_constraint {c cs' : D → Prop} (hcs : ∀ x, cs x ↔ c x ∧ cs' x) : ⟨f, cs⟩ ≽' ⟨f, cs'⟩ := { phi := id, phi_feasibility := fun x h_feas_x => ((hcs x).mp h_feas_x).2, phi_optimality := fun _ _ => le_refl _ } +@[rel] def weaken_constraints (cs' : D → Prop) (hcs : ∀ x, cs x → cs' x) : ⟨f, cs⟩ ≽' ⟨f, cs'⟩ := { phi := id, phi_feasibility := fun x h_feas_x => hcs x h_feas_x, diff --git a/CvxLean/Meta/Attributes.lean b/CvxLean/Meta/Attributes.lean new file mode 100644 index 00000000..32a83768 --- /dev/null +++ b/CvxLean/Meta/Attributes.lean @@ -0,0 +1,20 @@ +import Lean + +/-! +Attributes for lemmas that make strong equivalences, equivalences, reductions, and relaxations. By +tagging them, we can easily retrieve all the lemmas for a certain relation. +-/ + +open Lean Meta + +initialize strongEquivThmExtension : SimpExtension ← + registerSimpAttr `strong_equiv "Strong equivalence theorem." + +initialize equivThmExtension : SimpExtension ← + registerSimpAttr `equiv "Equivalence theorem." + +initialize redThmExtension : SimpExtension ← + registerSimpAttr `red "Reduction theorem." + +initialize relThmExtension : SimpExtension ← + registerSimpAttr `rel "Relaxation theorem." diff --git a/CvxLean/Meta/Equivalence.lean b/CvxLean/Meta/Equivalence.lean index b3fdb9ee..01df03c0 100644 --- a/CvxLean/Meta/Equivalence.lean +++ b/CvxLean/Meta/Equivalence.lean @@ -3,10 +3,10 @@ import CvxLean.Lib.Equivalence namespace CvxLean -namespace Meta - open Lean Meta +namespace Meta + /-- `Equivalence` type components as expressions. -/ structure EquivalenceExpr where domainP : Expr @@ -41,6 +41,8 @@ def fromGoal (goal : MVarId) : MetaM EquivalenceExpr := do end EquivalenceExpr +section BasicTactics + macro "equivalence_rfl" : tactic => `(tactic| apply Minimization.Equivalence.refl) macro "equivalence_symm" : tactic => `(tactic| apply Minimization.Equivalence.symm) @@ -53,6 +55,8 @@ elab "equivalence_step" _arr:darrow tac:tacticSeqIndentGt : tactic => do evalTactic <| ← `(tactic| equivalence_trans) evalTacticSeq1Indented tac.raw +end BasicTactics + end Meta end CvxLean From 91d95de4ab651e66099a591d239cac1d574716a7 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Thu, 1 Feb 2024 18:32:45 -0500 Subject: [PATCH 11/52] feat: use `equiv` theorems to reduce computable backward map --- CvxLean/Command/Equivalence.lean | 95 ++++----------------- CvxLean/Meta/Util/Debug.lean | 2 +- CvxLean/Meta/Util/Error.lean | 8 ++ CvxLean/Meta/Util/Simp.lean | 19 +++++ CvxLean/Tactic/Basic/ChangeOfVariables.lean | 1 + 5 files changed, 45 insertions(+), 80 deletions(-) create mode 100644 CvxLean/Meta/Util/Simp.lean diff --git a/CvxLean/Command/Equivalence.lean b/CvxLean/Command/Equivalence.lean index f0bff599..4ee96ab8 100644 --- a/CvxLean/Command/Equivalence.lean +++ b/CvxLean/Command/Equivalence.lean @@ -1,6 +1,9 @@ import CvxLean.Lib.Equivalence import CvxLean.Syntax.Minimization import CvxLean.Meta.Util.Expr +import CvxLean.Meta.Util.Simp +import CvxLean.Meta.Util.Error +import CvxLean.Meta.Util.Debug import CvxLean.Meta.Equivalence import CvxLean.Meta.TacticBuilder import CvxLean.Command.Solve.Float.RealToFloatLibrary @@ -59,29 +62,13 @@ def evalEquivalenceAux (probIdStx eqvIdStx : TSyntax `ident) (xs : Array (Syntax let rhs ← instantiateMVars rhs let rhs ← mkLambdaFVars (xs.map Prod.snd) rhs let rhs ← instantiateMVars rhs - Lean.addDecl <| - Declaration.defnDecl - (mkDefinitionValEx probId - [] - (← inferType rhs) - rhs - (Lean.ReducibilityHints.regular 0) - (DefinitionSafety.safe) - []) + simpleAddDefn probId rhs -- Add equivalence proof to the environment. let eqv ← instantiateMVars eqv let eqv ← mkLambdaFVars (xs.map Prod.snd) eqv let eqv ← instantiateMVars eqv - Lean.addDecl <| - Declaration.defnDecl - (mkDefinitionValEx eqvId - [] - (← inferType eqv) - eqv - (Lean.ReducibilityHints.regular 0) - (DefinitionSafety.safe) - []) + simpleAddDefn eqvId eqv if bwdMap then lambdaTelescope eqv fun eqvArgs eqvBody => do @@ -89,67 +76,17 @@ def evalEquivalenceAux (probIdStx eqvIdStx : TSyntax `ident) (xs : Array (Syntax let psi := (← whnf eqvBody).getArg! 7 let mut simpCtx ← Simp.Context.mkDefault - let simpCfg : Simp.Config := - { zeta := true - beta := true - eta := true - iota := true - proj := true - decide := true - arith := true - dsimp := true - unfoldPartialApp := true - etaStruct := .all } - simpCtx := { simpCtx with config := simpCfg } - - let mut simpThms := {} + simpCtx := { simpCtx with config := aggressiveSimpConfig } + + let (.some ext) ← getSimpExtension? `equiv | + throwEquivalenceError "could not find `equiv` simp extension." + + let mut simpThms ← ext.getTheorems simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.mk - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.refl - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.trans - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.ofStrongEquivalence - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.ofEq simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.psi - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.map_objFun - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.map_objFun_log - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.map_objFun_sq - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.map_domain - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.add_constraint - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_1 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_2 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_3 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_4 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_5 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_6 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_7 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_8 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_9 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_objFun_10 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraints - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_1 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_2 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_3 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_4 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_5 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_6 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_7 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_8 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_9 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_10 - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_1_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_2_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_3_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_4_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_5_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_6_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_7_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_8_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_9_last - simpThms ← simpThms.addDeclToUnfold ``Minimization.Equivalence.rewrite_constraint_10_last - simpThms ← simpThms.addDeclToUnfold ``CvxLean.ChangeOfVariables.toEquivalence - simpThms ← simpThms.addDeclToUnfold ``Eq.mpr simpThms ← simpThms.addDeclToUnfold ``Eq.mp + simpThms ← simpThms.addDeclToUnfold ``Eq.mpr + simpCtx := { simpCtx with simpTheorems := #[simpThms] } let (res, _) ← simp psi simpCtx @@ -159,14 +96,14 @@ def evalEquivalenceAux (probIdStx eqvIdStx : TSyntax `ident) (xs : Array (Syntax let eqvNonPropArgs ← eqvArgs.filterM fun arg => do return !(← inferType (← inferType arg)).isProp let psi ← mkLambdaFVars eqvNonPropArgs res.expr - trace[Meta.debug] "psi: {psi}" + trace[CvxLean.debug] "psi: {psi}" try let psiF ← realToFloat psi Lean.simpleAddAndCompileDefn (eqvId ++ `backward_map) psiF catch e => - trace[Meta.debug] - "`equivalence` error: failed to create `{eqvId}.backward_map`.\n{e.toMessageData}" + trace[CvxLean.debug] + "`equivalence` warning: failed to create `{eqvId}.backward_map`.\n{e.toMessageData}" /-- Create `equivalence` command. It is similar to the `reduction` command, but requires an `Equivalence` instead of a `Reduction`. -/ diff --git a/CvxLean/Meta/Util/Debug.lean b/CvxLean/Meta/Util/Debug.lean index f0807c98..07b7df1a 100644 --- a/CvxLean/Meta/Util/Debug.lean +++ b/CvxLean/Meta/Util/Debug.lean @@ -6,6 +6,6 @@ Custom debug trace classes. open Lean Meta -builtin_initialize +initialize registerTraceClass `CvxLean registerTraceClass `CvxLean.debug diff --git a/CvxLean/Meta/Util/Error.lean b/CvxLean/Meta/Util/Error.lean index 10f210d7..744e4405 100644 --- a/CvxLean/Meta/Util/Error.lean +++ b/CvxLean/Meta/Util/Error.lean @@ -22,3 +22,11 @@ syntax "throwSolveError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwSolveError $msg:interpolatedStr) => `(throwError ("`solve` error: " ++ (m! $msg))) | `(throwSolveError $msg:term) => `(throwError ("`solve` error: " ++ $msg)) + +/-- Errors in the `equivalence` command. -/ +syntax "throwEquivalenceError " (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwEquivalenceError $msg:interpolatedStr) => + `(throwError ("`equivalence` error: " ++ (m! $msg))) + | `(throwEquivalenceError $msg:term) => `(throwError ("`equivalence` error: " ++ $msg)) diff --git a/CvxLean/Meta/Util/Simp.lean b/CvxLean/Meta/Util/Simp.lean new file mode 100644 index 00000000..083b47a2 --- /dev/null +++ b/CvxLean/Meta/Util/Simp.lean @@ -0,0 +1,19 @@ +import Lean + +/-! +Custom `simp` configuration. +-/ + +open Lean Meta + +def aggressiveSimpConfig : Simp.Config := + { zeta := true + beta := true + eta := true + iota := true + proj := true + decide := true + arith := true + dsimp := true + unfoldPartialApp := true + etaStruct := .all } diff --git a/CvxLean/Tactic/Basic/ChangeOfVariables.lean b/CvxLean/Tactic/Basic/ChangeOfVariables.lean index 0db8f288..c7379a9b 100644 --- a/CvxLean/Tactic/Basic/ChangeOfVariables.lean +++ b/CvxLean/Tactic/Basic/ChangeOfVariables.lean @@ -23,6 +23,7 @@ class ChangeOfVariables {D E} (c : E → D) where property : ∀ x, condition x → c (inv x) = x /-- -/ +@[equiv] def ChangeOfVariables.toEquivalence {D E R} [Preorder R] {f : D → R} {cs : D → Prop} (c : E → D) [cov : ChangeOfVariables c] (h : ∀ x, cs x → cov.condition x) : ⟨f, cs⟩ ≡ ⟨fun x => f (c x), fun x => cs (c x)⟩ := From d1d15ceff12b427cbf01da202af1c083ae022052 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Thu, 1 Feb 2024 18:35:56 -0500 Subject: [PATCH 12/52] feat: more custom error commands --- CvxLean/Meta/Util/Error.lean | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CvxLean/Meta/Util/Error.lean b/CvxLean/Meta/Util/Error.lean index 744e4405..96261cb9 100644 --- a/CvxLean/Meta/Util/Error.lean +++ b/CvxLean/Meta/Util/Error.lean @@ -4,6 +4,18 @@ import Lean Custom error messages. -/ +syntax "throwPreDCPError " (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwPreDCPError $msg:interpolatedStr) => `(throwError ("`pre_dcp` error: " ++ (m! $msg))) + | `(throwPreDCPError $msg:term) => `(throwError ("`pre_dcp` error: " ++ $msg)) + +syntax "throwDCPError " (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwDCPError $msg:interpolatedStr) => `(throwError ("`dcp` error: " ++ (m! $msg))) + | `(throwDCPError $msg:term) => `(throwError ("`dcp` error: " ++ $msg)) + syntax "throwCoeffsError " (interpolatedStr(term) <|> term) : term macro_rules @@ -30,3 +42,19 @@ macro_rules | `(throwEquivalenceError $msg:interpolatedStr) => `(throwError ("`equivalence` error: " ++ (m! $msg))) | `(throwEquivalenceError $msg:term) => `(throwError ("`equivalence` error: " ++ $msg)) + +/-- Errors in the `reduction` command. -/ +syntax "throwReductionError " (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwReductionError $msg:interpolatedStr) => + `(throwError ("`reduction` error: " ++ (m! $msg))) + | `(throwReductionError $msg:term) => `(throwError ("`reduction` error: " ++ $msg)) + +/-- Errors in the `relaxation` command. -/ +syntax "throwRelaxationError " (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwRelaxationError $msg:interpolatedStr) => + `(throwError ("`relaxation` error: " ++ (m! $msg))) + | `(throwRelaxationError $msg:term) => `(throwError ("`relaxation` error: " ++ $msg)) From 3b4e9d2a070ccada585aaaa215d17791d366943b Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Thu, 1 Feb 2024 19:19:09 -0500 Subject: [PATCH 13/52] doc: everything in `Command` --- CvxLean/Command/Equivalence.lean | 30 ++++-- CvxLean/Command/Reduction.lean | 172 ++++++++++++++++++++++--------- CvxLean/Command/Relaxation.lean | 47 ++++----- 3 files changed, 170 insertions(+), 79 deletions(-) diff --git a/CvxLean/Command/Equivalence.lean b/CvxLean/Command/Equivalence.lean index 4ee96ab8..48506fd5 100644 --- a/CvxLean/Command/Equivalence.lean +++ b/CvxLean/Command/Equivalence.lean @@ -7,12 +7,24 @@ import CvxLean.Meta.Util.Debug import CvxLean.Meta.Equivalence import CvxLean.Meta.TacticBuilder import CvxLean.Command.Solve.Float.RealToFloatLibrary -import CvxLean.Tactic.Basic.ChangeOfVariables /-! # The `equivalence` command - +Given a problem `p : Minimization D R` and fresh identifiers `eqv` and `q`, a user can use the +equivalence command as follows: +``` +equivalence eqv/q : p := by ... +``` +Placing the cursor on after the `by` keyword opens up a tactic environment where the goal is to +prove `p ≡ ?q`. After applying a sequence of tactics that transform the goal into, say `r ≡ ?q`, +the user can leave the equivalence environment and two new definitions will be added to the +environment: +* `q := r`, +* `eqv : p ≡ q`. + +Writing `equivalence'` instead of `equivalence` will also generate a backward solution map at the +level of floats. -/ namespace CvxLean @@ -34,9 +46,15 @@ syntax (name := equivalence) syntax (name := equivalenceAndBwdMap) "equivalence'" ident "/" ident declSig ":=" Lean.Parser.Term.byTactic : command -/-- See `evalEquivalence` and `evalEquivalenceAndBwdMap`. -/ +/-- Open an equivalence environment with a given left-hand-side problem (`lhsStx`) and perhaps some +parameters (`xs`). From this, an equivalence goal is set to a target problem which is represented by +a metavariable. The proof (`proofStx`) is evaluated to produce the desired equivalence. The +metavariable is then instantiated and the resulting problem is stored using the identifier +`probIdStx`. The equivalence witness is stored according to the identifier `redIdStx`. Optionally, a +floating-point backward solution map is created. See `evalEquivalence` and +`evalEquivalenceAndBwdMap`. -/ def evalEquivalenceAux (probIdStx eqvIdStx : TSyntax `ident) (xs : Array (Syntax × Expr)) - (lhsStx: Syntax) (proofStx: TSyntax `Lean.Parser.Term.byTactic) (bwdMap : Bool) : + (lhsStx : Syntax) (proofStx : TSyntax `Lean.Parser.Term.byTactic) (bwdMap : Bool) : TermElabM Unit := do let D ← Meta.mkFreshTypeMVar let R ← Meta.mkFreshTypeMVar @@ -105,8 +123,7 @@ def evalEquivalenceAux (probIdStx eqvIdStx : TSyntax `ident) (xs : Array (Syntax trace[CvxLean.debug] "`equivalence` warning: failed to create `{eqvId}.backward_map`.\n{e.toMessageData}" -/-- Create `equivalence` command. It is similar to the `reduction` command, but requires an -`Equivalence` instead of a `Reduction`. -/ +/-- Create `equivalence` command. -/ @[command_elab «equivalence»] def evalEquivalence : CommandElab := fun stx => match stx with | `(equivalence $eqvId / $probId $declSig := $proofStx) => do @@ -126,5 +143,4 @@ def evalEquivalenceAndBwdMap : CommandElab := fun stx => match stx with evalEquivalenceAux probId eqvId xs lhsStx proofStx true | _ => throwUnsupportedSyntax - end CvxLean diff --git a/CvxLean/Command/Reduction.lean b/CvxLean/Command/Reduction.lean index 9729fba2..1424ef1e 100644 --- a/CvxLean/Command/Reduction.lean +++ b/CvxLean/Command/Reduction.lean @@ -2,13 +2,34 @@ import Lean import CvxLean.Lib.Reduction import CvxLean.Syntax.Minimization import CvxLean.Meta.Util.Expr +import CvxLean.Meta.Util.Simp +import CvxLean.Meta.Util.Error +import CvxLean.Meta.Util.Debug import CvxLean.Meta.Reduction import CvxLean.Meta.TacticBuilder +import CvxLean.Command.Solve.Float.RealToFloatLibrary /-! # The `reduction` command +Given a problem `p : Minimization D R` and fresh identifiers `red` and `q`, a user can use the +reduction command as follows: +``` +reduction red/q : p := by ... +``` +Placing the cursor on after the `by` keyword opens up a tactic environment where the goal is to +prove `p ≼ ?q`. After applying a sequence of tactics that transform the goal into, say `r ≼ ?q`, +the user can leave the reduction environment and two new definitions will be added to the +environment: +* `q := r`, +* `red : p ≼ q`. +Writing `reduction'` instead of `reduction` will also generate a backward solution map at the +level of floats. + +It is essentially the same as `Command/Equivalence.lean`, except that the goal is to prove a +reduction instead of an equivalence. We note that proving that two problems are equivalent is +usually preferred. -/ namespace CvxLean @@ -27,57 +48,110 @@ def elabReductionProof (lhs : Expr) (rhsName : Name) (stx : Syntax) : TermElabM syntax (name := reduction) "reduction" ident "/" ident declSig ":=" Lean.Parser.Term.byTactic : command -/-- Reduction command. -/ +syntax (name := reductionAndBwdMap) + "reduction'" ident "/" ident declSig ":=" Lean.Parser.Term.byTactic : command + +/-- Open a reduction environment with a given left-hand-side problem (`lhsStx`) and perhaps some +parameters (`xs`). From this, a reduction goal is set to a target problem which is represented by a +metavariable. The proof (`proofStx`) is evaluated to produce the desired reduction. The metavariable +is then instantiated and the resulting problem is stored using the identifier `probIdStx`. The +reduction witness is stored according to the identifier `redIdStx`. Optionally, a floating-point +backward solution map is created. See `evalReduction` and `evalReductionAndBwdMap`. -/ +def evalReductionAux (probIdStx redIdStx : TSyntax `ident) (xs : Array (Syntax × Expr)) + (lhsStx : Syntax) (proofStx : TSyntax `Lean.Parser.Term.byTactic) (bwdMap : Bool) : + TermElabM Unit := do + let D ← Meta.mkFreshTypeMVar + let R ← Meta.mkFreshTypeMVar + let lhsTy := mkApp2 (Lean.mkConst ``Minimization) D R + let lhs ← elabTermAndSynthesizeEnsuringType lhsStx (some lhsTy) + + -- NOTE: `instantiateMVars` does not infer the preorder instance. + for mvarId in ← getMVars lhs do + try { + let mvarVal ← synthInstance (← mvarId.getDecl).type + mvarId.assign mvarVal } + catch _ => pure () + + let rhsName := probIdStx.getId + let (rhs, eqv) ← elabReductionProof lhs rhsName proofStx + + -- Names for new definitions. + let currNamespace ← getCurrNamespace + let probId := currNamespace ++ probIdStx.getId + let redId := currNamespace ++ redIdStx.getId + + -- Add reduced problem to the environment. + let rhs ← instantiateMVars rhs + let rhs ← mkLambdaFVars (xs.map Prod.snd) rhs + let rhs ← instantiateMVars rhs + simpleAddDefn probId rhs + + -- Add reduction proof to the environment. + let eqv ← instantiateMVars eqv + let eqv ← mkLambdaFVars (xs.map Prod.snd) eqv + let eqv ← instantiateMVars eqv + simpleAddDefn redId eqv + + if bwdMap then + lambdaTelescope eqv fun eqvArgs eqvBody => do + -- Get psi, reduce it appropriately and convert to float. + let psi := (← whnf eqvBody).getArg! 7 + + let mut simpCtx ← Simp.Context.mkDefault + simpCtx := { simpCtx with config := aggressiveSimpConfig } + + let (.some redExt) ← getSimpExtension? `red | + throwReductionError "could not find `red` simp extension." + + let (.some equivExt) ← getSimpExtension? `equiv | + throwReductionError "could not find `equiv` simp extension." + + let mut simpEqvThms ← equivExt.getTheorems + simpEqvThms ← simpEqvThms.addDeclToUnfold ``Minimization.Equivalence.mk + simpEqvThms ← simpEqvThms.addDeclToUnfold ``Minimization.Equivalence.psi + let mut simpRedThms ← redExt.getTheorems + simpRedThms ← simpRedThms.addDeclToUnfold ``Minimization.Reduction.mk + simpRedThms ← simpRedThms.addDeclToUnfold ``Minimization.Reduction.psi + simpRedThms ← simpRedThms.addDeclToUnfold ``Eq.mp + simpRedThms ← simpRedThms.addDeclToUnfold ``Eq.mpr + + simpCtx := { simpCtx with simpTheorems := #[simpRedThms, simpEqvThms] } + + let (res, _) ← simp psi simpCtx + + -- NOTE: We ignore arguments with `Prop`s here as keeping them would only mean requiring + -- proofs about floats. + let redNonPropArgs ← eqvArgs.filterM fun arg => do + return !(← inferType (← inferType arg)).isProp + let psi ← mkLambdaFVars redNonPropArgs res.expr + trace[CvxLean.debug] "psi: {psi}" + + try + let psiF ← realToFloat psi + Lean.simpleAddAndCompileDefn (redId ++ `backward_map) psiF + catch e => + trace[CvxLean.debug] + "`reduction` warning: failed to create `{redId}.backward_map`.\n{e.toMessageData}" + +/-- Create `reduction` command. It is similar to the `equivalence` command, but requires a +`Reduction` instead of an `Equivalence`. -/ @[command_elab «reduction»] def evalReduction : CommandElab := fun stx => match stx with -| `(reduction $redId / $probId $declSig := $proofStx) => do - liftTermElabM do - let (binders, lhsStx) := expandDeclSig declSig.raw - elabBindersEx binders.getArgs fun xs => do - let D ← Meta.mkFreshTypeMVar - let R ← Meta.mkFreshTypeMVar - let lhsTy := mkApp2 (Lean.mkConst ``Minimization) D R - let lhs ← elabTermAndSynthesizeEnsuringType lhsStx (some lhsTy) - - -- NOTE: `instantiateMVars` does not infer the preorder instance. - for mvarId in ← getMVars lhs do - try { - let mvarVal ← synthInstance (← mvarId.getDecl).type - mvarId.assign mvarVal } - catch _ => pure () - - let rhsName := probId.getId - let (rhs, proof) ← elabReductionProof lhs rhsName proofStx.raw - - -- Add reduced problem to the environment. - let rhs ← instantiateMVars rhs - let rhs ← mkLambdaFVars (xs.map Prod.snd) rhs - let rhs ← instantiateMVars rhs - Lean.addDecl <| - Declaration.defnDecl - (mkDefinitionValEx probId.getId - [] - (← inferType rhs) - rhs - (Lean.ReducibilityHints.regular 0) - (DefinitionSafety.safe) - []) - - -- Add reduction proof to the environment. - let proofTy ← inferType proof - let proofTy ← mkForallFVars (xs.map Prod.snd) proofTy - let proofTy ← instantiateMVars proofTy - let proof ← mkLambdaFVars (xs.map Prod.snd) proof - let proof ← instantiateMVars proof - Lean.addDecl <| - Declaration.defnDecl - (mkDefinitionValEx redId.getId - [] - proofTy - proof - (Lean.ReducibilityHints.regular 0) - (DefinitionSafety.safe) - []) + | `(reduction $eqvId / $probId $declSig := $proofStx) => do + liftTermElabM do + let (binders, lhsStx) := expandDeclSig declSig.raw + elabBindersEx binders.getArgs fun xs => + evalReductionAux probId eqvId xs lhsStx proofStx false + | _ => throwUnsupportedSyntax + +/-- Same as `reduction` but also adds the backward map to the environment. -/ +@[command_elab «reductionAndBwdMap»] +def evalReductionAndBwdMap : CommandElab := fun stx => match stx with + | `(reduction' $eqvId / $probId $declSig := $proofStx) => do + liftTermElabM do + let (binders, lhsStx) := expandDeclSig declSig.raw + elabBindersEx binders.getArgs fun xs => + evalReductionAux probId eqvId xs lhsStx proofStx true | _ => throwUnsupportedSyntax end CvxLean diff --git a/CvxLean/Command/Relaxation.lean b/CvxLean/Command/Relaxation.lean index 762434a8..46239702 100644 --- a/CvxLean/Command/Relaxation.lean +++ b/CvxLean/Command/Relaxation.lean @@ -8,7 +8,22 @@ import CvxLean.Meta.TacticBuilder /-! # The `relaxation` command +Given a problem `p : Minimization D R` and fresh identifiers `rel` and `q`, a user can use the +relaxation command as follows: +``` +relaxation rel/q : p := by ... +``` +Placing the cursor on after the `by` keyword opens up a tactic environment where the goal is to +prove `p ≽' ?q`. After applying a sequence of tactics that transform the goal into, say `r ≽' ?q`, +the user can leave the relaxation environment and two new definitions will be added to the +environment: +* `q := r`, +* `rel : p ≽' q`. +This command is very similar to `equivalence` (`Command/Equivalence.lean`) and `reduction` +(`Command/Reduction.lean`) in how it is designed. Of course, the key difference is that the goal +is to prove a relaxation. Note that in this case, there is no option to create a backward map as +relaxations do not guarantee that the solution can be mapped back. -/ namespace CvxLean @@ -30,7 +45,7 @@ syntax (name := relaxation) /-- Relaxation command. -/ @[command_elab «relaxation»] def evalRelaxation : CommandElab := fun stx => match stx with -| `(relaxation $relId / $probId $declSig := $proofStx) => do +| `(relaxation $relIdStx / $probIdStx $declSig := $proofStx) => do liftTermElabM do let (binders, lhsStx) := expandDeclSig declSig.raw elabBindersEx binders.getArgs fun xs => do @@ -46,38 +61,24 @@ def evalRelaxation : CommandElab := fun stx => match stx with mvarId.assign mvarVal } catch _ => pure () - let rhsName := probId.getId + let rhsName := probIdStx.getId let (rhs, proof) ← elabRelaxationProof lhs rhsName proofStx.raw + -- Names for new definitions. + let currNamespace ← getCurrNamespace + let probId := currNamespace ++ probIdStx.getId + let relId := currNamespace ++ relIdStx.getId + -- Add relaxed problem to the environment. let rhs ← instantiateMVars rhs let rhs ← mkLambdaFVars (xs.map Prod.snd) rhs let rhs ← instantiateMVars rhs - Lean.addDecl <| - Declaration.defnDecl - (mkDefinitionValEx probId.getId - [] - (← inferType rhs) - rhs - (Lean.ReducibilityHints.regular 0) - (DefinitionSafety.safe) - []) + simpleAddDefn probId rhs -- Add Relaxation proof to the environment. - let proofTy ← inferType proof - let proofTy ← mkForallFVars (xs.map Prod.snd) proofTy - let proofTy ← instantiateMVars proofTy let proof ← mkLambdaFVars (xs.map Prod.snd) proof let proof ← instantiateMVars proof - Lean.addDecl <| - Declaration.defnDecl - (mkDefinitionValEx relId.getId - [] - proofTy - proof - (Lean.ReducibilityHints.regular 0) - (DefinitionSafety.safe) - []) + simpleAddDefn relId proof | _ => throwUnsupportedSyntax end CvxLean From a7b4d3315a91cb823d32ccb7fc021270bfe0cd77 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Fri, 2 Feb 2024 19:22:20 -0500 Subject: [PATCH 14/52] chore: bump to v4.6.0-rc1 and update mathlib --- lake-manifest.json | 23 ++++++++++++++++------- lakefile.lean | 2 +- lean-toolchain | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lake-manifest.json b/lake-manifest.json index c2fc68cd..3d9812ca 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -4,7 +4,7 @@ [{"url": "https://github.com/leanprover/std4", "type": "git", "subDir": null, - "rev": "16d8352f7ed0d38cbc58ace03b3429d693cf50c6", + "rev": "276953b13323ca151939eafaaec9129bf7970306", "name": "std", "manifestFile": "lake-manifest.json", "inputRev": "main", @@ -13,7 +13,7 @@ {"url": "https://github.com/leanprover-community/quote4", "type": "git", "subDir": null, - "rev": "ccba5d35d07a448fab14c0e391c8105df6e2564c", + "rev": "1c88406514a636d241903e2e288d21dc6d861e01", "name": "Qq", "manifestFile": "lake-manifest.json", "inputRev": "master", @@ -22,7 +22,7 @@ {"url": "https://github.com/leanprover-community/aesop", "type": "git", "subDir": null, - "rev": "3141402ba5a5f0372d2378fd75a481bc79a74ecf", + "rev": "6beed82dcfbb7731d173cd517675df27d62ad0f4", "name": "aesop", "manifestFile": "lake-manifest.json", "inputRev": "master", @@ -31,10 +31,10 @@ {"url": "https://github.com/leanprover-community/ProofWidgets4", "type": "git", "subDir": null, - "rev": "909febc72b4f64628f8d35cd0554f8a90b6e0749", + "rev": "af1e86cf7a37389632a02f4a111e6b501b2b818f", "name": "proofwidgets", "manifestFile": "lake-manifest.json", - "inputRev": "v0.0.23", + "inputRev": "v0.0.27", "inherited": true, "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover/lean4-cli", @@ -46,13 +46,22 @@ "inputRev": "main", "inherited": true, "configFile": "lakefile.lean"}, + {"url": "https://github.com/leanprover-community/import-graph.git", + "type": "git", + "subDir": null, + "rev": "8079d2d1d0e073bde42eab159c24f4c2d0d3a871", + "name": "importGraph", + "manifestFile": "lake-manifest.json", + "inputRev": "main", + "inherited": true, + "configFile": "lakefile.lean"}, {"url": "https://github.com/leanprover-community/mathlib4", "type": "git", "subDir": null, - "rev": "1250aa83953a2c7d5819cebea08ad7fdef997d49", + "rev": "c838d28fb418158125f1551662ef55113d22eeec", "name": "mathlib", "manifestFile": "lake-manifest.json", - "inputRev": "1250aa83953a2c7d5819cebea08ad7fdef997d49", + "inputRev": "c838d28fb418158125f1551662ef55113d22eeec", "inherited": false, "configFile": "lakefile.lean"}], "name": "CvxLean", diff --git a/lakefile.lean b/lakefile.lean index 25674305..8cffb30f 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -3,7 +3,7 @@ open System Lake DSL require mathlib from git "https://github.com/leanprover-community/mathlib4" @ - "1250aa83953a2c7d5819cebea08ad7fdef997d49" + "c838d28fb418158125f1551662ef55113d22eeec" meta if get_config? env = some "dev" then require scilean from git diff --git a/lean-toolchain b/lean-toolchain index 91ccf6ac..cfcdd327 100644 --- a/lean-toolchain +++ b/lean-toolchain @@ -1 +1 @@ -leanprover/lean4:v4.4.0-rc1 +leanprover/lean4:v4.6.0-rc1 From db410b2c6d11b75bb2e293959ecae479b5538d17 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Fri, 2 Feb 2024 19:47:58 -0500 Subject: [PATCH 15/52] wip: fixing proofs, no `Abs` --- CvxLean/Lib/Math/Data/Array.lean | 2 +- CvxLean/Lib/Math/Data/Matrix.lean | 6 ++---- CvxLean/Lib/Math/Data/Real.lean | 3 --- CvxLean/Lib/Math/Data/Vec.lean | 8 +++----- CvxLean/Lib/Math/LinearAlgebra/Matrix/LDL.lean | 13 ++++++------- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/CvxLean/Lib/Math/Data/Array.lean b/CvxLean/Lib/Math/Data/Array.lean index 2be3b108..a23b44ba 100644 --- a/CvxLean/Lib/Math/Data/Array.lean +++ b/CvxLean/Lib/Math/Data/Array.lean @@ -31,7 +31,7 @@ def Array.filterIdx (as : Array α) (f : Nat → Bool) := Id.run do pure cs else pure cs -termination_by _ => as.size - i +termination_by as.size - i def Array.zipWithM (f : α → β → MetaM γ) (as : Array α) (bs : Array β) : MetaM (Array γ) := diff --git a/CvxLean/Lib/Math/Data/Matrix.lean b/CvxLean/Lib/Math/Data/Matrix.lean index 31f563a7..ba690b09 100644 --- a/CvxLean/Lib/Math/Data/Matrix.lean +++ b/CvxLean/Lib/Math/Data/Matrix.lean @@ -17,10 +17,8 @@ instance [Preorder α] : Preorder (Matrix m n α) where le_trans := fun _ _ _ hAB hBC i j => le_trans (hAB i j) (hBC i j) lt_iff_le_not_le := fun _ _ => refl _ -def abs [Abs α] (A : Matrix m n α) : Matrix m n α := - fun i j => Abs.abs (A i j) - -instance [Abs α] : Abs (Matrix m n α) := ⟨abs⟩ +def abs (A : Matrix m n ℝ) : Matrix m n ℝ := + fun i j => |A i j| theorem vecCons_zero_zero {n} [Zero R] : vecCons (0 : R) (0 : Fin n → R) = 0 := by ext i ; refine' Fin.cases _ _ i <;> simp [vecCons] diff --git a/CvxLean/Lib/Math/Data/Real.lean b/CvxLean/Lib/Math/Data/Real.lean index cb223f24..89eedbad 100644 --- a/CvxLean/Lib/Math/Data/Real.lean +++ b/CvxLean/Lib/Math/Data/Real.lean @@ -43,9 +43,6 @@ Useful instances to construct expressions. noncomputable instance instDivReal : Div ℝ := by infer_instance -noncomputable instance instAbsReal : Abs ℝ := by - infer_instance - noncomputable instance instMinReal : Min ℝ := by infer_instance diff --git a/CvxLean/Lib/Math/Data/Vec.lean b/CvxLean/Lib/Math/Data/Vec.lean index c43956ae..88c7f07b 100644 --- a/CvxLean/Lib/Math/Data/Vec.lean +++ b/CvxLean/Lib/Math/Data/Vec.lean @@ -21,10 +21,8 @@ Named functions on vectors. Each of them corresponds to an atom. variable {m : Type u} {n : Type v} [Fintype m] [Fintype n] {α : Type w} /-- See `CvxLean.Tactic.DCP.AtomLibrary.Fns.Abs`. -/ -def abs [Abs α] (x : m → α) : m → α := - fun i => Abs.abs (x i) - -instance [Abs α] : Abs (m → α) := ⟨abs⟩ +def abs (x : m → ℝ) : m → ℝ := + fun i => |x i| /-- See `CvxLean.Tactic.DCP.AtomLibrary.Fns.VecConst`. -/ def const (n : ℕ) (k : α) : Fin n → α := @@ -66,7 +64,7 @@ Named functions on real vectors, including those defined in open Real BigOperators /-- See `CvxLean.Tactic.DCP.AtomLibrary.Fns.Norm2`. -/ -instance : Norm (m → ℝ) := PiLp.hasNorm 2 _ +instance : Norm (m → ℝ) := PiLp.instNorm 2 _ variable (x y : m → ℝ) diff --git a/CvxLean/Lib/Math/LinearAlgebra/Matrix/LDL.lean b/CvxLean/Lib/Math/LinearAlgebra/Matrix/LDL.lean index f3622025..4a737ebe 100644 --- a/CvxLean/Lib/Math/LinearAlgebra/Matrix/LDL.lean +++ b/CvxLean/Lib/Math/LinearAlgebra/Matrix/LDL.lean @@ -21,13 +21,12 @@ open scoped Matrix ComplexOrder variable {S : Matrix n n 𝕜} [Fintype n] (hS : S.PosDef) @[simp] -lemma lowerInv_diagonal (i : n) : - lowerInv hS i i = 1 := by - rw [lowerInv_eq_gramSchmidtBasis] - simpa only [gramSchmidtBasis, Basis.coe_mk] - using @repr_gramSchmidt_diagonal 𝕜 (n → 𝕜) _ - (NormedAddCommGroup.ofMatrix hS.transpose) - (InnerProductSpace.ofMatrix hS.transpose) n _ _ _ i (Pi.basisFun 𝕜 n) +lemma lowerInv_diagonal (i : n) : lowerInv hS i i = 1 := by + simp only [lowerInv_eq_gramSchmidtBasis, gramSchmidtBasis] + letI := NormedAddCommGroup.ofMatrix hS.transpose + letI := InnerProductSpace.ofMatrix hS.transpose + rw [Basis.coe_mk, ← @repr_gramSchmidt_diagonal 𝕜 (n → 𝕜) _ _ _ n _ _ _ i (Pi.basisFun 𝕜 n)] + simp [Basis.toMatrix] lemma lower_eq_to_matrix : lower hS = From 50dc6197aa9e99d01b3fd2088e8d158b1cdc3c85 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Fri, 2 Feb 2024 20:01:48 -0500 Subject: [PATCH 16/52] fix: all proofs fixed --- CvxLean/Command/Solve/Float/RealToFloatLibrary.lean | 6 ------ CvxLean/Lib/Equivalence.lean | 2 +- CvxLean/Tactic/DCP/AtomLibrary/Fns/LogSumExp.lean | 4 ++-- CvxLean/Tactic/PreDCP/Egg/ToExpr.lean | 4 ++-- CvxLean/Tactic/PreDCP/PreDCP.lean | 6 +++--- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean b/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean index 1ca7830c..4b17238b 100644 --- a/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean +++ b/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean @@ -47,12 +47,6 @@ addRealToFloat (n : Nat) : AddMonoidWithOne.toNatCast.natCast (R := ℝ) n := addRealToFloat (i) (x : ℕ) : @Nat.cast Real i x := Float.ofNat x -addRealToFloat (n) (i1) (i2) : @instOfNat Real n i1 i2 := - @instOfNatFloat n - -addRealToFloat (x : ℕ) (i) : @instOfNat Real x Real.natCast i := - @instOfNatFloat x - addRealToFloat (k : Nat) : @SMul.smul ℕ ℝ AddMonoid.toNatSMul k := (fun (x : Float) => (OfNat.ofNat k) * x) diff --git a/CvxLean/Lib/Equivalence.lean b/CvxLean/Lib/Equivalence.lean index e5beecec..986efeab 100644 --- a/CvxLean/Lib/Equivalence.lean +++ b/CvxLean/Lib/Equivalence.lean @@ -221,7 +221,7 @@ noncomputable def map_objFun_log {f : D → ℝ} (h : ∀ x, cs x → f x > 0) : ⟨f, cs⟩ ≡ ⟨fun x => (Real.log (f x)), cs⟩ := by apply map_objFun intros r s h_feas_r h_feas_s - exact Real.log_le_log (h r h_feas_r) (h s h_feas_s) + exact Real.log_le_log_iff (h r h_feas_r) (h s h_feas_s) @[equiv] noncomputable def map_objFun_sq {f : D → ℝ} (h : ∀ x, cs x → f x ≥ 0) : diff --git a/CvxLean/Tactic/DCP/AtomLibrary/Fns/LogSumExp.lean b/CvxLean/Tactic/DCP/AtomLibrary/Fns/LogSumExp.lean index b98dbc72..40d48873 100644 --- a/CvxLean/Tactic/DCP/AtomLibrary/Fns/LogSumExp.lean +++ b/CvxLean/Tactic/DCP/AtomLibrary/Fns/LogSumExp.lean @@ -32,7 +32,7 @@ feasibility optimality by intros y hy simp [Vec.sum_exp_eq_sum_div, div_le_iff (exp_pos _)] at c - rw [←log_exp t, log_le_log (Vec.sum_exp_pos hn y) (exp_pos _)] + rw [←log_exp t, log_le_log_iff (Vec.sum_exp_pos hn y) (exp_pos _)] refine le_trans ?_ c apply Finset.sum_le_sum intros i _ @@ -55,7 +55,7 @@ optimality by intros x' y' hx' hy' simp [Vec.sum, Vec.exp] at c refine le_trans ?_ c - rw [log_le_log (by positivity) (by positivity)] + rw [log_le_log_iff (by positivity) (by positivity)] apply add_le_add { rw [exp_le_exp]; exact hx' } { rw [exp_le_exp]; exact hy' } diff --git a/CvxLean/Tactic/PreDCP/Egg/ToExpr.lean b/CvxLean/Tactic/PreDCP/Egg/ToExpr.lean index acf53009..cb4fc1d9 100644 --- a/CvxLean/Tactic/PreDCP/Egg/ToExpr.lean +++ b/CvxLean/Tactic/PreDCP/Egg/ToExpr.lean @@ -94,8 +94,8 @@ partial def EggTree.toExpr (vars params : List String) : Tree String String → | Tree.node "abs" #[t] => do let t ← toExpr vars params t return mkAppN - (mkConst ``Abs.abs [levelZero]) - #[(mkConst ``Real), (mkConst ``Real.instAbsReal), t] + (mkConst ``abs [levelZero]) + #[(mkConst ``Real), (mkConst ``Real.lattice), (mkConst ``Real.instAddGroupReal), t] -- Square root. | Tree.node "sqrt" #[t] => do let t ← toExpr vars params t diff --git a/CvxLean/Tactic/PreDCP/PreDCP.lean b/CvxLean/Tactic/PreDCP/PreDCP.lean index 7c13bcba..9edbfec5 100644 --- a/CvxLean/Tactic/PreDCP/PreDCP.lean +++ b/CvxLean/Tactic/PreDCP/PreDCP.lean @@ -146,10 +146,10 @@ def preDCPBuilder : EquivalenceBuilder := fun eqvExpr g => g.withContext do try -- Call egg (time it for evaluation). - let before ← BaseIO.toIO IO.monoMsNow + let bef ← BaseIO.toIO IO.monoMsNow let steps ← runEggRequest eggRequest - let after ← BaseIO.toIO IO.monoMsNow - let diff := after - before + let aft ← BaseIO.toIO IO.monoMsNow + let diff := aft - bef dbg_trace s!"Egg time: {diff} ms." dbg_trace s!"Number of steps: {steps.size}." let size := (gStr.map fun (_, t) => t.size).fold 0 Nat.add From 3a66d96106d64f10a78fc28a5758edd585c309fa Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Fri, 2 Feb 2024 20:03:12 -0500 Subject: [PATCH 17/52] fix: `log_le_log_iff` --- CvxLean/Examples/CovarianceEstimation.lean | 2 +- CvxLean/Tactic/PreDCP/RewriteMapLibrary.lean | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CvxLean/Examples/CovarianceEstimation.lean b/CvxLean/Examples/CovarianceEstimation.lean index b56854f9..eee541a8 100644 --- a/CvxLean/Examples/CovarianceEstimation.lean +++ b/CvxLean/Examples/CovarianceEstimation.lean @@ -19,7 +19,7 @@ reduction red/covEstimationConvex (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → · intros R S hR hS h apply neg_le_neg simp only [maximizeNeg] at h - rwa [neg_neg, neg_neg, neg_le_neg_iff, log_le_log] at h + rwa [neg_neg, neg_neg, neg_le_neg_iff, log_le_log_iff] at h exact prod_gaussianPdf_pos S y hS.1 exact prod_gaussianPdf_pos R y hR.1 simp only [Function.comp, neg_neg, maximizeNeg] diff --git a/CvxLean/Tactic/PreDCP/RewriteMapLibrary.lean b/CvxLean/Tactic/PreDCP/RewriteMapLibrary.lean index 48d34489..50117d42 100644 --- a/CvxLean/Tactic/PreDCP/RewriteMapLibrary.lean +++ b/CvxLean/Tactic/PreDCP/RewriteMapLibrary.lean @@ -57,10 +57,10 @@ register_rewrite_map "div_le_one-rev" ; "(le ?a ?b)" => "(le (div ?a ?b) 1)" := rw [←div_le_one (by positivity!)]; register_rewrite_map "log_le_log" ; "(le (log ?a) (log ?b))" => "(le ?a ?b)" := - rw [Real.log_le_log (by positivity!) (by positivity!)]; + rw [Real.log_le_log_iff (by positivity!) (by positivity!)]; register_rewrite_map "log_le_log-rev" ; "(le ?a ?b)" => "(le (log ?a) (log ?b))" := - rw [←Real.log_le_log (by positivity!) (by positivity!)]; + rw [←Real.log_le_log_iff (by positivity!) (by positivity!)]; register_rewrite_map "pow_two_le_pow_two"; "(le (pow ?a 2) (pow ?b 2))" => "(le ?a ?b)":= rw [Real.pow_two_le_pow_two (by positivity!) (by positivity!)]; From ec9ebeda65aef3282ef5432efbc2bc3abb21e5e5 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 11:47:43 -0500 Subject: [PATCH 18/52] fix: confusion between real and vector norms --- CvxLean/Command/Solve/Float/RealToFloatLibrary.lean | 2 +- CvxLean/Lib/Math/Data/Matrix.lean | 3 --- CvxLean/Lib/Math/Data/Vec.lean | 7 +++++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean b/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean index 4b17238b..4a729f90 100644 --- a/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean +++ b/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean @@ -123,7 +123,7 @@ addRealToFloat : @Real.log := Float.log addRealToFloat (n) (i) : @Norm.norm.{0} (Fin n → ℝ) i := - @Vec.Computable.norm n + @Real.Computable.norm n addRealToFloat (i) : @OfScientific.ofScientific Real i := Float.ofScientific diff --git a/CvxLean/Lib/Math/Data/Matrix.lean b/CvxLean/Lib/Math/Data/Matrix.lean index ba690b09..a4eb4ef1 100644 --- a/CvxLean/Lib/Math/Data/Matrix.lean +++ b/CvxLean/Lib/Math/Data/Matrix.lean @@ -99,9 +99,6 @@ def fromBlocks {l : Type} {m : Type} {n : Type} {o : Type} {α : Type} : def toUpperTri (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float := fun i j => if i ≤ j then A i j else 0 -def norm {n m : ℕ} (A : Matrix (Fin n) (Fin m) Float) : Fin n → Float := - fun i => Vec.Computable.norm (A i) - end Computable end Matrix diff --git a/CvxLean/Lib/Math/Data/Vec.lean b/CvxLean/Lib/Math/Data/Vec.lean index 88c7f07b..7692f0ba 100644 --- a/CvxLean/Lib/Math/Data/Vec.lean +++ b/CvxLean/Lib/Math/Data/Vec.lean @@ -114,7 +114,7 @@ end RealLemmas namespace Computable /-! -Computable operations on matrices used in `RealToFloat`. +Computable operations on vectors used in `RealToFloat`. -/ variable {n : ℕ} @@ -128,9 +128,12 @@ def sum (x : Fin n → Float) : Float := def cumsum (x : Fin n → Float) : Fin n → Float := fun i => (((toArray x).toList.take (i.val + 1)).foldl Float.add 0) -def norm {n : ℕ} (x : Fin n → Float) : Float := +def _root_.Real.Computable.norm {n : ℕ} (x : Fin n → Float) : Float := Float.sqrt (sum (fun i => (Float.pow (x i) 2))) +def norm {n m : ℕ} (A : Fin n → Fin m → Float) : Fin n → Float := + fun i => Real.Computable.norm (A i) + def exp {m} (x : m → Float) : m → Float := fun i => Float.exp (x i) From 15672a1febd87d46c58175c1d228779018a2b4d3 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 12:06:47 -0500 Subject: [PATCH 19/52] fix: reduction backward maps --- CvxLean/Command/Equivalence.lean | 3 ++- CvxLean/Command/Reduction.lean | 17 +++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CvxLean/Command/Equivalence.lean b/CvxLean/Command/Equivalence.lean index 48506fd5..1e5038c8 100644 --- a/CvxLean/Command/Equivalence.lean +++ b/CvxLean/Command/Equivalence.lean @@ -92,6 +92,7 @@ def evalEquivalenceAux (probIdStx eqvIdStx : TSyntax `ident) (xs : Array (Syntax lambdaTelescope eqv fun eqvArgs eqvBody => do -- Get psi, reduce it appropriately and convert to float. let psi := (← whnf eqvBody).getArg! 7 + trace[CvxLean.debug] "psi: {psi}" let mut simpCtx ← Simp.Context.mkDefault simpCtx := { simpCtx with config := aggressiveSimpConfig } @@ -114,7 +115,7 @@ def evalEquivalenceAux (probIdStx eqvIdStx : TSyntax `ident) (xs : Array (Syntax let eqvNonPropArgs ← eqvArgs.filterM fun arg => do return !(← inferType (← inferType arg)).isProp let psi ← mkLambdaFVars eqvNonPropArgs res.expr - trace[CvxLean.debug] "psi: {psi}" + trace[CvxLean.debug] "simplified psi: {psi}" try let psiF ← realToFloat psi diff --git a/CvxLean/Command/Reduction.lean b/CvxLean/Command/Reduction.lean index 1424ef1e..1f2461c9 100644 --- a/CvxLean/Command/Reduction.lean +++ b/CvxLean/Command/Reduction.lean @@ -73,7 +73,7 @@ def evalReductionAux (probIdStx redIdStx : TSyntax `ident) (xs : Array (Syntax catch _ => pure () let rhsName := probIdStx.getId - let (rhs, eqv) ← elabReductionProof lhs rhsName proofStx + let (rhs, red) ← elabReductionProof lhs rhsName proofStx -- Names for new definitions. let currNamespace ← getCurrNamespace @@ -87,15 +87,16 @@ def evalReductionAux (probIdStx redIdStx : TSyntax `ident) (xs : Array (Syntax simpleAddDefn probId rhs -- Add reduction proof to the environment. - let eqv ← instantiateMVars eqv - let eqv ← mkLambdaFVars (xs.map Prod.snd) eqv - let eqv ← instantiateMVars eqv - simpleAddDefn redId eqv + let red ← instantiateMVars red + let red ← mkLambdaFVars (xs.map Prod.snd) red + let red ← instantiateMVars red + simpleAddDefn redId red if bwdMap then - lambdaTelescope eqv fun eqvArgs eqvBody => do + lambdaTelescope red fun eqvArgs redBody => do -- Get psi, reduce it appropriately and convert to float. - let psi := (← whnf eqvBody).getArg! 7 + let psi := (← whnf redBody).getArg! 6 + trace[CvxLean.debug] "psi: {psi}" let mut simpCtx ← Simp.Context.mkDefault simpCtx := { simpCtx with config := aggressiveSimpConfig } @@ -124,7 +125,7 @@ def evalReductionAux (probIdStx redIdStx : TSyntax `ident) (xs : Array (Syntax let redNonPropArgs ← eqvArgs.filterM fun arg => do return !(← inferType (← inferType arg)).isProp let psi ← mkLambdaFVars redNonPropArgs res.expr - trace[CvxLean.debug] "psi: {psi}" + trace[CvxLean.debug] "simplified psi: {psi}" try let psiF ← realToFloat psi From 7191b0c553ed4984e5e9fc311156114b4e883fbe Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 12:47:41 -0500 Subject: [PATCH 20/52] feat: computable matrix inverse --- CvxLean/Lib/Math/Data/Matrix.lean | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/CvxLean/Lib/Math/Data/Matrix.lean b/CvxLean/Lib/Math/Data/Matrix.lean index a4eb4ef1..1d5080cf 100644 --- a/CvxLean/Lib/Math/Data/Matrix.lean +++ b/CvxLean/Lib/Math/Data/Matrix.lean @@ -78,7 +78,6 @@ def trace (A : Matrix (Fin n) (Fin n) Float) : Float := def covarianceMatrix {N n : ℕ} (Y : Matrix (Fin N) (Fin n) Float) (i j : Fin n) : Float := Vec.Computable.sum (fun k => (Y k i) * (Y k j)) / (OfNat.ofNat N) - --((vecToArray Y).map (fun y => (y i) * y j)).foldl (· + ·) 0 / (OfNat.ofNat N) def diagonal (x : Fin n → Float) : Matrix (Fin n) (Fin n) Float := fun i j => (if i = j then x i else 0) @@ -99,6 +98,35 @@ def fromBlocks {l : Type} {m : Type} {n : Type} {o : Type} {α : Type} : def toUpperTri (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float := fun i j => if i ≤ j then A i j else 0 +private def minorAux (A : Matrix (Fin n.succ) (Fin n.succ) Float) (a b : Fin n.succ) : + Matrix (Fin n) (Fin n) Float := + fun i j => + let i' : Fin n.succ := if i.val < a.val then i else i.succ; + let j' : Fin n.succ := if j.val < b.val then j else j.succ; + A i' j' + +def minor (A : Matrix (Fin n) (Fin n) Float) (a b : Fin n) : + Matrix (Fin n.pred) (Fin n.pred) Float := + match n with + | 0 => fun _ => Fin.elim0 + | _ + 1 => minorAux A a b + +def det {n : ℕ} (A : Matrix (Fin n) (Fin n) Float) : Float := + if h : 0 < n then + if n == 1 then A ⟨0, h⟩ ⟨0, h⟩ else + (List.finRange n).foldl (fun s i => + s + (-1) ^ (Float.ofNat i.val) * A i ⟨0, h⟩ * det (minor A i ⟨0, h⟩)) 0 + else 0 + +def cofactor (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float := + fun i j => (-1) ^ (Float.ofNat (i.val + j.val)) * (A i j) + +def adjugate (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float := + transpose (cofactor A) + +def inv (A : Matrix (Fin n) (Fin n) Float) : Matrix (Fin n) (Fin n) Float := + (1 / det A) • adjugate A + end Computable end Matrix From ae3c24a053a4d9cbe09a4682b0c30ae25e3831ff Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 12:56:24 -0500 Subject: [PATCH 21/52] feat: recovering all original solutions in case studies --- .../Solve/Float/RealToFloatLibrary.lean | 3 ++ CvxLean/Examples/CovarianceEstimation.lean | 43 +++++++++++++++++-- CvxLean/Examples/VehicleSpeedScheduling.lean | 14 +++--- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean b/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean index 4a729f90..e9620c58 100644 --- a/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean +++ b/CvxLean/Command/Solve/Float/RealToFloatLibrary.lean @@ -200,6 +200,9 @@ addRealToFloat (l m n) (i) : addRealToFloat (n : Nat) (i1) (i2) : @Matrix.toUpperTri.{0,0} (Fin n) ℝ i1 i2 := @Matrix.Computable.toUpperTri n +addRealToFloat (n) (i) : @Inv.inv (Matrix (Fin n) (Fin n) ℝ) i := + @Matrix.Computable.inv n + end Matrix section CovarianceEstimation diff --git a/CvxLean/Examples/CovarianceEstimation.lean b/CvxLean/Examples/CovarianceEstimation.lean index eee541a8..dd77b0e1 100644 --- a/CvxLean/Examples/CovarianceEstimation.lean +++ b/CvxLean/Examples/CovarianceEstimation.lean @@ -11,7 +11,7 @@ noncomputable def covEstimation (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → Fi c_pos_def : R.PosDef c_sparse : R⁻¹.abs.sum ≤ α -reduction red/covEstimationConvex (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → Fin n → ℝ) : +reduction' red/covEstimationConvex (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → Fin n → ℝ) : covEstimation n N α y := by -- Change objective function. reduction_step => @@ -22,6 +22,7 @@ reduction red/covEstimationConvex (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → rwa [neg_neg, neg_neg, neg_le_neg_iff, log_le_log_iff] at h exact prod_gaussianPdf_pos S y hS.1 exact prod_gaussianPdf_pos R y hR.1 + conv_opt => simp only [Function.comp, neg_neg, maximizeNeg] -- Move logarithm and sum inward. reduction_step => @@ -38,14 +39,16 @@ reduction red/covEstimationConvex (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → · intros R hR simp only [nonsing_inv_nonsing_inv R hR.1.isUnit_det] -- Dissolve matrix inverse. - reduction_step => + conv_opt => simp only [Function.comp, Matrix.PosDef_inv_iff_PosDef] + reduction_step => apply Reduction.rewrite_objFun · intros R hR rewrite [nonsing_inv_nonsing_inv R (hR.1.isUnit_det), Matrix.det_nonsing_inv] rewrite [Real.inverse_eq_inv, Real.log_inv] rfl + reduction_step => apply Reduction.rewrite_constraints · intros R rw [and_congr_right_iff] @@ -61,14 +64,46 @@ reduction red/covEstimationConvex (n : ℕ) (N : ℕ) (α : ℝ) (y : Fin N → -- c_pos_def : PosDef R -- c_sparse : sum (Matrix.abs R) ≤ α -set_option maxHeartbeats 20000000 +-- We solve the problem for a simple example. + +@[optimization_param, reducible] +def nₚ : ℕ := 2 + +@[optimization_param, reducible] +def Nₚ : ℕ := 4 + +@[optimization_param] +def αₚ : ℝ := 1 -solve covEstimationConvex 2 4 1 ![![0,2],![2,0],![-2,0],![0,-2]] +@[optimization_param] +def yₚ : Fin Nₚ → Fin nₚ → ℝ := ![![0, 2], ![2, 0], ![-2, 0], ![0, -2]] + +solve covEstimationConvex nₚ Nₚ αₚ yₚ #print covEstimationConvex.reduced +-- minimize +-- -(-(Nₚ • log (sqrt ((2 * π) ^ nₚ)) + Nₚ • (-Vec.sum t.0 / 2)) + +-- -(↑Nₚ * trace ((covarianceMatrix fun i => yₚ i) * Rᵀ) / 2)) +-- subject to +-- _ : Real.posOrthCone (αₚ - sum T.2) +-- _ : Vec.expCone t.0 1 (diag Y.1) +-- _ : +-- PSDCone +-- (let Z := toUpperTri Y.1; +-- let D := diagonal (diag Y.1); +-- let X := fromBlocks D Z Zᵀ R; +-- X) +-- _ : Matrix.posOrthCone (T.2 - R) +-- _ : Matrix.posOrthCone (T.2 + R) #eval covEstimationConvex.status -- "PRIMAL_AND_DUAL_FEASIBLE" #eval covEstimationConvex.value -- 14.124098 #eval covEstimationConvex.solution -- ![![0.499903, 0.000000], ![0.000000, 0.499905]] +-- We recover the optimal solution in the original problem. + +def Rₚ_opt := red.backward_map nₚ Nₚ αₚ.float yₚ.float covEstimationConvex.solution + +#eval Rₚ_opt -- !![2.000240, -0.000000; -0.000000, 2.000232] + end CovarianceEstimation diff --git a/CvxLean/Examples/VehicleSpeedScheduling.lean b/CvxLean/Examples/VehicleSpeedScheduling.lean index b66d8a37..6c241d5d 100644 --- a/CvxLean/Examples/VehicleSpeedScheduling.lean +++ b/CvxLean/Examples/VehicleSpeedScheduling.lean @@ -210,15 +210,11 @@ def bₚ : ℝ := 6 @[optimization_param] def cₚ : ℝ := 10 -def p := vehSpeedSchedQuadratic nₚ dₚ τminₚ τmaxₚ sminₚ smaxₚ aₚ bₚ cₚ nₚ_pos dₚ_pos sminₚ_pos +solve vehSpeedSchedQuadratic nₚ dₚ τminₚ τmaxₚ sminₚ smaxₚ aₚ bₚ cₚ nₚ_pos dₚ_pos sminₚ_pos -set_option trace.Meta.debug true - -solve p - -#eval p.status -- "PRIMAL_AND_DUAL_FEASIBLE" -#eval p.value -- 275.042133 -#eval p.solution -- ... +#eval vehSpeedSchedQuadratic.status -- "PRIMAL_AND_DUAL_FEASIBLE" +#eval vehSpeedSchedQuadratic.value -- 275.042133 +#eval vehSpeedSchedQuadratic.solution -- ... -- NOTE: F is not really used here, but it is a parameter of the equivalence, so we must give it a -- value. @@ -230,7 +226,7 @@ def eqv₂.backward_mapₚ := eqv₂.backward_map nₚ dₚ.float τminₚ.float -- Finally, we can obtain the solution to the original problem. -def sₚ_opt := eqv₁.backward_mapₚ (eqv₂.backward_mapₚ p.solution) +def sₚ_opt := eqv₁.backward_mapₚ (eqv₂.backward_mapₚ vehSpeedSchedQuadratic.solution) #eval sₚ_opt -- ![0.955578, 0.955548, 0.955565, 0.955532, 0.955564, 0.955560, 0.912362, 0.960401, 0.912365, From 6d9a372d5cd22ccbb0ac39a99aece51ad139f18d Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 13:08:36 -0500 Subject: [PATCH 22/52] fix: tests passing --- CvxLean/Test/DCP/Label.lean | 11 +---- CvxLean/Test/PreDCP/AlmostDGP.lean | 69 +++++++++++++--------------- CvxLean/Test/PreDCP/DGP.lean | 25 ++++------ CvxLean/Test/PreDCP/DQCP.lean | 5 +- CvxLean/Test/PreDCP/MainExample.lean | 3 -- CvxLean/Test/PreDCP/Stanford.lean | 11 ++--- 6 files changed, 48 insertions(+), 76 deletions(-) diff --git a/CvxLean/Test/DCP/Label.lean b/CvxLean/Test/DCP/Label.lean index ab6425bd..d7f2a4d3 100644 --- a/CvxLean/Test/DCP/Label.lean +++ b/CvxLean/Test/DCP/Label.lean @@ -2,19 +2,10 @@ import CvxLean.Syntax.Label def testLabel := {** 0 ** x **} -/- -def testLabel : Nat := -0 --/ #print testLabel set_option pp.CvxLean.labels true -/- -def testLabel : Nat := -{** 0 |x**} --/ #print testLabel - -#check {** Nat ** x **} × {** Nat ** y **} × {** Nat ** z**} \ No newline at end of file +#check {** Nat ** x **} × {** Nat ** y **} × {** Nat ** z**} diff --git a/CvxLean/Test/PreDCP/AlmostDGP.lean b/CvxLean/Test/PreDCP/AlmostDGP.lean index 20939590..0c9c1a17 100644 --- a/CvxLean/Test/PreDCP/AlmostDGP.lean +++ b/CvxLean/Test/PreDCP/AlmostDGP.lean @@ -11,7 +11,7 @@ noncomputable section open CvxLean Minimization Real -/- This problem is not DGP because of -10.123 -/ +/- This problem is not DGP because of `-10.123`. -/ section AlmostDGP1 def agp1 := @@ -26,7 +26,6 @@ time_cmd reduction reda1/dcpa1 : agp1 := by pre_dcp #print dcpa1 --- def dcpa1 : Minimization ℝ ℝ := -- optimization (x : ℝ) -- minimize x -- subject to @@ -36,7 +35,7 @@ solve dcpa1 end AlmostDGP1 -/- This problem is not DGP because of -5.382. -/ +/- This problem is not DGP because of `-5.382`. -/ section AlmostDGP2 def agp2 := @@ -47,14 +46,12 @@ def agp2 := h2 : 0 < y h3 : x * y - 5.382 ≤ 0 -set_option trace.Meta.debug true time_cmd reduction reda2/dcpa2 : agp2 := by change_of_variables! (u) (x ↦ Real.exp u) change_of_variables! (v) (y ↦ Real.exp v) pre_dcp #print dcpa2 --- def dcpa2 : Minimization (ℝ × ℝ) ℝ := -- optimization (x : ℝ) (y : ℝ) -- minimize x -- subject to @@ -64,7 +61,7 @@ solve dcpa2 end AlmostDGP2 -/- This problem is not DGP because -6 * y / z is not a positive monomial. -/ +/- This problem is not DGP because `-6 * y / z` is not a positive monomial. -/ section AlmostDGP3 def agp3 := @@ -83,10 +80,9 @@ time_cmd reduction reda3/dcpa3 : agp3 := by change_of_variables! (u) (x ↦ Real.exp u) change_of_variables! (v) (y ↦ Real.exp v) change_of_variables! (w) (z ↦ Real.exp w) - pre_dcp -- TODO: Check this. + pre_dcp #print dcpa3 --- def dcpa3 : Minimization (ℝ × ℝ × ℝ) ℝ := -- optimization (x : ℝ) (y : ℝ) (z : ℝ) -- minimize exp y + (exp x + exp z) -- subject to @@ -99,7 +95,7 @@ solve dcpa3 end AlmostDGP3 -/- This problem is not DGP because -x and -y are not positive monomials. -/ +/- This problem is not DGP because `-x` and `-y` are not positive monomials. -/ section AlmostDGP4 def agp4 := @@ -117,7 +113,6 @@ time_cmd reduction reda4/dcpa4 : agp4 := by pre_dcp #print dcpa4 --- def dcpa4 : Minimization (ℝ × ℝ) ℝ := -- optimization (x : ℝ) (y : ℝ) -- minimize -(x + y) -- subject to @@ -127,39 +122,41 @@ solve dcpa4 end AlmostDGP4 --- /- This problem is not convex. -/ --- section AlmostDGP5 +/- This problem is not convex. -/ +section AlmostDGP5 --- def agp5 := --- optimization (x y : ℝ) --- minimize (x * y) --- subject to --- h1 : 0 < x --- h2 : 0 < y --- h3 : x * y ≤ 2 + x - y +def agp5 := + optimization (x y : ℝ) + minimize (x * y) + subject to + h1 : 0 < x + h2 : 0 < y + h3 : x * y ≤ 2 + x - y --- reduction reda5/dcpa5 : agp5 := by --- map_exp --- try { pre_dcp } -- Should fail. +reduction reda5/dcpa5 : agp5 := by + change_of_variables! (u) (x ↦ Real.exp u) + change_of_variables! (v) (y ↦ Real.exp v) + fail_if_success pre_dcp --- end AlmostDGP5 +end AlmostDGP5 --- /- This problem is not convex. -/ --- section AlmostDGP6 +/- This problem is not convex. -/ +section AlmostDGP6 --- def agp6 := --- optimization (x y : ℝ) --- minimize (x * y) --- subject to --- h1 : 0 < x --- h2 : 0 < y --- h3 : sqrt (x * y - y) ≤ 1 +def agp6 := + optimization (x y : ℝ) + minimize (x * y) + subject to + h1 : 0 < x + h2 : 0 < y + h3 : sqrt (x * y - y) ≤ 1 --- reduction reda6/dcpa6 : agp6 := by --- map_exp --- try { pre_dcp } -- Should fail. +reduction reda6/dcpa6 : agp6 := by + change_of_variables! (u) (x ↦ Real.exp u) + change_of_variables! (v) (y ↦ Real.exp v) + fail_if_success pre_dcp --- end AlmostDGP6 +end AlmostDGP6 end diff --git a/CvxLean/Test/PreDCP/DGP.lean b/CvxLean/Test/PreDCP/DGP.lean index 6074213c..4363dce8 100644 --- a/CvxLean/Test/PreDCP/DGP.lean +++ b/CvxLean/Test/PreDCP/DGP.lean @@ -25,7 +25,6 @@ time_cmd reduction red1/dcp1 : gp1 := by pre_dcp #print dcp1 --- def dcp1 : Minimization ℝ ℝ := -- optimization (x : ℝ) -- minimize x -- subject to @@ -51,7 +50,6 @@ time_cmd reduction red2/dcp2 : gp2 := by pre_dcp #print dcp2 --- def dcp2 : Minimization (ℝ × ℝ) ℝ := -- optimization (x : ℝ) (y : ℝ) -- minimize x -- subject to @@ -77,7 +75,6 @@ time_cmd reduction red3/dcp3 : gp3 := by pre_dcp #print dcp3 --- def dcp3 : Minimization (ℝ × ℝ) ℝ := -- optimization (x : ℝ) (y : ℝ) -- minimize x -- subject to @@ -110,7 +107,6 @@ time_cmd reduction red4/dcp4 : gp4 := by solve dcp4 #print dcp4 --- def dcp4 : Minimization (ℝ × ℝ × ℝ) ℝ := -- optimization (x : ℝ) (y : ℝ) (z : ℝ) -- minimize x - y -- subject to @@ -144,7 +140,6 @@ time_cmd reduction red5/dcp5 : gp5 := by pre_dcp #print dcp5 --- def dcp5 : Minimization (ℝ × ℝ × ℝ) ℝ := -- optimization (x : ℝ) (y : ℝ) (z : ℝ) -- minimize -(x - y) -- subject to @@ -183,7 +178,6 @@ time_cmd reduction red6/dcp6 : gp6 := by pre_dcp #print dcp6 --- def dcp6 : Minimization (ℝ × ℝ × ℝ) ℝ := -- optimization (h : ℝ) (w : ℝ) (d : ℝ) -- minimize -(h + (d + w)) -- subject to @@ -201,8 +195,6 @@ end GP6 /- In https://web.stanford.edu/~boyd/papers/pdf/gp_tutorial.pdf section 2.2. -/ section GP7 --- NOTE: We don't have the power atom yet. --- TODO: add atoms for ^(1/2) and ^(-1/2). -- objFun : (x ^ (-1)) * y ^ (-1 / 2) * z ^ (-1) + 2.3 * x * z + 4 * x * y * z -- h4 : (1 / 3) * x ^ (-2) * y ^ (-2) + (4 / 3) * y ^ (1 / 2) * z ^ (-1) ≤ 1 def gp7 := @@ -216,7 +208,7 @@ def gp7 := h5 : x + 2 * y + 3 * z ≤ 1 h6 : (1 / 2) * x * y = 1 -set_option maxHeartbeats 1000000 +set_option maxHeartbeats 1000000 in time_cmd reduction red7/dcp7 : gp7 := by change_of_variables! (u) (x ↦ exp u) change_of_variables! (v) (y ↦ exp v) @@ -224,13 +216,12 @@ time_cmd reduction red7/dcp7 : gp7 := by pre_dcp #print dcp7 --- def dcp7 : Minimization (ℝ × ℝ × ℝ) ℝ := --- optimization (x : ℝ) (y : ℝ) (z : ℝ) --- minimize exp (y * -(1 / 2) - z - x) + (23 / 10 * exp (x + z) + 4 * exp (x + (y + z))) +-- optimization (u : ℝ) (v : ℝ) (w : ℝ) +-- minimize rexp (-(w + (u + v * (1 / 2)))) + (23 / 10 * rexp (u + w) + 4 * rexp (u + (v + w))) -- subject to --- h4 : exp (-2 * (x + y)) / 3 ≤ 1 - 4 / 3 * exp (y * (1 / 2) - z) --- h5 : exp z * 3 ≤ 1 - exp x - exp y * 2 --- h6 : x + (y - log 2) = 0 +-- h4 : rexp (-(2 * (u + v))) + rexp (v * (1 / 2) - w) * 4 ≤ 3 +-- h5 : rexp v * 2 ≤ 1 - rexp u - rexp w * 3 +-- h6 : u + (v + log (1 / 2)) = 0 solve dcp7 @@ -248,8 +239,8 @@ def gp8 := h2 : 0 < w h3 : 0 < A h4 : 0 < r - h5 : 10 * sqrt (w ^ (2 : ℝ) + h ^ (2 : ℝ)) / 2 * h ≤ 0.5 * A - h6 : 20 * sqrt (w ^ (2 : ℝ) + h ^ (2 : ℝ)) / 2 * w ≤ 0.5 * A + h5 : 10 * sqrt (w ^ (2 : ℝ) + h ^ (2 : ℝ)) / (2 * h) ≤ 0.5 * A + h6 : 20 * sqrt (w ^ (2 : ℝ) + h ^ (2 : ℝ)) / (2 * w) ≤ 0.5 * A h7 : 1 ≤ h h8 : h ≤ 100 h9 : 1 ≤ w diff --git a/CvxLean/Test/PreDCP/DQCP.lean b/CvxLean/Test/PreDCP/DQCP.lean index 18e5d4b4..3baedd37 100644 --- a/CvxLean/Test/PreDCP/DQCP.lean +++ b/CvxLean/Test/PreDCP/DQCP.lean @@ -12,7 +12,6 @@ noncomputable section open CvxLean Minimization Real -/- TODO: where does this example come from? -/ section QCP1 def qcp1 := @@ -28,7 +27,6 @@ time_cmd reduction redqcp1/dqcp1 : qcp1 := by pre_dcp #print dqcp1 --- def dqcp1 : Minimization (ℝ × ℝ) ℝ := -- optimization (x : ℝ) (y : ℝ) -- minimize y -- subject to @@ -43,7 +41,7 @@ solve dqcp1 end QCP1 -/- -/ +/- https://www.cvxpy.org/examples/dqcp/hypersonic_shape_design.html -/ section QCP2 def hypersonicShapeDesign (a b : ℝ) := @@ -60,7 +58,6 @@ time_cmd equivalence redqcp2/dqcp2 : hypersonicShapeDesign 0.05 0.65 := by solve dqcp2 #print dqcp2.reduced --- def dqcp2.reduced : Minimization (ℝ × ℝ × ℝ × ℝ × ℝ × ℝ × ℝ) ℝ := -- optimization (Δx : ℝ) (t₀.0 : ℝ) (t₁.1 : ℝ) (t.2 : ℝ) (y'.3 : ℝ) (t.4 : ℝ) (t.5 : ℝ) -- minimize t₀.0 - 1 -- subject to diff --git a/CvxLean/Test/PreDCP/MainExample.lean b/CvxLean/Test/PreDCP/MainExample.lean index 477572e4..c864ccf9 100644 --- a/CvxLean/Test/PreDCP/MainExample.lean +++ b/CvxLean/Test/PreDCP/MainExample.lean @@ -19,9 +19,7 @@ def p := time_cmd equivalence eq/q : p := by pre_dcp - #print q --- def q : Minimization ℝ ℝ := -- optimization (x : ℝ) -- minimize x -- subject to @@ -31,7 +29,6 @@ time_cmd equivalence eq/q : p := by solve q #print q.reduced --- def q.reduced : Minimization (ℝ × ℝ × ℝ) ℝ := -- optimization (x : ℝ) (t.0 : ℝ) (t.1 : ℝ) -- minimize x -- subject to diff --git a/CvxLean/Test/PreDCP/Stanford.lean b/CvxLean/Test/PreDCP/Stanford.lean index 6b4ab408..a0bd6859 100644 --- a/CvxLean/Test/PreDCP/Stanford.lean +++ b/CvxLean/Test/PreDCP/Stanford.lean @@ -16,23 +16,22 @@ open CvxLean Minimization Real section E367 def e367 := - optimization (x1 x2 x3 x4 x5 x6 : ℝ) - minimize (-(sqrt x1 + sqrt x2 + sqrt x3 + sqrt x4 + sqrt x5 + sqrt x6) ^ (2 : ℝ)) + optimization (x1 x2 x3 x4: ℝ) + minimize (-(sqrt x1 + sqrt x2 + sqrt x3 + sqrt x4) ^ (2 : ℝ)) subject to h1 : 0.001 ≤ x1 h2 : 0.001 ≤ x2 h3 : 0.001 ≤ x3 h4 : 0.001 ≤ x4 - h5 : 0.001 ≤ x5 - h6 : 0.001 ≤ x6 -set_option maxHeartbeats 100000000 +set_option maxHeartbeats 1000000 in time_cmd reduction red367/dcp367 : e367 := by pre_dcp - dcp #print dcp367 +solve dcp367 + end E367 end From a413785784996bb60a3847f49217a594c7cee47f Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 13:59:41 -0500 Subject: [PATCH 23/52] feat: do not `norm_num` too much --- CvxLean/Tactic/Arith/NormNumVariants.lean | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/CvxLean/Tactic/Arith/NormNumVariants.lean b/CvxLean/Tactic/Arith/NormNumVariants.lean index 4913dbec..8b3e902e 100644 --- a/CvxLean/Tactic/Arith/NormNumVariants.lean +++ b/CvxLean/Tactic/Arith/NormNumVariants.lean @@ -7,18 +7,11 @@ namespace CvxLean open Lean Elab Meta Tactic /-- Variant of `norm_num` used to get rid of the `OfScientific`s. -/ -def normNumCleanUp (useSimp : Bool) : TacticM Unit := - Mathlib.Meta.NormNum.elabNormNum mkNullNode mkNullNode (useSimp := useSimp) - -syntax (name := norm_num_clean_up) "norm_num_clean_up" : tactic - -@[tactic norm_num_clean_up] -def evalNormNumCleanUp : Tactic := fun stx => match stx with - | `(tactic| norm_num_clean_up) => do - normNumCleanUp (useSimp := false) - saveTacticInfoForToken stx - | _ => throwUnsupportedSyntax +syntax (name := normNumCleanUp) "norm_num_clean_up" : tactic +macro_rules + | `(tactic| norm_num_clean_up) => + `(tactic| repeat (conv in OfScientific.ofScientific _ _ _ => norm_num1)) /-- Variant of `norm_num` that simplifies powers. -/ syntax "norm_num_simp_pow" : tactic From 1f24e96fb3eb08269873df9690b6aa5092c1571e Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 13:59:48 -0500 Subject: [PATCH 24/52] feat: more custom errors --- CvxLean/Meta/Util/Error.lean | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CvxLean/Meta/Util/Error.lean b/CvxLean/Meta/Util/Error.lean index 96261cb9..76836222 100644 --- a/CvxLean/Meta/Util/Error.lean +++ b/CvxLean/Meta/Util/Error.lean @@ -4,6 +4,18 @@ import Lean Custom error messages. -/ +syntax "throwRwConstrError" (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwRwConstrError $msg:interpolatedStr) => `(throwError ("`rw_constr` error: " ++ (m! $msg))) + | `(throwRwConstrError $msg:term) => `(throwError ("`rw_constr` error: " ++ $msg)) + +syntax "throwRwObjError" (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwRwObjError $msg:interpolatedStr) => `(throwError ("`rw_obj` error: " ++ (m! $msg))) + | `(throwRwObjError $msg:term) => `(throwError ("`rw_obj` error: " ++ $msg)) + syntax "throwPreDCPError " (interpolatedStr(term) <|> term) : term macro_rules From ec17efe0f6b9209a36f6de38067846015ca74411 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 14:03:06 -0500 Subject: [PATCH 25/52] feat: better error messages in `rw_obj`, `rw_constr`, and `pre_dcp` --- CvxLean/Tactic/Basic/RewriteOpt.lean | 33 ++++++++++++++-------------- CvxLean/Tactic/PreDCP/PreDCP.lean | 23 +++++++++---------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/CvxLean/Tactic/Basic/RewriteOpt.lean b/CvxLean/Tactic/Basic/RewriteOpt.lean index f7dc29ed..11cf9154 100644 --- a/CvxLean/Tactic/Basic/RewriteOpt.lean +++ b/CvxLean/Tactic/Basic/RewriteOpt.lean @@ -1,6 +1,8 @@ import CvxLean.Meta.Minimization import CvxLean.Meta.Equivalence import CvxLean.Meta.TacticBuilder +import CvxLean.Meta.Util.Error +import CvxLean.Meta.Util.Debug import CvxLean.Tactic.Basic.ShowVars /-! @@ -31,7 +33,7 @@ def rewriteObjLemma (numConstrs : Nat) : MetaM Name := | 8 => return ``Minimization.Equivalence.rewrite_objFun_8 | 9 => return ``Minimization.Equivalence.rewrite_objFun_9 | 10 => return ``Minimization.Equivalence.rewrite_objFun_10 - | _ => throwError "`rw_obj` error: can only rewrite problems with up to 10 constraints." + | _ => throwRwObjError "can only rewrite problems with up to 10 constraints." /-- -/ def rewriteObjBuilder (shouldEval : Bool) (tacStx : Syntax) (rhs? : Option Expr) : @@ -53,14 +55,15 @@ def rewriteObjBuilder (shouldEval : Bool) (tacStx : Syntax) (rhs? : Option Expr) if tag == `g && rhs?.isSome then g.assign rhs?.get! if !foundGToRw then - throwError "`rw_obj` error: could not find rewrite goal." + throwRwObjError "could not find rewrite goal." let (fvarIds, gAfterIntros) ← gToRw.introN (1 + numConstrs) ([`p] ++ constrTags) if fvarIds.size == 0 then - throwError "`rw_obj` error: could not introduce optimization variables." + throwRwObjError "could not introduce optimization variables." let gAfterShowVars ← showVars gAfterIntros (fvarIds.get! 0) if shouldEval then - if let _ :: _ ← evalTacticAt tacStx gAfterShowVars then - throwError "`rw_objFun` error: could not close all goals." + if let gs@(_ :: _) ← evalTacticAt tacStx gAfterShowVars then + trace[CvxLean.debug] "`rw_obj` could not close {gs} using {tacStx}." + throwRwObjError "could not close all goals." /-- Returns lemma to rewrite constraints at `rwIdx` and the name of the RHS parameter. -/ def rewriteConstrLemma (rwIdx : Nat) (numConstrs : Nat) : MetaM (Name × Name) := @@ -76,7 +79,7 @@ def rewriteConstrLemma (rwIdx : Nat) (numConstrs : Nat) : MetaM (Name × Name) : | 8 => return (``Minimization.Equivalence.rewrite_constraint_8_last, `c8') | 9 => return (``Minimization.Equivalence.rewrite_constraint_9_last, `c9') | 10 => return (``Minimization.Equivalence.rewrite_constraint_10_last, `c10') - | _ => throwError "`rw_constr` error: can only rewrite problems with up to 10 constraints." + | _ => throwRwConstrError "error: can only rewrite problems with up to 10 constraints." else match rwIdx with | 1 => return (``Minimization.Equivalence.rewrite_constraint_1, `c1') @@ -89,7 +92,7 @@ def rewriteConstrLemma (rwIdx : Nat) (numConstrs : Nat) : MetaM (Name × Name) : | 8 => return (``Minimization.Equivalence.rewrite_constraint_8, `c8') | 9 => return (``Minimization.Equivalence.rewrite_constraint_9, `c9') | 10 => return (``Minimization.Equivalence.rewrite_constraint_10, `c10') - | _ => throwError "`rw_constr` error: can only rewrite problems with up to 10 constraints." + | _ => throwRwConstrError "can only rewrite problems with up to 10 constraints." section RIntro @@ -114,7 +117,7 @@ def namesToRintroPat (names : List Name) : MetaM (TSyntax `rcasesPat) := do return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4, $n5, $n6, $n7, $n8, $n9⟩) | [n1, n2, n3, n4, n5, n6, n7, n8, n9, n10] => return ← `(rcasesPat| ⟨$n1, $n2, $n3, $n4, $n5, $n6, $n7, $n8, $n9, $n10⟩) - | _ => throwError "`rw_constr` error: could not apply `rintro`, too many constraints." + | _ => throwRwConstrError "could not apply `rintro`, too many constraints." end RIntro @@ -134,7 +137,7 @@ def rewriteConstrBuilder (shouldEval : Bool) (constrTag : Name) (tacStx : Syntax if constrTags[i]! == constrTag then rwIdx := i + 1 if rwIdx == 0 then - throwError "`rw_constr` error: could not find constraint to rewrite." + throwRwConstrError "could not find constraint to rewrite." let (lemmaName, rhsName) ← rewriteConstrLemma rwIdx numConstrs let gs ← g.apply (mkConst lemmaName) let mut gToRw := g @@ -147,7 +150,7 @@ def rewriteConstrBuilder (shouldEval : Bool) (constrTag : Name) (tacStx : Syntax if tag == rhsName && rhs?.isSome then g.assign rhs?.get! if !foundGToRw then - throwError "`rw_constr` error: could not find rewrite goal." + throwRwConstrError "error: could not find rewrite goal." -- Intros appropriately. let constrTagsBefore := constrTags.take rwIdx.pred let numConstrsBefore := constrTagsBefore.length @@ -156,24 +159,22 @@ def rewriteConstrBuilder (shouldEval : Bool) (constrTag : Name) (tacStx : Syntax let (probFVarId, gAfterIntros) := ← do let (fvarIds, gAfterIntros1) ← gToRw.introN (1 + numConstrsBefore) ([`p] ++ constrTagsBefore) if fvarIds.size == 0 then - throwError "`rw_constr` error: could not introduce optimization variables." + throwRwConstrError "could not introduce optimization variables." if isLast then return (fvarIds[0]!, gAfterIntros1) else let toRIntro ← namesToRintroPat constrTagsAfter - trace[Meta.debug] "toRIntro: {toRIntro} {constrTagsAfter} {constrTagsBefore}" let (gsAfterRIntro, _) ← TermElabM.run <| Std.Tactic.RCases.rintro #[toRIntro] none gAfterIntros1 - trace[Meta.debug] "gsAfterRIntro: {gsAfterRIntro}" if gsAfterRIntro.length != 1 then - throwError "`rw_constr` error: could not introduce optimization variables." + throwRwConstrError "could not introduce optimization variables." return (fvarIds[0]!, gsAfterRIntro[0]!) let gAfterShowVars ← showVars gAfterIntros probFVarId if shouldEval then if let gs@(_ :: _) ← evalTacticAt tacStx gAfterShowVars then - trace[Meta.debug] "`rw_constr` could not close {gs} using {tacStx}" - throwError "`rw_constr` error: could not close all goals." + trace[CvxLean.debug] "`rw_constr` could not close {gs} using {tacStx}." + throwRwConstrError "could not close all goals." end Meta diff --git a/CvxLean/Tactic/PreDCP/PreDCP.lean b/CvxLean/Tactic/PreDCP/PreDCP.lean index 9edbfec5..43a0b053 100644 --- a/CvxLean/Tactic/PreDCP/PreDCP.lean +++ b/CvxLean/Tactic/PreDCP/PreDCP.lean @@ -1,6 +1,8 @@ import CvxLean.Lib.Equivalence import CvxLean.Meta.Equivalence import CvxLean.Meta.TacticBuilder +import CvxLean.Meta.Util.Error +import CvxLean.Meta.Util.Debug import CvxLean.Tactic.PreDCP.RewriteMapExt import CvxLean.Tactic.PreDCP.RewriteMapLibrary import CvxLean.Tactic.PreDCP.Egg.All @@ -37,7 +39,7 @@ def findTactic (atObjFun : Bool) (rewriteName : String) (direction : EggRewriteD return (← `(tactic| (rw [eq_comm]; $tac)), false) else return (← `(tactic| (rw [Iff.comm]; $tac)), false) - | _ => throwError "Unknown rewrite name {rewriteName}({direction})." + | _ => throwPreDCPError "unknown rewrite name {rewriteName}({direction})." /-- Given an egg rewrite and a current goal with all the necessary information about the minimization problem, we find the appropriate rewrite to apply, and output the remaining goals. -/ @@ -49,12 +51,11 @@ def evalStep (step : EggRewrite) (vars params : List Name) (paramsDecls : List L else if let [_, tag] := step.location.splitOn ":" then return tag else - throwError "`pre_dcp` error: Unexpected tag name {step.location}." + throwPreDCPError "unexpected tag name {step.location}." let tagNum := tagsMap.find! step.location let atObjFun := tagNum == 0 - -- Build expexcted expression to generate the right rewrite condition. Again, mapping the - -- objective function is an exception where the expected term is not used. + -- Build expexcted expression to generate the right rewrite condition. let expectedTermStr := step.expectedTerm let mut expectedExpr ← EggString.toExpr vars params expectedTermStr if !atObjFun then @@ -62,7 +63,6 @@ def evalStep (step : EggRewrite) (vars params : List Name) (paramsDecls : List L let fvars := Array.mk <| vars.map (fun v => mkFVar (FVarId.mk v)) let paramsFvars := Array.mk <| params.map (fun v => mkFVar (FVarId.mk v)) let paramsDeclsIds := Array.mk <| paramsDecls.map (fun decl => mkFVar decl.fvarId) - -- TODO: Why do we need this? let D ← instantiateMVars eqvExpr.domainP expectedExpr ← withLocalDeclD `p D fun p => do @@ -84,7 +84,7 @@ def evalStep (step : EggRewrite) (vars params : List Name) (paramsDecls : List L if isMap then -- Maps, e.g., `map_objFun_log` are applied directly to the equivalence goal. if let _ :: _ ← evalTacticAt tacStx g then - throwError "`pre_dcp` error: failed to apply {step.rewriteName}." + throwPreDCPError "failed to apply {step.rewriteName}." else -- Rewrites use the machinery from `Tactic.Basic.RewriteOpt`. if atObjFun then @@ -161,17 +161,18 @@ def preDCPBuilder : EquivalenceBuilder := fun eqvExpr g => g.withContext do for step in steps do let gs ← Tactic.run g <| (evalStep step varsNames paramsNames paramsDecls tagsMap).toTactic if gs.length != 1 then - throwError (s!"`pre_dcp` error: failed to rewrite {step.rewriteName} after evaluating " - ++ s!"step ({gs.length} goals remaining).") + trace[CvxLean.debug] "Remaining goals: {gs}." + throwPreDCPError "failed to rewrite {step.rewriteName} ({gs.length} goals remaining)." else + trace[CvxLean.debug] "Rewrote {step.rewriteName}." g := gs[0]! - dbg_trace s!"Rewrote {step.rewriteName}." let gsFinal ← evalTacticAt (← `(tactic| equivalence_rfl)) g if gsFinal.length != 0 then - throwError "`pre_dcp` error: could not close last goal." + trace[CvxLean.debug] "Remaining goals: {gsFinal}." + throwPreDCPError "could not close last goal." catch e => - throwError "`pre_dcp` error: {e.toMessageData}" + throwPreDCPError "{e.toMessageData}" /-- The `pre_dcp` tactic encodes a given minimization problem, sends it to egg, and reconstructs the proof from egg's output. -/ From 6340942f8e2a4787e2ef714555e773fee3affe80 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 14:03:28 -0500 Subject: [PATCH 26/52] fix: one unit test needed more heartbeats --- CvxLean/Test/PreDCP/Unit.lean | 1 + 1 file changed, 1 insertion(+) diff --git a/CvxLean/Test/PreDCP/Unit.lean b/CvxLean/Test/PreDCP/Unit.lean index 311460c5..f63b52d4 100644 --- a/CvxLean/Test/PreDCP/Unit.lean +++ b/CvxLean/Test/PreDCP/Unit.lean @@ -1191,6 +1191,7 @@ def expSubRevConstr := hx : 0 < x h : exp (2 * x) / exp x ≤ 1 +set_option maxHeartbeats 1000000 in time_cmd equivalence expSubRevConstrRed/expSubRevConstrAuto : expSubRevConstr := by pre_dcp From bc874f58c3391b93c751c1f9f3e7a384dd110c6a Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 14:08:42 -0500 Subject: [PATCH 27/52] doc: case studies --- CvxLean/Examples/CovarianceEstimation.lean | 6 ++++++ CvxLean/Examples/FittingSphere.lean | 6 ++++++ CvxLean/Examples/HypersonicShapeDesign.lean | 6 ++++++ CvxLean/Examples/TrussDesign.lean | 6 ++++++ CvxLean/Examples/VehicleSpeedScheduling.lean | 6 ++++++ 5 files changed, 30 insertions(+) diff --git a/CvxLean/Examples/CovarianceEstimation.lean b/CvxLean/Examples/CovarianceEstimation.lean index dd77b0e1..57019169 100644 --- a/CvxLean/Examples/CovarianceEstimation.lean +++ b/CvxLean/Examples/CovarianceEstimation.lean @@ -1,5 +1,11 @@ import CvxLean +/-! +# Case study: Sparse covariance estimation for Gaussian variables + +See https://www.cvxpy.org/examples/applications/sparse_covariance_est.html. +-/ + namespace CovarianceEstimation open CvxLean Minimization Real BigOperators Matrix diff --git a/CvxLean/Examples/FittingSphere.lean b/CvxLean/Examples/FittingSphere.lean index 8eab3936..a1aae3c3 100644 --- a/CvxLean/Examples/FittingSphere.lean +++ b/CvxLean/Examples/FittingSphere.lean @@ -1,5 +1,11 @@ import CvxLean +/-! +# Case study: Fitting a sphere to data + +See exercise 8.16 in https://github.com/cvxgrp/cvxbook_additional_exercises. +-/ + noncomputable section open CvxLean Minimization Real BigOperators Matrix Finset diff --git a/CvxLean/Examples/HypersonicShapeDesign.lean b/CvxLean/Examples/HypersonicShapeDesign.lean index 52d0f378..5b11bd3c 100644 --- a/CvxLean/Examples/HypersonicShapeDesign.lean +++ b/CvxLean/Examples/HypersonicShapeDesign.lean @@ -1,6 +1,12 @@ import CvxLean import CvxLean.Command.Util.TimeCmd +/-! +# Case study: Aerospace Design via Quasiconvex Optimization + +See https://www.cvxpy.org/examples/dqcp/hypersonic_shape_design.html. +-/ + noncomputable section namespace HypersonicShapeDesign diff --git a/CvxLean/Examples/TrussDesign.lean b/CvxLean/Examples/TrussDesign.lean index 380c6588..a04f6693 100644 --- a/CvxLean/Examples/TrussDesign.lean +++ b/CvxLean/Examples/TrussDesign.lean @@ -1,5 +1,11 @@ import CvxLean +/-! +# Case study: Truss design + +See section 6.3 in https://web.stanford.edu/~boyd/papers/pdf/gp_tutorial.pdf. +-/ + noncomputable section namespace TrussDesign diff --git a/CvxLean/Examples/VehicleSpeedScheduling.lean b/CvxLean/Examples/VehicleSpeedScheduling.lean index 6c241d5d..836e0d01 100644 --- a/CvxLean/Examples/VehicleSpeedScheduling.lean +++ b/CvxLean/Examples/VehicleSpeedScheduling.lean @@ -1,5 +1,11 @@ import CvxLean +/-! +# Case study: Optimal vehicle speed scheduling + +See exercise 4.20 in https://github.com/cvxgrp/cvxbook_additional_exercises. +-/ + noncomputable section namespace VehicleSpeedSched From 459b995be4aea866bb97bfb4d0bc2ba1268ed90a Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 14:23:30 -0500 Subject: [PATCH 28/52] doc: `Meta/Util` and some refactor --- .../Command/Solve/Float/RealToFloatCmd.lean | 2 +- CvxLean/Meta/Minimization.lean | 2 +- CvxLean/Meta/Util/Expr.lean | 33 ++++++++++--------- CvxLean/Meta/Util/Meta.lean | 33 +++++++++---------- CvxLean/Meta/Util/SubExpr.lean | 4 +++ CvxLean/Meta/Util/ToExpr.lean | 7 ++-- CvxLean/Tactic/Basic/RemoveConstr.lean | 2 +- CvxLean/Tactic/Basic/RenameVars.lean | 4 +-- CvxLean/Tactic/DCP/Atoms.lean | 2 +- CvxLean/Tactic/DCP/DCP.lean | 2 +- 10 files changed, 48 insertions(+), 43 deletions(-) diff --git a/CvxLean/Command/Solve/Float/RealToFloatCmd.lean b/CvxLean/Command/Solve/Float/RealToFloatCmd.lean index 90cb1f95..b81c9009 100644 --- a/CvxLean/Command/Solve/Float/RealToFloatCmd.lean +++ b/CvxLean/Command/Solve/Float/RealToFloatCmd.lean @@ -20,7 +20,7 @@ translation. namespace CvxLean -open Lean Lean.Elab Lean.Meta Lean.Elab.Command Lean.Elab.Term +open Lean Expr Elab Meta Command Term syntax (name := addRealToFloatCommand) "addRealToFloat" Lean.Parser.Term.funBinder* ":" term ":=" term : command diff --git a/CvxLean/Meta/Minimization.lean b/CvxLean/Meta/Minimization.lean index d86130a9..049bf715 100644 --- a/CvxLean/Meta/Minimization.lean +++ b/CvxLean/Meta/Minimization.lean @@ -138,7 +138,7 @@ partial def mkProjections (domain : Expr) (p : Expr) : m (List (Name × Expr × def withDomainLocalDecls [Inhabited α] (domain : Expr) (p : Expr) (x : Array Expr → Array Expr → m α) : m α := do let pr := (← mkProjections domain p).toArray - withLetDecls' (pr.map fun (n, ty, val) => (n, fun _ => return ty, fun _ => return val)) fun xs => do + withLetDecls (pr.map fun (n, ty, val) => (n, fun _ => return ty, fun _ => return val)) fun xs => do let mut xs := xs -- Use projections instead of variables named "_" : for i in [:pr.size] do diff --git a/CvxLean/Meta/Util/Expr.lean b/CvxLean/Meta/Util/Expr.lean index 29345f92..d8ae5cfa 100644 --- a/CvxLean/Meta/Util/Expr.lean +++ b/CvxLean/Meta/Util/Expr.lean @@ -1,41 +1,44 @@ import Lean - import CvxLean.Lib.Math.Data.Array +/-! +Helper functions to create and manipulate Lean expressions. +-/ + open Lean -/-- TODO Check if `expr` is constant by checking if it contains any free variable -from `vars`. -/ -def Lean.Expr.isConstant (expr : Expr) : Bool := (Lean.collectFVars {} expr).fvarSet.isEmpty +/-- Check if `e` is constant by checking if it contains any free variable. -/ +def Lean.Expr.isConstant (e : Expr) : Bool := (Lean.collectFVars {} e).fvarSet.isEmpty -/-- Check if `expr` is constant by checking if it contains any free variable -from `vars`. -/ -def Lean.Expr.isRelativelyConstant (expr : Expr) (vars : Array FVarId) : Bool := Id.run do - let fvarSet := (Lean.collectFVars {} expr).fvarSet +/-- Check if `e` is constant by checking if it contains any free variable from `vars`. -/ +def Lean.Expr.isRelativelyConstant (e : Expr) (vars : Array FVarId) : Bool := Id.run do + let fvarSet := (Lean.collectFVars {} e).fvarSet for v in vars do if fvarSet.contains v then return false return true +/-- Remove the metadata from an expression. -/ def Lean.Expr.removeMData (e : Expr) : CoreM Expr := do let post (e : Expr) : CoreM TransformStep := do return TransformStep.done e.consumeMData Core.transform e (post := post) -def Lean.Elab.Term.elabTermAndSynthesizeEnsuringType (stx : Syntax) - (expectedType? : Option Expr) (errorMsgHeader? : Option String := none) : - TermElabM Expr := do +/-- Combination of `elabTermAndSynthesize` and `ensureHasType`. -/ +def Lean.Elab.Term.elabTermAndSynthesizeEnsuringType (stx : Syntax) (expectedType? : Option Expr) + (errorMsgHeader? : Option String := none) : TermElabM Expr := do let e ← elabTermAndSynthesize stx expectedType? withRef stx <| ensureHasType expectedType? e errorMsgHeader? -def mkAppBeta (e : Expr) (arg : Expr) := e.bindingBody!.instantiate1 arg +def Lean.Expr.mkAppBeta (e : Expr) (arg : Expr) := + e.bindingBody!.instantiate1 arg -def mkAppNBeta (e : Expr) (args : Array Expr) : Expr := Id.run do +def Lean.Expr.mkAppNBeta (e : Expr) (args : Array Expr) : Expr := Id.run do let mut e := e for _arg in args do e := e.bindingBody! e.instantiateRev args -def convertLambdasToLets (e : Expr) (args : Array Expr) : Expr := Id.run do +def Lean.Expr.convertLambdasToLets (e : Expr) (args : Array Expr) : Expr := Id.run do let mut e := e let mut names := #[] let mut tys := #[] @@ -75,7 +78,7 @@ def Lean.Expr.mkProd (as : Array Expr) : MetaM Expr := (as.take (as.size - 1)) private def Lean.Expr.mkArrayAux (ty : Expr) (as : List Expr) : - MetaM Expr := do + MetaM Expr := do match as with | [] => return mkAppN (mkConst ``Array.empty [levelZero]) #[ty] | e::es => diff --git a/CvxLean/Meta/Util/Meta.lean b/CvxLean/Meta/Util/Meta.lean index 1418f3d9..3d9c0674 100644 --- a/CvxLean/Meta/Util/Meta.lean +++ b/CvxLean/Meta/Util/Meta.lean @@ -1,30 +1,33 @@ import Lean +/-! +Helper `Meta`-related functions. +-/ + namespace Lean.Meta variable [MonadControlT MetaM m] [Monad m] -/-- Open lambda-expression by introducing a new local declaration. Similar to -lambdaTelescope, but for only one variable. -/ +/-- Open lambda-expression by introducing a new local declaration. Similar to `lambdaTelescope`, but +for only one variable. -/ def withLambdaBody (e : Expr) (x : (fvar : Expr) → (body : Expr) → MetaM α) : MetaM α := do match e with - | Expr.lam n ty body _ => + | .lam n ty body _ => withLocalDeclD n ty fun fvar => do let body := body.instantiate1 fvar x fvar body - | _ => throwError "withLambdaBody: expected lambda-expression: {e}" - + | _ => throwError "`withLambdaBody` error: expected lambda-expression, got {e}." +/-- Add local declarations where the type constructor is trivial (non-dependant on the other +declarations). -/ def withLocalDeclsDNondep [Inhabited α] (declInfos : Array (Lean.Name × Expr)) (k : (xs : Array Expr) → m α) : m α := withLocalDeclsD (declInfos.map fun (n, t) => (n, fun _ => pure t)) k /-- Introduce let declarations into the local context. -/ -partial def withLetDecls' -- TODO: SciLean conflict. - [Inhabited α] +partial def withLetDecls [Inhabited α] (declInfos : Array (Name × (Array Expr → m Expr) × (Array Expr → m Expr))) - (k : (xs : Array Expr) → m α) : - m α := + (k : (xs : Array Expr) → m α) : m α := loop #[] where loop [Inhabited α] (acc : Array Expr) : m α := do @@ -34,17 +37,11 @@ where else k acc -def runMetaMAsCoreM (x : MetaM α) : CoreM α := - Prod.fst <$> x.run {} {} - -end Lean.Meta - -namespace Lean.Meta - -open Lean.Elab.Tactic Lean.Elab.Term +open Elab Tactic Term +/-- Run a tactic on a goal in `MetaM`. -/ partial def runTactic (goal : MVarId) (tac : MVarId → TacticM (List MVarId)) : - MetaM (List MVarId) := do + MetaM (List MVarId) := do TermElabM.run' (run goal (do replaceMainGoal <| ← tac <| ← getMainGoal)) end Lean.Meta diff --git a/CvxLean/Meta/Util/SubExpr.lean b/CvxLean/Meta/Util/SubExpr.lean index 4e7eaab4..471b15e1 100644 --- a/CvxLean/Meta/Util/SubExpr.lean +++ b/CvxLean/Meta/Util/SubExpr.lean @@ -1,5 +1,9 @@ import Lean +/-! +Delaborator helper functions. +-/ + namespace Lean.PrettyPrinter.Delaborator namespace SubExpr diff --git a/CvxLean/Meta/Util/ToExpr.lean b/CvxLean/Meta/Util/ToExpr.lean index 046facab..dd06db00 100644 --- a/CvxLean/Meta/Util/ToExpr.lean +++ b/CvxLean/Meta/Util/ToExpr.lean @@ -1,5 +1,9 @@ import Lean +/-! +Some `ToExpr` instances. +-/ + section ToExpr open Lean @@ -25,8 +29,7 @@ instance : ToExpr Float where toTypeExpr := mkConst ``Float instance {n} : ToExpr (Fin n) where - toExpr x := - mkApp (mkConst ``Fin.ofNat) (toExpr x.val) + toExpr x := mkApp (mkConst ``Fin.ofNat) (toExpr x.val) toTypeExpr := mkApp (mkConst ``Fin) (toExpr n) instance : ToExpr (Array Float) := by infer_instance diff --git a/CvxLean/Tactic/Basic/RemoveConstr.lean b/CvxLean/Tactic/Basic/RemoveConstr.lean index 5b9441f8..b9d762b0 100644 --- a/CvxLean/Tactic/Basic/RemoveConstr.lean +++ b/CvxLean/Tactic/Basic/RemoveConstr.lean @@ -4,7 +4,7 @@ import CvxLean.Meta.Util.Expr namespace CvxLean -open Lean Meta Elab Term Tactic +open Lean Expr Meta Elab Term Tactic namespace Meta diff --git a/CvxLean/Tactic/Basic/RenameVars.lean b/CvxLean/Tactic/Basic/RenameVars.lean index f2deb7e9..81ad3988 100644 --- a/CvxLean/Tactic/Basic/RenameVars.lean +++ b/CvxLean/Tactic/Basic/RenameVars.lean @@ -4,7 +4,7 @@ import CvxLean.Meta.Util.Expr namespace CvxLean -open Lean Meta Elab Tactic +open Lean Expr Meta Elab Tactic namespace Meta @@ -12,12 +12,10 @@ namespace Meta def renameVarsBuilder (names : Array Lean.Name) : EquivalenceBuilder := fun eqvExpr g => do let lhsMinExpr ← eqvExpr.toMinimizationExprLHS let vars ← decomposeDomain (← instantiateMVars eqvExpr.domainP) - let fvars := Array.mk <| vars.map (fun ⟨n, _⟩ => mkFVar (FVarId.mk n)) -- Create new domain. let renamedVars ← manipulateVars vars names.data let newDomain := composeDomain renamedVars - let newFVars := Array.mk <| renamedVars.map (fun ⟨n, _⟩ => mkFVar (FVarId.mk n)) -- Create new minimization expression. let newObjFun ← withLocalDeclD `p newDomain fun p => do diff --git a/CvxLean/Tactic/DCP/Atoms.lean b/CvxLean/Tactic/DCP/Atoms.lean index d8a77103..5d4746d8 100644 --- a/CvxLean/Tactic/DCP/Atoms.lean +++ b/CvxLean/Tactic/DCP/Atoms.lean @@ -7,7 +7,7 @@ import CvxLean.Tactic.DCP.DCP namespace CvxLean -open Lean Lean.Meta Lean.Elab Lean.Elab.Command +open Lean Expr Meta Elab Command /-- -/ def mkAndIntro (xs : Array Expr) : MetaM Expr := do diff --git a/CvxLean/Tactic/DCP/DCP.lean b/CvxLean/Tactic/DCP/DCP.lean index 8675bd5a..2c56ff71 100644 --- a/CvxLean/Tactic/DCP/DCP.lean +++ b/CvxLean/Tactic/DCP/DCP.lean @@ -13,7 +13,7 @@ import CvxLean.Tactic.Arith.Arith namespace CvxLean -open Lean Lean.Meta +open Lean Expr Meta namespace DCP From 2eed96a2625246bd0c401bb89aa5fe4d9e7d92b9 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 14:51:41 -0500 Subject: [PATCH 29/52] doc: `Meta` --- CvxLean/Meta/Equivalence.lean | 30 +++++++---- CvxLean/Meta/Minimization.lean | 94 +++++++++++++++++---------------- CvxLean/Meta/Reduction.lean | 31 +++++++---- CvxLean/Meta/Relaxation.lean | 9 ++++ CvxLean/Meta/TacticBuilder.lean | 53 +++++++++++++------ CvxLean/Meta/Util/Error.lean | 15 ++++-- 6 files changed, 145 insertions(+), 87 deletions(-) diff --git a/CvxLean/Meta/Equivalence.lean b/CvxLean/Meta/Equivalence.lean index 01df03c0..7f3d463e 100644 --- a/CvxLean/Meta/Equivalence.lean +++ b/CvxLean/Meta/Equivalence.lean @@ -1,6 +1,16 @@ import CvxLean.Meta.Minimization import CvxLean.Lib.Equivalence +/-! +Infrastructure to work with `Equivalence` types as expressions. We also define some basic tactics +that work on equivalence goals: +* `equivalence_rfl` closes a goal by reflexivity. +* `equivalence_symm` applies symmetry. +* `equivalence_trans` applies transitivity. +* `equivalence_step => ...` allows users to apply one equivalence step in the `equivalence` command + by first applying transitivity as otherwise the goal would be closed immediately. +-/ + namespace CvxLean open Lean Meta @@ -9,30 +19,30 @@ namespace Meta /-- `Equivalence` type components as expressions. -/ structure EquivalenceExpr where - domainP : Expr - domainQ : Expr + domainLHS : Expr + domainRHS : Expr codomain : Expr codomainPreorder : Expr - p : Expr - q : Expr + lhs : Expr + rhs : Expr namespace EquivalenceExpr def toMinimizationExprLHS (eqvExpr : EquivalenceExpr) : MetaM MinimizationExpr := do - MinimizationExpr.fromExpr (← whnf eqvExpr.p) + MinimizationExpr.fromExpr (← whnf eqvExpr.lhs) def toMinimizationExprRHS (eqvExpr : EquivalenceExpr) : MetaM MinimizationExpr := do - MinimizationExpr.fromExpr (← whnf eqvExpr.q) + MinimizationExpr.fromExpr (← whnf eqvExpr.rhs) def toExpr (eqvExpr : EquivalenceExpr) : Expr := - mkApp6 (mkConst ``Minimization.Equivalence) - eqvExpr.domainP eqvExpr.domainQ eqvExpr.codomain eqvExpr.codomainPreorder eqvExpr.p eqvExpr.q + mkApp6 (mkConst ``Minimization.Equivalence) eqvExpr.domainLHS eqvExpr.domainRHS eqvExpr.codomain + eqvExpr.codomainPreorder eqvExpr.lhs eqvExpr.rhs def fromExpr (e : Expr) : MetaM EquivalenceExpr := do match e with | .app (.app (.app (.app (.app (.app (.const ``Minimization.Equivalence _) - domainP) domainQ) codomain) codomainPreorder) p) q => do - return EquivalenceExpr.mk domainP domainQ codomain codomainPreorder p q + domainLHS) domainRHS) codomain) codomainPreorder) lhs) rhs => do + return EquivalenceExpr.mk domainLHS domainRHS codomain codomainPreorder lhs rhs | _ => throwError "Expression not of the form `Minimization.Equivalence ...`." def fromGoal (goal : MVarId) : MetaM EquivalenceExpr := do diff --git a/CvxLean/Meta/Minimization.lean b/CvxLean/Meta/Minimization.lean index 049bf715..04d66a47 100644 --- a/CvxLean/Meta/Minimization.lean +++ b/CvxLean/Meta/Minimization.lean @@ -2,6 +2,11 @@ import CvxLean.Syntax.Label import CvxLean.Meta.Util.Meta import CvxLean.Lib.Minimization +/-! +Infrastructure to work with the `Minimization` and `Solution` types as expressions. Crucially, we +define functions to compose and decompose domans and constraints, taking into account their names. +-/ + namespace CvxLean namespace Meta @@ -65,9 +70,9 @@ def fromGoal (goal : MVarId) : MetaM SolutionExpr := do end SolutionExpr -/-- Replaces projections of an FVar `p` in an expression `e` by the expressions `rs`. - For example, `p.2.2.1` will be replaced by `rs[2]`. If `p` is not fully projected, - e.g. `p.2` when `rs` has more than 2 elements, it is not replaced at all. -/ +/-- Replaces projections of an FVar `p` in an expression `e` by the expressions `rs`. For example, +`p.2.2.1` will be replaced by `rs[2]`. If `p` is not fully projected, e.g., `p.2` when `rs` has more +than 2 elements, it is not replaced at all. -/ def replaceProjections (e : Expr) (p : FVarId) (rs : Array Expr) : MetaM Expr := do let pre (e : Expr) : MetaM TransformStep := do match ← decomposeProj e p rs.data with @@ -77,18 +82,19 @@ def replaceProjections (e : Expr) (p : FVarId) (rs : Array Expr) : MetaM Expr := where decomposeProj (e : Expr) (p : FVarId) (rs : List Expr) (first : Option Bool := none) : MetaM (Option Expr) := do - /- `first` tells us whether the outermost projection was `.1` (`some true`) or - `.2` (`some false`). If this is not a recursive call, `first` is `none`. -/ - -- TODO: This won't recognize "p.2 i". + -- `first` tells us whether the outermost projection was `.1` (`some true`) or + -- `.2` (`some false`). If this is not a recursive call, `first` is `none`. -/ + -- TODO: This won't recognize "p.2 i" which means that we cannot handle vector or matrix + -- applications or functions that depend on these such as sums. match first, e, rs with - | _, Expr.fvar fVarId, [r] => - if fVarId == p then return r else return none - | some true, Expr.fvar fVarId, r :: _ :: _ => - if fVarId == p then return r else return none - | none, Expr.app (Expr.app (Expr.app (Expr.const ``Prod.fst _) _) _) e, r :: rs => do - return ← decomposeProj e p (r :: rs) (first := true) - | _, Expr.app (Expr.app (Expr.app (Expr.const ``Prod.snd _) _) _) e, _ :: rs => do - return ← decomposeProj e p rs (first := first == some true) + | _, .fvar fVarId, [r] => + if fVarId == p then return r else return none + | some true, .fvar fVarId, r :: _ :: _ => + if fVarId == p then return r else return none + | none, .app (.app (.app (.const ``Prod.fst _) _) _) e, r :: rs => do + decomposeProj e p (r :: rs) (first := true) + | _, .app (.app (.app (.const ``Prod.snd _) _) _) e, _ :: rs => do + decomposeProj e p rs (first := first == some true) | _, _, [] => return none | _, _, _ :: _ => return none @@ -96,51 +102,42 @@ where of their name and type. -/ def decomposeDomain (domain : Expr) : m (List (Name × Expr)) := do match domain with - | Expr.app (Expr.app (Expr.const `Prod _) ty1) ty2 => do - return (← decomposeLabel ty1) :: (← decomposeDomain ty2) + | .app (.app (.const `Prod _) ty1) ty2 => do + return (← decomposeLabel ty1) :: (← decomposeDomain ty2) | _ => do return [← decomposeLabel domain] /-- Same as `decomposeDomain` but also try to instantiate meta-variables. -/ def decomposeDomainInstantiating (minExpr : MinimizationExpr) : MetaM (List (Name × Expr)) := do decomposeDomain (← instantiateMVars minExpr.domain) -/-- Get a HashSet of variable names in a given domain. -/ -def getVariableNameSet (domain : Expr) : m (HashSet Name) := do - let mut res : HashSet Name := {} - for (name, _) in ← decomposeDomain domain do - res := res.insert name - return res - /-- Determine domain type from a list of variables and their types. -/ def composeDomain (vars : List (Name × Expr)) : Expr := match vars with | [] => mkConst `Unit | [(name, ty)] => mkLabel name ty | (name, ty) :: rest => - mkApp2 (mkConst `Prod [levelZero, levelZero]) - (mkLabel name ty) (composeDomain rest) + mkApp2 (mkConst `Prod [levelZero, levelZero]) (mkLabel name ty) (composeDomain rest) -/-- Determine a list of variables described by a `domain`. - The expression `p` must be a variable of Type `domain`. - Returns a list of variables, with name, type, and definition in terms of `p`. -/ +/-- Determine a list of variables described by a `domain`. The expression `p` must be a variable of +type `domain`. Returns a list of variables, with name, type, and definition in terms of `p`. -/ partial def mkProjections (domain : Expr) (p : Expr) : m (List (Name × Expr × Expr)) := do match domain with - | Expr.app (Expr.app (Expr.const ``Prod lvls) (ty1 : Expr)) (ty2 : Expr) => do - let v ← getLabelName ty1 - let d := mkApp3 (mkConst `Prod.fst lvls) ty1 ty2 p - let r ← mkProjections ty2 (mkApp3 (mkConst `Prod.snd lvls) ty1 ty2 p) - return (v, ty1, d) :: r + | .app (.app (.const ``Prod lvls) (ty1 : Expr)) (ty2 : Expr) => do + let v ← getLabelName ty1 + let d := mkApp3 (mkConst `Prod.fst lvls) ty1 ty2 p + let r ← mkProjections ty2 (mkApp3 (mkConst `Prod.snd lvls) ty1 ty2 p) + return (v, ty1, d) :: r | _ => do return [(← getLabelName domain, domain, p)] -/-- Introduce let declarations into the context, corresponding to the projections of `p`. - The argument `domain` specifies the type of `p`. CvxLeanLabels in the `domain` are used to - determine the names of the new variables. -/ +/-- Introduce let declarations into the context, corresponding to the projections of `p`. The +argument `domain` specifies the type of `p`. CvxLeanLabels in the `domain` are used to determine the +names of the new variables. -/ def withDomainLocalDecls [Inhabited α] (domain : Expr) (p : Expr) (x : Array Expr → Array Expr → m α) : m α := do let pr := (← mkProjections domain p).toArray withLetDecls (pr.map fun (n, ty, val) => (n, fun _ => return ty, fun _ => return val)) fun xs => do let mut xs := xs - -- Use projections instead of variables named "_" : + -- Use projections instead of variables named "_". for i in [:pr.size] do if pr[i]!.1 == `_ then xs := xs.set! i pr[i]!.2.2 @@ -149,7 +146,7 @@ def withDomainLocalDecls [Inhabited α] (domain : Expr) (p : Expr) /-- Decompose an expression into its `And`-connected components. -/ def decomposeAnd (e : Expr) : MetaM (List (Expr)) := do match e with - | Expr.app (Expr.app (Expr.const ``And _) p) q => do + | .app (.app (.const ``And _) p) q => do return p :: (← decomposeAnd q) | _ => return [e] @@ -158,13 +155,6 @@ def decomposeConstraints (e : Expr) : MetaM (List (Name × Expr)) := do (← decomposeAnd e).mapM fun e => do return (← getLabelName e, e) -/-- Get a HashSet of constraint names in a given domain. -/ -def getConstraintNameSet (e : Expr) : MetaM (HashSet Name) := do - let mut res : HashSet Name := {} - for (name, _) in ← decomposeConstraints e do - res := res.insert name - return res - /-- Compose a list of expressions with `And`. -/ def composeAnd : List Expr → Expr | [] => mkConst ``True @@ -182,6 +172,20 @@ def composeAndWithProj : List Expr → (Expr × (Expr → List Expr)) fun e => mkApp3 (mkConst ``And.left) c cs e :: prs (mkApp3 (mkConst ``And.right) c cs e) (res, prs) +/-- Get a HashSet of variable names in a given domain. -/ +def getVariableNameSet (domain : Expr) : m (HashSet Name) := do + let mut res : HashSet Name := {} + for (name, _) in ← decomposeDomain domain do + res := res.insert name + return res + +/-- Get a `HashSet` of constraint names in a given domain. -/ +def getConstraintNameSet (e : Expr) : MetaM (HashSet Name) := do + let mut res : HashSet Name := {} + for (name, _) in ← decomposeConstraints e do + res := res.insert name + return res + /-- Generates a name that is not yet contained in `set`. -/ partial def generateNewName (base : String) (set : HashSet Name) : MetaM Name := do tryNumber 1 set diff --git a/CvxLean/Meta/Reduction.lean b/CvxLean/Meta/Reduction.lean index 143dafee..7430d9a7 100644 --- a/CvxLean/Meta/Reduction.lean +++ b/CvxLean/Meta/Reduction.lean @@ -1,6 +1,15 @@ import CvxLean.Meta.Equivalence import CvxLean.Lib.Reduction +/-! +Infrastructure to work with `Reduction` types as expressions. We also define some basic tactics +that work on reduction goals: +* `reduction_rfl` closes a goal by reflexivity. +* `reduction_trans` applies transitivity. +* `reduction_step => ...` allows users to apply one reduction step in the `reduction` command + by first applying transitivity as otherwise the goal would be closed immediately. +-/ + namespace CvxLean namespace Meta @@ -9,30 +18,30 @@ open Lean Meta /-- `Reduction` type components as expressions. -/ structure ReductionExpr where - domainP : Expr - domainQ : Expr + domainLHS : Expr + domainRHS : Expr codomain : Expr - codomainPreorder : Expr - p : Expr - q : Expr + codomainLHSreorder : Expr + lhs : Expr + rhs : Expr namespace ReductionExpr def toMinimizationExprLHS (redExpr : ReductionExpr) : MetaM MinimizationExpr := - MinimizationExpr.fromExpr redExpr.p + MinimizationExpr.fromExpr redExpr.lhs def toMinimizationExprRHS (redExpr : ReductionExpr) : MetaM MinimizationExpr := - MinimizationExpr.fromExpr redExpr.q + MinimizationExpr.fromExpr redExpr.rhs def toExpr (redExpr : ReductionExpr) : Expr := - mkApp6 (mkConst ``Minimization.Reduction) - redExpr.domainP redExpr.domainQ redExpr.codomain redExpr.codomainPreorder redExpr.p redExpr.q + mkApp6 (mkConst ``Minimization.Reduction) redExpr.domainLHS redExpr.domainRHS redExpr.codomain + redExpr.codomainLHSreorder redExpr.lhs redExpr.rhs def fromExpr (e : Expr) : MetaM ReductionExpr := do match e with | .app (.app (.app (.app (.app (.app (.const ``Minimization.Reduction _) - domainP) domainQ) codomain) codomainPreorder) p) q => do - return ReductionExpr.mk domainP domainQ codomain codomainPreorder p q + domainLHS) domainRHS) codomain) codomainLHSreorder) p) q => do + return ReductionExpr.mk domainLHS domainRHS codomain codomainLHSreorder p q | _ => throwError "Expression not of the form `Minimization.Reduction ...`." def fromGoal (goal : MVarId) : MetaM ReductionExpr := do diff --git a/CvxLean/Meta/Relaxation.lean b/CvxLean/Meta/Relaxation.lean index a124e629..c3c805b9 100644 --- a/CvxLean/Meta/Relaxation.lean +++ b/CvxLean/Meta/Relaxation.lean @@ -1,6 +1,15 @@ import CvxLean.Meta.Equivalence import CvxLean.Lib.Relaxation +/-! +Infrastructure to work with `Relaxation` types as expressions. We also define some basic tactics +that work on relaxation goals: +* `relaxation_rfl` closes a goal by reflexivity. +* `relaxation_trans` applies transitivity. +* `relaxation_step => ...` allows users to apply one relaxation step in the `relaxation` command + by first applying transitivity as otherwise the goal would be closed immediately. +-/ + namespace CvxLean namespace Meta diff --git a/CvxLean/Meta/TacticBuilder.lean b/CvxLean/Meta/TacticBuilder.lean index af3b416f..5e123a67 100644 --- a/CvxLean/Meta/TacticBuilder.lean +++ b/CvxLean/Meta/TacticBuilder.lean @@ -1,6 +1,18 @@ import CvxLean.Meta.Equivalence import CvxLean.Meta.Reduction import CvxLean.Meta.Relaxation +import CvxLean.Meta.Util.Error + +/-! +There is a hierarchy on the possible transformations between two problems. Most crucially, a +tactic that builds an `Equivalence` should also build a `Reduction`. And both equivalence and +reduction-preserving tactics should build backward maps. + +This file avoid unnecessary code duplication by providing a common interface for all +transformation-preserving tactics. When building a tactic, a user must define an +`EquivalenceBuilder`, a `ReductionBuilder` or a `RelaxationBuilder`. These builders can then be +turned into tactics using the `toTactic` method. +-/ namespace CvxLean @@ -8,7 +20,7 @@ namespace Meta open Lean Meta Elab Tactic Term Command Minimization --- TODO: `StrongEquivalence` +-- TODO: `StrongEquivalence`. inductive TransformationGoal | Solution | Equivalence | Reduction | Relaxation @@ -28,8 +40,10 @@ def fromExpr (e : Expr) : MetaM TransformationGoal := do else if e.isAppOf `Minimization.Relaxation then return TransformationGoal.Relaxation else - throwError "Expected a `Solution`, `Equivalence`, `Reduction` or `Relaxation` goal, got {e}." + throwTacticBuilderError + "expected a `Solution`, `Equivalence`, `Reduction` or `Relaxation` goal, got {e}." +/-- Applies appropriate transitivity tactic to the goal. -/ def applyTransitivity (transf : TransformationGoal) (g : MVarId) : TacticM (MVarId × MVarId) := g.withContext do if transf.isTransitive then @@ -40,7 +54,7 @@ def applyTransitivity (transf : TransformationGoal) (g : MVarId) : TacticM (MVar | TransformationGoal.Equivalence => evalTacticAt (← `(tactic| equivalence_trans)) g | _ => pure [] if gsTrans.length != 4 then - throwError "Transitivity failed." + throwTacticBuilderError "transitivity failed." let mut gToChange := gsTrans[0]! gToChange.setTag Name.anonymous let gNext := gsTrans[1]! @@ -51,7 +65,8 @@ def applyTransitivity (transf : TransformationGoal) (g : MVarId) : TacticM (MVar end TransformationGoal -/-- -/ +/-- Given a relaxation goal in the form of a `RelaxationExpr` and the `MVarId` of the current goal, +provide a tactic to close it. -/ def RelaxationBuilder := RelaxationExpr → MVarId → TacticM Unit namespace RelaxationBuilder @@ -66,11 +81,11 @@ def toTactic (builder : RelaxationBuilder) : TacticM Unit := withMainContext do match transf with | TransformationGoal.Solution => - throwError "Relaxation tactic does not apply to `Solution`." + throwTacticBuilderError "relaxation tactic does not apply to `Solution`." | TransformationGoal.Equivalence => do - throwError "Relaxation tactic does not apply to `Equivalence`." + throwTacticBuilderError "relaxation tactic does not apply to `Equivalence`." | TransformationGoal.Reduction => do - throwError "Relaxation tactic does not apply to `Reduction`." + throwTacticBuilderError "relaxation tactic does not apply to `Reduction`." | TransformationGoal.Relaxation => do pure () @@ -84,7 +99,8 @@ def toTactic (builder : RelaxationBuilder) : TacticM Unit := withMainContext do end RelaxationBuilder -/-- -/ +/-- Given a reduction goal in the form of a `ReductionExpr` and the `MVarId` of the current goal, +provide a tactic to close it. -/ def ReductionBuilder := ReductionExpr → MVarId → Tactic namespace ReductionBuilder @@ -105,11 +121,11 @@ def toTactic (builder : ReductionBuilder) : Tactic := fun stx => do gToChange := red gNext := sol else - throwError "Could not apply reduction tactic to `Solution`." + throwTacticBuilderError "could not apply reduction tactic to `Solution`." | TransformationGoal.Equivalence => do - throwError "Expected `Reduction`, found `Equivalence`." + throwTacticBuilderError "expected `Reduction`, found `Equivalence`." | TransformationGoal.Relaxation => do - throwError "Expected `Reduction`, found `Relaxation`." + throwTacticBuilderError "expected `Reduction`, found `Relaxation`." | TransformationGoal.Reduction => do pure () @@ -123,7 +139,8 @@ def toTactic (builder : ReductionBuilder) : Tactic := fun stx => do end ReductionBuilder -/-- -/ +/-- Given an equivalence goal in the form of a `EquivalenceExpr` and the `MVarId` of the current +goal, provide a tactic to close it. -/ def EquivalenceBuilder := EquivalenceExpr → MVarId → TacticM Unit namespace EquivalenceBuilder @@ -144,16 +161,16 @@ def toTactic (builder : EquivalenceBuilder) : TacticM Unit := withMainContext do gToChange := eqv gNext := sol else - throwError "Could not apply equivalence tactic to `Solution`." + throwTacticBuilderError "could not apply equivalence tactic to `Solution`." | TransformationGoal.Equivalence => do pure () | TransformationGoal.Reduction => do if let [eqv] ← gToChange.apply (mkConst ``Minimization.Reduction.ofEquivalence) then gToChange := eqv else - throwError "Could not apply equivalence tactic to `Reduction`." + throwTacticBuilderError "could not apply equivalence tactic to `Reduction`." | TransformationGoal.Relaxation => do - throwError "Equivalence tactic does not apply to `Relaxation`." + throwTacticBuilderError "equivalence tactic does not apply to `Relaxation`." -- Run builder. let eqvExpr ← EquivalenceExpr.fromExpr (← gToChange.getType) @@ -231,7 +248,7 @@ def elabTransformationProof (transf : TransformationGoal) (lhs : Expr) (rhsName let proof ← elabTerm stx (some transfTy) let some mvarDecl ← getSyntheticMVarDecl? proof.mvarId! | - throwError "SyntheticMVarDecl not found." + throwTacticBuilderError "`SyntheticMVarDecl` not found." modify fun s => { s with syntheticMVars := {} } @@ -239,7 +256,9 @@ def elabTransformationProof (transf : TransformationGoal) (lhs : Expr) (rhsName | SyntheticMVarKind.tactic tacticCode savedContext => withSavedContext savedContext do runTransformationTactic transf proof.mvarId! tacticCode - | _ => throwError "Expected SyntheticMVarDecl of kind `tactic`, got {mvarDecl.kind}" + | _ => + throwTacticBuilderError + "expected `SyntheticMVarDecl` of kind `tactic`, got {mvarDecl.kind}" return (rhs, ← instantiateMVars proof) finally diff --git a/CvxLean/Meta/Util/Error.lean b/CvxLean/Meta/Util/Error.lean index 76836222..ea50e67b 100644 --- a/CvxLean/Meta/Util/Error.lean +++ b/CvxLean/Meta/Util/Error.lean @@ -4,6 +4,13 @@ import Lean Custom error messages. -/ +syntax "throwTacticBuilderError" (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwTacticBuilderError $msg:interpolatedStr) => + `(throwError ("Tactic builder error: " ++ (m! $msg))) + | `(throwTacticBuilderError $msg:term) => `(throwError ("Tactic builder error: " ++ $msg)) + syntax "throwRwConstrError" (interpolatedStr(term) <|> term) : term macro_rules @@ -38,7 +45,7 @@ syntax "throwRealToFloatError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwRealToFloatError $msg:interpolatedStr) => - `(throwError ("`real-to-float` error: " ++ (m! $msg))) + `(throwError ("`real-to-float` error: " ++ (m! $msg))) | `(throwRealToFloatError $msg:term) => `(throwError ("`real-to-float` error: " ++ $msg)) syntax "throwSolveError " (interpolatedStr(term) <|> term) : term @@ -52,7 +59,7 @@ syntax "throwEquivalenceError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwEquivalenceError $msg:interpolatedStr) => - `(throwError ("`equivalence` error: " ++ (m! $msg))) + `(throwError ("`equivalence` error: " ++ (m! $msg))) | `(throwEquivalenceError $msg:term) => `(throwError ("`equivalence` error: " ++ $msg)) /-- Errors in the `reduction` command. -/ @@ -60,7 +67,7 @@ syntax "throwReductionError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwReductionError $msg:interpolatedStr) => - `(throwError ("`reduction` error: " ++ (m! $msg))) + `(throwError ("`reduction` error: " ++ (m! $msg))) | `(throwReductionError $msg:term) => `(throwError ("`reduction` error: " ++ $msg)) /-- Errors in the `relaxation` command. -/ @@ -68,5 +75,5 @@ syntax "throwRelaxationError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwRelaxationError $msg:interpolatedStr) => - `(throwError ("`relaxation` error: " ++ (m! $msg))) + `(throwError ("`relaxation` error: " ++ (m! $msg))) | `(throwRelaxationError $msg:term) => `(throwError ("`relaxation` error: " ++ $msg)) From 242fa180a5a3bc8f7b7bbdf59f2a23e1e4783a3a Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 14:56:18 -0500 Subject: [PATCH 30/52] doc: some `Lib` --- CvxLean/Lib/Equivalence.lean | 1 + CvxLean/Lib/Reduction.lean | 9 ++------- CvxLean/Lib/Relaxation.lean | 8 +++++--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CvxLean/Lib/Equivalence.lean b/CvxLean/Lib/Equivalence.lean index 986efeab..1dcc4069 100644 --- a/CvxLean/Lib/Equivalence.lean +++ b/CvxLean/Lib/Equivalence.lean @@ -17,6 +17,7 @@ namespace Minimization variable {D E F R : Type} [Preorder R] variable (p : Minimization D R) (q : Minimization E R) (r : Minimization F R) + /- Let `p := ⟨f, cs⟩`, `q := ⟨g, ds⟩` and `r := ⟨h, es⟩`. -/ /-- Regular notion of equivalence between optimization problems. We require maps `(φ, ψ)` between diff --git a/CvxLean/Lib/Reduction.lean b/CvxLean/Lib/Reduction.lean index 1d8109a9..31ae0fb6 100644 --- a/CvxLean/Lib/Reduction.lean +++ b/CvxLean/Lib/Reduction.lean @@ -4,7 +4,8 @@ import CvxLean.Lib.Equivalence # Reduction of optimization problems We define the notion of reduction. It is a reflexive and transitive relation and it induces a -backward map between solutions. An equivalence gives a reduction. +backward map between solutions. The idea is that solving the reduced problem is "as hard" as solving +the original problem. An equivalence gives a reduction. ## References @@ -232,12 +233,6 @@ def rewrite_constraint_10_last end Rewrites -section Other - --- TODO: from equiv. - -end Other - end Reduction namespace Equivalence diff --git a/CvxLean/Lib/Relaxation.lean b/CvxLean/Lib/Relaxation.lean index 26f564c6..ee17988f 100644 --- a/CvxLean/Lib/Relaxation.lean +++ b/CvxLean/Lib/Relaxation.lean @@ -3,7 +3,9 @@ import CvxLean.Lib.Equivalence /-! # Relaxation of optimization problems -We define the notion of relaxation. +We define the notion of relaxation. It is a reflexive and transitive relation and it induces a +forward map between solutions. The idea is that solving the original problem is "as hard" as solving +the relaxed problem. A strong equivalence gives a relaxation. ## References @@ -38,9 +40,9 @@ def trans (Rx₁ : p ≽' q) (Rx₂ : q ≽' r) : p ≽' r := { phi := Rx₂.phi ∘ Rx₁.phi, phi_feasibility := fun x h => Rx₂.phi_feasibility (Rx₁.phi x) (Rx₁.phi_feasibility x h), phi_optimality := fun x hx => - -- h(φ₂(φ₁(x))) ≤ g(φ₁(x)) + -- `h(φ₂(φ₁(x))) ≤ g(φ₁(x))` have h₁ := Rx₂.phi_optimality (Rx₁.phi x) (Rx₁.phi_feasibility x hx) - -- g(φ₁(x)) ≤ f(x) + -- `g(φ₁(x)) ≤ f(x)` have h₂ := Rx₁.phi_optimality x hx le_trans h₁ h₂ } From 43b7bb5ca3da555eb29324ff71e9fd1214c363f5 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 15:14:05 -0500 Subject: [PATCH 31/52] doc: `Lib` --- CvxLean/Lib/Cones/ExpCone.lean | 3 +- CvxLean/Lib/Cones/SOCone.lean | 19 ++--- CvxLean/Lib/Cones/ZeroCone.lean | 2 +- CvxLean/Lib/Math/CovarianceEstimation.lean | 2 +- CvxLean/Lib/Math/Data/Fin.lean | 4 + CvxLean/Lib/Math/Data/Matrix.lean | 5 ++ CvxLean/Lib/Math/Data/Real.lean | 5 ++ CvxLean/Lib/Math/Data/Vec.lean | 5 ++ .../Lib/Math/LinearAlgebra/Eigenspace.lean | 8 +- .../Lib/Math/LinearAlgebra/Matrix/PosDef.lean | 47 +++++----- .../Math/LinearAlgebra/Matrix/Spectrum.lean | 21 ++--- .../Math/LinearAlgebra/Matrix/Triangular.lean | 25 +++--- CvxLean/Lib/Math/LogDet.lean | 4 +- CvxLean/Lib/Math/LogSumExp.lean | 4 + CvxLean/Lib/Math/SchurComplement.lean | 85 +++++++++---------- CvxLean/Lib/Math/Subadditivity.lean | 5 +- 16 files changed, 116 insertions(+), 128 deletions(-) diff --git a/CvxLean/Lib/Cones/ExpCone.lean b/CvxLean/Lib/Cones/ExpCone.lean index 06bc3557..d14fd6b3 100644 --- a/CvxLean/Lib/Cones/ExpCone.lean +++ b/CvxLean/Lib/Cones/ExpCone.lean @@ -1,8 +1,7 @@ import Mathlib.Data.Complex.Exponential /-! -We follow the MOSEK modeling cookbook: -https://docs.mosek.com/modeling-cookbook/expo.html +We follow the MOSEK modeling cookbook: https://docs.mosek.com/modeling-cookbook/expo.html -/ namespace Real diff --git a/CvxLean/Lib/Cones/SOCone.lean b/CvxLean/Lib/Cones/SOCone.lean index 013003e9..592faffd 100644 --- a/CvxLean/Lib/Cones/SOCone.lean +++ b/CvxLean/Lib/Cones/SOCone.lean @@ -5,8 +5,7 @@ import CvxLean.Lib.Math.Data.Real import CvxLean.Lib.Math.Data.Vec /-! -We follow the MOSEK modeling cookbook: -https://docs.mosek.com/modeling-cookbook/cqo.html +We follow the MOSEK modeling cookbook: https://docs.mosek.com/modeling-cookbook/cqo.html -/ namespace Real @@ -39,9 +38,8 @@ noncomputable section ConeConversion def rotateSoCone {n : ℕ} (t : ℝ) (x : Fin n.succ → ℝ) : ℝ × ℝ × (Fin n → ℝ) := ((t + x 0) / sqrt 2, (t - x 0) / sqrt 2, fun i => x i.succ) -lemma rotateSoCone_rotatedSoCone {n : ℕ} {t : ℝ} {x : Fin n.succ → ℝ} - (h : soCone t x) : - let (v, w, x) := rotateSoCone t x; rotatedSoCone v w x := by +lemma rotateSoCone_rotatedSoCone {n : ℕ} {t : ℝ} {x : Fin n.succ → ℝ} (h : soCone t x) : + let (v, w, x) := rotateSoCone t x; rotatedSoCone v w x := by simp [rotatedSoCone, rotateSoCone] have habsx0t : |x 0| ≤ t := by rw [soCone, Fin.sum_univ_succ] at h @@ -62,13 +60,11 @@ lemma rotateSoCone_rotatedSoCone {n : ℕ} {t : ℝ} {x : Fin n.succ → ℝ} { simp [le_div_iff]; linarith } /-- If `(v, w, x) ∈ 𝒬ⁿ⁺²` then `u(v, w, x) ∈ 𝒬ᵣⁿ⁺¹`. -/ -def unrotateSoCone {n : ℕ} (v w : Real) (x : Fin n → ℝ) : - ℝ × (Fin n.succ → ℝ) := +def unrotateSoCone {n : ℕ} (v w : Real) (x : Fin n → ℝ) : ℝ × (Fin n.succ → ℝ) := ((v + w) / sqrt 2, Matrix.vecCons ((v - w) / sqrt 2) x) -lemma unrotateSoCone_soCone {n : ℕ} {v w : ℝ} {x : Fin n → ℝ} - (h : rotatedSoCone v w x) : - let (t, x) := unrotateSoCone v w x; soCone t x := by +lemma unrotateSoCone_soCone {n : ℕ} {v w : ℝ} {x : Fin n → ℝ} (h : rotatedSoCone v w x) : + let (t, x) := unrotateSoCone v w x; soCone t x := by simp [soCone, unrotateSoCone] replace ⟨h, hv, hw⟩ := h rw [sqrt_le_iff] @@ -82,9 +78,6 @@ lemma unrotateSoCone_soCone {n : ℕ} {v w : ℝ} {x : Fin n → ℝ} norm_cast at hrw h rwa [hrw] } --- TODO(RFM): rotate then unrotate? --- TODO(RFM): unrotate then rotate? - end ConeConversion section Lemmas diff --git a/CvxLean/Lib/Cones/ZeroCone.lean b/CvxLean/Lib/Cones/ZeroCone.lean index cd632c36..1c81e566 100644 --- a/CvxLean/Lib/Cones/ZeroCone.lean +++ b/CvxLean/Lib/Cones/ZeroCone.lean @@ -11,7 +11,7 @@ def zeroCone (x : ℝ) : Prop := def Vec.zeroCone {n} [Fintype n] (x : n → ℝ) : Prop := ∀ i, Real.zeroCone (x i) -/-- The `n`-dimensional zero cone `{0}ⁿˣᵐ`. -/ +/-- The `n×m`-dimensional zero cone `{0}ⁿˣᵐ`. -/ def Matrix.zeroCone {n m} [Fintype n] [Fintype m] (M : Matrix n m ℝ) : Prop := ∀ i j, Real.zeroCone (M i j) diff --git a/CvxLean/Lib/Math/CovarianceEstimation.lean b/CvxLean/Lib/Math/CovarianceEstimation.lean index 16a7bca4..ff15babe 100644 --- a/CvxLean/Lib/Math/CovarianceEstimation.lean +++ b/CvxLean/Lib/Math/CovarianceEstimation.lean @@ -1,7 +1,7 @@ import CvxLean.Lib.Math.LinearAlgebra.Matrix.PosDef /-! -Definitions needed for the case study +Definitions needed for `Examples/CovarianceEstimation.lean`. -/ noncomputable section CovarianceEstimation diff --git a/CvxLean/Lib/Math/Data/Fin.lean b/CvxLean/Lib/Math/Data/Fin.lean index f3104659..65b44275 100644 --- a/CvxLean/Lib/Math/Data/Fin.lean +++ b/CvxLean/Lib/Math/Data/Fin.lean @@ -1,5 +1,9 @@ import Mathlib.Data.Fin.Basic +/-! +Basic properties about `Fin`. +-/ + namespace Fin variable {n : ℕ} diff --git a/CvxLean/Lib/Math/Data/Matrix.lean b/CvxLean/Lib/Math/Data/Matrix.lean index 1d5080cf..ca1ce1a7 100644 --- a/CvxLean/Lib/Math/Data/Matrix.lean +++ b/CvxLean/Lib/Math/Data/Matrix.lean @@ -6,6 +6,11 @@ import Mathlib.Data.Array.Defs import CvxLean.Lib.Math.Data.List import CvxLean.Lib.Math.Data.Vec +/-! +Extra operations on matrices. Importantly, computable versions of matrix operations are defined +here, which are used by the real-to-float procedure. +-/ + namespace Matrix def const (k : α) : Matrix m n α := diff --git a/CvxLean/Lib/Math/Data/Real.lean b/CvxLean/Lib/Math/Data/Real.lean index 89eedbad..b7a26cd6 100644 --- a/CvxLean/Lib/Math/Data/Real.lean +++ b/CvxLean/Lib/Math/Data/Real.lean @@ -2,6 +2,11 @@ import Mathlib.Data.Real.Basic import Mathlib.Data.Complex.Exponential import Mathlib.Analysis.SpecialFunctions.Pow.Real +/-! +Extra real functions and results. Some are needed to define atoms. There are also some lemmas that +are useful for the pre-DCP rewrite system. +-/ + namespace Real /-- This makes real powers the default, avoiding a mixture of `ℕ` and `ℝ`, which is problematic for diff --git a/CvxLean/Lib/Math/Data/Vec.lean b/CvxLean/Lib/Math/Data/Vec.lean index 7692f0ba..9b31d6f4 100644 --- a/CvxLean/Lib/Math/Data/Vec.lean +++ b/CvxLean/Lib/Math/Data/Vec.lean @@ -3,6 +3,11 @@ import Mathlib.Analysis.InnerProductSpace.PiL2 import CvxLean.Lib.Math.Data.Real import CvxLean.Lib.Math.Data.Fin +/-! +Extra vector functions and results. Some are needed to define atoms. Importantly, computable +versions of vector operations are defined here, which are used by the real-to-float procedure. +-/ + namespace Vec noncomputable instance (priority := high) : NormedAddCommGroup (Fin n → ℝ) := diff --git a/CvxLean/Lib/Math/LinearAlgebra/Eigenspace.lean b/CvxLean/Lib/Math/LinearAlgebra/Eigenspace.lean index 891c6fe5..c155455d 100644 --- a/CvxLean/Lib/Math/LinearAlgebra/Eigenspace.lean +++ b/CvxLean/Lib/Math/LinearAlgebra/Eigenspace.lean @@ -24,13 +24,11 @@ lemma eigenspace_one : eigenspace (1 : End R M) 1 = ⊤ := by intros x _ simp only [mem_eigenspace_iff, LinearMap.one_apply, one_smul] -lemma has_eigenvector_add {f g : End R M} {a b : R} {x : M} - (hf : HasEigenvector f a x) (hg : HasEigenvector g b x) : - HasEigenvector (f + g) (a + b) x := +lemma has_eigenvector_add {f g : End R M} {a b : R} {x : M} (hf : HasEigenvector f a x) + (hg : HasEigenvector g b x) : HasEigenvector (f + g) (a + b) x := ⟨eigenspace_add ⟨hf.1, hg.1⟩, hf.2⟩ -lemma has_eigenvector_one {x : M} (hx : x ≠ 0) : - HasEigenvector (1 : End R M) 1 x := +lemma has_eigenvector_one {x : M} (hx : x ≠ 0) : HasEigenvector (1 : End R M) 1 x := ⟨by { rw [eigenspace_one]; apply Submodule.mem_top }, hx⟩ end End diff --git a/CvxLean/Lib/Math/LinearAlgebra/Matrix/PosDef.lean b/CvxLean/Lib/Math/LinearAlgebra/Matrix/PosDef.lean index eeb764d0..e0388f23 100644 --- a/CvxLean/Lib/Math/LinearAlgebra/Matrix/PosDef.lean +++ b/CvxLean/Lib/Math/LinearAlgebra/Matrix/PosDef.lean @@ -13,15 +13,13 @@ variable {𝕜 : Type _} variable [NormedField 𝕜] [PartialOrder 𝕜] [StarOrderedRing 𝕜] variable [IsROrC 𝕜] -lemma PosSemidef.det_nonneg {M : Matrix n n ℝ} (hM : M.PosSemidef) - [DecidableEq n] : 0 ≤ det M := by +lemma PosSemidef.det_nonneg {M : Matrix n n ℝ} (hM : M.PosSemidef) [DecidableEq n] : 0 ≤ det M := by rw [hM.1.det_eq_prod_eigenvalues] apply Finset.prod_nonneg intros i _hi apply eigenvalues_nonneg hM -lemma PosDef.det_ne_zero [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.PosDef) : - M.det ≠ 0 := by +lemma PosDef.det_ne_zero [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.PosDef) : M.det ≠ 0 := by rw [← Matrix.nondegenerate_iff_det_ne_zero] intros v hv have hv' := hv (star v) @@ -30,12 +28,11 @@ lemma PosDef.det_ne_zero [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.PosDef) : have := hM.2 (star v) h simp [star_star, hv'] at this -lemma PosDef.isUnit_det [DecidableEq n] {M : Matrix n n ℝ} (hM : M.PosDef) : - IsUnit M.det := +lemma PosDef.isUnit_det [DecidableEq n] {M : Matrix n n ℝ} (hM : M.PosDef) : IsUnit M.det := isUnit_iff_ne_zero.2 hM.det_ne_zero -noncomputable instance PosDef.Invertible [DecidableEq n] {M : Matrix n n 𝕜} - (hM : M.PosDef) : Invertible M := +noncomputable instance PosDef.Invertible [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.PosDef) : + Invertible M := invertibleOfIsUnitDet M (isUnit_iff_ne_zero.2 hM.det_ne_zero) lemma PosSemidef_diagonal [DecidableEq n] {f : n → ℝ} (hf : ∀ i, 0 ≤ f i) : @@ -48,8 +45,7 @@ lemma PosSemidef_diagonal [DecidableEq n] {f : n → ℝ} (hf : ∀ i, 0 ≤ f i rw [mulVec_diagonal f x i, mul_comm, mul_assoc] exact mul_nonneg (hf i) (mul_self_nonneg (x i)) -lemma PosDef_diagonal [DecidableEq n] {f : n → ℝ} (hf : ∀ i, 0 < f i) : - (diagonal f).PosDef := by +lemma PosDef_diagonal [DecidableEq n] {f : n → ℝ} (hf : ∀ i, 0 < f i) : (diagonal f).PosDef := by refine' ⟨isHermitian_diagonal _, _⟩ intros x hx simp only [star, id.def, IsROrC.re_to_real] @@ -65,15 +61,15 @@ lemma PosDef_diagonal [DecidableEq n] {f : n → ℝ} (hf : ∀ i, 0 < f i) : rw [mul_self_eq_zero.1 (le_antisymm this (mul_self_nonneg (x i)))] rfl } -lemma PosSemidef.conjTranspose_mul_mul (M N : Matrix n n 𝕜) - (hM : M.PosSemidef) : (Nᴴ * M * N).PosSemidef := by +lemma PosSemidef.conjTranspose_mul_mul (M N : Matrix n n 𝕜) (hM : M.PosSemidef) : + (Nᴴ * M * N).PosSemidef := by refine' ⟨isHermitian_conjTranspose_mul_mul _ hM.1, _⟩ intro x convert hM.2 (N.mulVec x) using 1 rw [mul_assoc, mulVec_mulVec, ← mulVec_mulVec, dotProduct_mulVec, star_mulVec] -lemma PosDef.conjTranspose_mul_mul [DecidableEq n] (M N : Matrix n n 𝕜) - (hM : M.PosDef) (hN : N.det ≠ 0) : (Nᴴ * M * N).PosDef := by +lemma PosDef.conjTranspose_mul_mul [DecidableEq n] (M N : Matrix n n 𝕜) (hM : M.PosDef) + (hN : N.det ≠ 0) : (Nᴴ * M * N).PosDef := by refine' ⟨isHermitian_conjTranspose_mul_mul _ hM.1, _⟩ intros x hx convert @@ -81,9 +77,8 @@ lemma PosDef.conjTranspose_mul_mul [DecidableEq n] (M N : Matrix n n 𝕜) rw [Matrix.mul_assoc, mulVec_mulVec, ← mulVec_mulVec, dotProduct_mulVec, star_mulVec] -lemma IsHermitian.nonsingular_inv [DecidableEq n] {M : Matrix n n 𝕜} - (hM : M.IsHermitian) (hMdet : IsUnit M.det): - M⁻¹.IsHermitian := by +lemma IsHermitian.nonsingular_inv [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.IsHermitian) + (hMdet : IsUnit M.det) : M⁻¹.IsHermitian := by refine' (Matrix.inv_eq_right_inv _).symm rw [conjTranspose_nonsing_inv, hM.eq, mul_nonsing_inv _ hMdet] @@ -92,8 +87,7 @@ lemma conj_symm {M : Matrix n n 𝕜} (hM : M.IsHermitian) : nth_rewrite 1 [star_dotProduct, star_mulVec] rw [star_star, dotProduct_mulVec, hM.eq] -lemma PosDef.nonsingular_inv [DecidableEq n] {M : Matrix n n 𝕜} - (hM : M.PosDef) : M⁻¹.PosDef := by +lemma PosDef.nonsingular_inv [DecidableEq n] {M : Matrix n n 𝕜} (hM : M.PosDef) : M⁻¹.PosDef := by refine' ⟨hM.1.nonsingular_inv (isUnit_iff_ne_zero.2 hM.det_ne_zero), _⟩ intros x hx have hMMinv := mul_nonsing_inv _ (isUnit_iff_ne_zero.2 hM.det_ne_zero) @@ -104,26 +98,25 @@ lemma PosDef.nonsingular_inv [DecidableEq n] {M : Matrix n n 𝕜} rw [conj_symm ((@isHermitian_inv _ _ _ _ _ _ M hM.Invertible).2 hM.1)] at hres exact hres -lemma PosSemidef.mul_mul_of_IsHermitian {M N : Matrix n n 𝕜} - (hM : M.PosSemidef) (hN : N.IsHermitian) : (N * M * N).PosSemidef := by +lemma PosSemidef.mul_mul_of_IsHermitian {M N : Matrix n n 𝕜} (hM : M.PosSemidef) + (hN : N.IsHermitian) : (N * M * N).PosSemidef := by convert hM.conjTranspose_mul_mul M N; exact hN.symm -lemma PosSemidef.add {M N : Matrix n n 𝕜} (hM : M.PosSemidef) - (hN : N.PosSemidef) : (M + N).PosSemidef := by +lemma PosSemidef.add {M N : Matrix n n 𝕜} (hM : M.PosSemidef) (hN : N.PosSemidef) : + (M + N).PosSemidef := by refine' ⟨hM.1.add hN.1, _⟩ intros x simp only [add_mulVec, dotProduct_add, map_add] apply add_nonneg (hM.2 x) (hN.2 x) -lemma isUnit_det_of_PosDef_inv [DecidableEq n] {M : Matrix n n ℝ} - (h : M⁻¹.PosDef) : IsUnit M.det := by +lemma isUnit_det_of_PosDef_inv [DecidableEq n] {M : Matrix n n ℝ} (h : M⁻¹.PosDef) : + IsUnit M.det := by apply isUnit_iff_ne_zero.2 have := h.isUnit_det rw [det_nonsing_inv, isUnit_ring_inverse] at this apply IsUnit.ne_zero this -lemma PosDef_inv_iff_PosDef [DecidableEq n] (M : Matrix n n ℝ) : - M⁻¹.PosDef ↔ M.PosDef := by +lemma PosDef_inv_iff_PosDef [DecidableEq n] (M : Matrix n n ℝ) : M⁻¹.PosDef ↔ M.PosDef := by constructor { intros hM rw [← Matrix.nonsing_inv_nonsing_inv M (isUnit_det_of_PosDef_inv hM)] diff --git a/CvxLean/Lib/Math/LinearAlgebra/Matrix/Spectrum.lean b/CvxLean/Lib/Math/LinearAlgebra/Matrix/Spectrum.lean index 58c1032a..67a09224 100644 --- a/CvxLean/Lib/Math/LinearAlgebra/Matrix/Spectrum.lean +++ b/CvxLean/Lib/Math/LinearAlgebra/Matrix/Spectrum.lean @@ -11,8 +11,8 @@ open Matrix open BigOperators -/-- We need to change the instance of normed add comm group for n → 𝕜 so that it -picks the correct inner product space instance. -/ +/-- We need to change the instance of normed add comm group for n → 𝕜 so that it picks the correct +inner product space instance. -/ @[local instance] noncomputable def frobeniusNormedAddCommGroup' [NormedAddCommGroup 𝕜] : NormedAddCommGroup (n → 𝕜) := @@ -24,16 +24,13 @@ noncomputable instance : InnerProductSpace 𝕜 (n → 𝕜) := EuclideanSpace.instInnerProductSpace lemma IsHermitian.hasEigenvector_eigenvectorBasis (hA : A.IsHermitian) (i : n) : - Module.End.HasEigenvector - (Matrix.toLin' A) (hA.eigenvalues i) (hA.eigenvectorBasis i) := by + Module.End.HasEigenvector (Matrix.toLin' A) (hA.eigenvalues i) (hA.eigenvectorBasis i) := by simp only [IsHermitian.eigenvectorBasis, OrthonormalBasis.coe_reindex] apply LinearMap.IsSymmetric.hasEigenvector_eigenvectorBasis -/-- *Diagonalization theorem*, *spectral theorem* for matrices; A hermitian -matrix can be diagonalized by a change of basis using a matrix consisting of -eigenvectors. -/ -theorem spectral_theorem (xs : OrthonormalBasis n 𝕜 (EuclideanSpace 𝕜 n)) - (as : n → ℝ) +/-- *Diagonalization theorem*, *spectral theorem* for matrices; A hermitian matrix can be +diagonalized by a change of basis using a matrix consisting of eigenvectors. -/ +theorem spectral_theorem (xs : OrthonormalBasis n 𝕜 (EuclideanSpace 𝕜 n)) (as : n → ℝ) (hxs : ∀ j, Module.End.HasEigenvector (Matrix.toLin' A) (as j) (xs j)) : xs.toBasis.toMatrix (Pi.basisFun 𝕜 n) * A = diagonal (IsROrC.ofReal ∘ as) * xs.toBasis.toMatrix (Pi.basisFun 𝕜 n) := by @@ -66,10 +63,8 @@ theorem spectral_theorem (xs : OrthonormalBasis n 𝕜 (EuclideanSpace 𝕜 n)) Equiv.symm_apply_apply, Equiv.apply_symm_apply] rfl } -lemma det_eq_prod_eigenvalues (xs : OrthonormalBasis n 𝕜 (EuclideanSpace 𝕜 n)) - (as : n → ℝ) - (hxs : ∀ j, Module.End.HasEigenvector (Matrix.toLin' A) (as j) (xs j)) : - det A = ∏ i, as i := by +lemma det_eq_prod_eigenvalues (xs : OrthonormalBasis n 𝕜 (EuclideanSpace 𝕜 n)) (as : n → ℝ) + (hxs : ∀ j, Module.End.HasEigenvector (Matrix.toLin' A) (as j) (xs j)) : det A = ∏ i, as i := by apply mul_left_cancel₀ (det_ne_zero_of_left_inverse (Basis.toMatrix_mul_toMatrix_flip (Pi.basisFun 𝕜 n) xs.toBasis)) rw [← det_mul, spectral_theorem xs as hxs, det_mul, mul_comm, det_diagonal] diff --git a/CvxLean/Lib/Math/LinearAlgebra/Matrix/Triangular.lean b/CvxLean/Lib/Math/LinearAlgebra/Matrix/Triangular.lean index 3ed53d7b..256ac6d5 100644 --- a/CvxLean/Lib/Math/LinearAlgebra/Matrix/Triangular.lean +++ b/CvxLean/Lib/Math/LinearAlgebra/Matrix/Triangular.lean @@ -4,10 +4,9 @@ import CvxLean.Lib.Math.LinearAlgebra.Matrix.Block /- # Triangular Matrices -This file defines upper and lower triangular matrices. The definitions are based -on `Matrix.BlockTriangular`. All properties should ideally be proved for -`Matrix.BlockTriangular` in general and then specialized to -(nonblock)-triangular matrices here. +This file defines upper and lower triangular matrices. The definitions are based on +`Matrix.BlockTriangular`. All properties should ideally be proved for `Matrix.BlockTriangular` in +general and then specialized to (nonblock)-triangular matrices here. -/ namespace Matrix @@ -17,42 +16,40 @@ open BigOperators Matrix variable {α m n : Type _} variable {R : Type _} [CommRing R] {M N : Matrix m m R} -/-- An upper triangular matrix is a matrix whose entries are zero below the -diagonal. -/ +/-- An upper triangular matrix is a matrix whose entries are zero below the diagonal. -/ def upperTriangular [LT m] (M : Matrix m m R) := M.BlockTriangular id -/-- A lower triangular matrix is a matrix whose entries are zero above the -diagonal. -/ +/-- A lower triangular matrix is a matrix whose entries are zero above the diagonal. -/ def lowerTriangular [LT m] (M : Matrix m m R) := M.BlockTriangular OrderDual.toDual -/-- The inverse of an upper triangular matrix is upper triangular -/ +/-- The inverse of an upper triangular matrix is upper triangular. -/ lemma upperTriangular_inv_of_upperTriangular [Fintype m] [LinearOrder m] [Invertible M] (hM : upperTriangular M) : upperTriangular M⁻¹ := blockTriangular_inv_of_blockTriangular hM -/-- The inverse of a lower triangular matrix is lower triangular -/ +/-- The inverse of a lower triangular matrix is lower triangular. -/ lemma lowerTriangular_inv_of_lowerTriangular [Fintype m] [LinearOrder m] [Invertible M] (hM : lowerTriangular M) : lowerTriangular M⁻¹ := blockTriangular_inv_of_blockTriangular hM -/-- Multiplication of upper triangular matrices is upper triangular -/ +/-- Multiplication of upper triangular matrices is upper triangular. -/ lemma upperTriangular.mul [Fintype m] [LinearOrder m] (hM : upperTriangular M) (hN : upperTriangular N) : upperTriangular (M * N) := BlockTriangular.mul hM hN -/-- Multiplication of lower triangular matrices is lower triangular -/ +/-- Multiplication of lower triangular matrices is lower triangular. -/ lemma lowerTriangular.mul [Fintype m] [LinearOrder m] (hM : lowerTriangular M) (hN : lowerTriangular N) : lowerTriangular (M * N) := BlockTriangular.mul hM hN -/-- Transpose of lower triangular matrix is upper triangular -/ +/-- Transpose of lower triangular matrix is upper triangular. -/ lemma lowerTriangular.transpose [Fintype m] [LinearOrder m] (hM : lowerTriangular M) : upperTriangular Mᵀ := BlockTriangular.transpose hM -/-- Transpose of upper triangular matrix is lower triangular -/ +/-- Transpose of upper triangular matrix is lower triangular. -/ lemma upperTriangular.transpose [Fintype m] [LinearOrder m] (hM : upperTriangular M) : lowerTriangular Mᵀ := BlockTriangular.transpose hM diff --git a/CvxLean/Lib/Math/LogDet.lean b/CvxLean/Lib/Math/LogDet.lean index 6b52bebf..190ec7ca 100644 --- a/CvxLean/Lib/Math/LogDet.lean +++ b/CvxLean/Lib/Math/LogDet.lean @@ -7,8 +7,8 @@ import CvxLean.Lib.Math.LinearAlgebra.Matrix.ToUpperTri import CvxLean.Lib.Math.LinearAlgebra.Matrix.LDL /-! -In this file we prove properties needed for the log-det-atom. We follow the proof in the -MOSEK documentation: https://docs.mosek.com/modeling-cookbook/sdo.html#log-determinant. +In this file we prove properties needed for the log-det-atom. We follow the proof in the MOSEK +documentation: https://docs.mosek.com/modeling-cookbook/sdo.html#log-determinant. See `CvxLean.Tactic.DCP.Fns.LogDet`. -/ diff --git a/CvxLean/Lib/Math/LogSumExp.lean b/CvxLean/Lib/Math/LogSumExp.lean index 183a75fc..f25e8885 100644 --- a/CvxLean/Lib/Math/LogSumExp.lean +++ b/CvxLean/Lib/Math/LogSumExp.lean @@ -1,6 +1,10 @@ import CvxLean.Lib.Math.Data.Real import CvxLean.Lib.Math.Data.Vec +/-! +See `CvxLean.Tactic.DCP.Fns.LogSumExp`. +-/ + namespace Vec open Real diff --git a/CvxLean/Lib/Math/SchurComplement.lean b/CvxLean/Lib/Math/SchurComplement.lean index e1c08203..1ad2d927 100644 --- a/CvxLean/Lib/Math/SchurComplement.lean +++ b/CvxLean/Lib/Math/SchurComplement.lean @@ -1,23 +1,20 @@ -/- -Copyright (c) 2022 Alexander Bentkamp. All rights reserved. -Released under Apache 2.0 license as described in the file LICENSE. -Authors: Alexander Bentkamp, Jeremy Avigad, Johan Commelin --/ import Mathlib.LinearAlgebra.Matrix.Symmetric import Mathlib.LinearAlgebra.Matrix.NonsingularInverse import Mathlib.LinearAlgebra.Matrix.PosDef import Mathlib.Algebra.Star.Pi -/-! # Schur complement +/-! +# Schur complement This file proves properties of the Schur complement `D - C A⁻¹ B` of a block Matrix `[A B; C D]`. -abentkamp marked this conversation as resolved. -Show resolved The determinant of a block Matrix in terms of the Schur complement is expressed in the lemmas `Matrix.det_fromBlocks₁₁` and `Matrix.det_fromBlocks₂₂` in the file `LinearAlgebra.Matrix.NonsingularInverse`. + ## Main result - * `Matrix.posSemidef.fromBlocks₁₁` : If a Matrix `A` is positive definite, then - `[A B; Bᴴ D]` is postive semidefinite if and only if `D - Bᴴ A⁻¹ B` is postive semidefinite. +* `Matrix.posSemidef.fromBlocks₁₁` : If a Matrix `A` is positive definite, then `[A B; Bᴴ D]` is + postive semidefinite if and only if `D - Bᴴ A⁻¹ B` is postive semidefinite. + +Authors: Alexander Bentkamp, Jeremy Avigad, Johan Commelin. -/ namespace Matrix @@ -26,34 +23,33 @@ open scoped Matrix ComplexOrder variable {n : Type _} {m : Type _} {𝕜 : Type _} [IsROrC 𝕜] -scoped infix:65 " ⊕ᵥ " => Sum.elim +scoped infix:65 " ⊕ᵥ " => Sum.elim -lemma schur_complement_eq₁₁ [Fintype m] [DecidableEq m] [Fintype n] - {A : Matrix m m 𝕜} (B : Matrix m n 𝕜) (D : Matrix n n 𝕜) (x : m → 𝕜) (y : n → 𝕜) - [Invertible A] (hA : A.IsHermitian) : -vecMul (star (x ⊕ᵥ y)) (fromBlocks A B Bᴴ D) ⬝ᵥ (x ⊕ᵥ y) = - vecMul (star (x + (A⁻¹ * B).mulVec y)) A ⬝ᵥ (x + (A⁻¹ * B).mulVec y) + - vecMul (star y) (D - Bᴴ * A⁻¹ * B) ⬝ᵥ y := by +lemma schur_complement_eq₁₁ [Fintype m] [DecidableEq m] [Fintype n] {A : Matrix m m 𝕜} + (B : Matrix m n 𝕜) (D : Matrix n n 𝕜) (x : m → 𝕜) (y : n → 𝕜) [Invertible A] + (hA : A.IsHermitian) : + vecMul (star (x ⊕ᵥ y)) (fromBlocks A B Bᴴ D) ⬝ᵥ (x ⊕ᵥ y) = + vecMul (star (x + (A⁻¹ * B).mulVec y)) A ⬝ᵥ (x + (A⁻¹ * B).mulVec y) + + vecMul (star y) (D - Bᴴ * A⁻¹ * B) ⬝ᵥ y := by simp [Function.star_sum_elim, fromBlocks_mulVec, vecMul_fromBlocks, add_vecMul, dotProduct_mulVec, vecMul_sub, Matrix.mul_assoc, vecMul_mulVec, hA.eq, conjTranspose_nonsing_inv, star_mulVec] abel -lemma schur_complement_eq₂₂ [Fintype m] [Fintype n] [DecidableEq n] - (A : Matrix m m 𝕜) (B : Matrix m n 𝕜) {D : Matrix n n 𝕜} (x : m → 𝕜) (y : n → 𝕜) - [Invertible D] (hD : D.IsHermitian) : -vecMul (star (x ⊕ᵥ y)) (fromBlocks A B Bᴴ D) ⬝ᵥ (x ⊕ᵥ y) = - vecMul (star ((D⁻¹ * Bᴴ).mulVec x + y)) D ⬝ᵥ ((D⁻¹ * Bᴴ).mulVec x + y) + - vecMul (star x) (A - B * D⁻¹ * Bᴴ) ⬝ᵥ x := by +lemma schur_complement_eq₂₂ [Fintype m] [Fintype n] [DecidableEq n] (A : Matrix m m 𝕜) + (B : Matrix m n 𝕜) {D : Matrix n n 𝕜} (x : m → 𝕜) (y : n → 𝕜) [Invertible D] + (hD : D.IsHermitian) : + vecMul (star (x ⊕ᵥ y)) (fromBlocks A B Bᴴ D) ⬝ᵥ (x ⊕ᵥ y) = + vecMul (star ((D⁻¹ * Bᴴ).mulVec x + y)) D ⬝ᵥ ((D⁻¹ * Bᴴ).mulVec x + y) + + vecMul (star x) (A - B * D⁻¹ * Bᴴ) ⬝ᵥ x := by simp [Function.star_sum_elim, fromBlocks_mulVec, vecMul_fromBlocks, add_vecMul, dotProduct_mulVec, vecMul_sub, Matrix.mul_assoc, vecMul_mulVec, hD.eq, conjTranspose_nonsing_inv, star_mulVec] abel -lemma IsHermitian.fromBlocks₁₁ [Fintype m] [DecidableEq m] - {A : Matrix m m 𝕜} (B : Matrix m n 𝕜) (D : Matrix n n 𝕜) - (hA : A.IsHermitian) : - (Matrix.fromBlocks A B Bᴴ D).IsHermitian ↔ (D - Bᴴ * A⁻¹ * B).IsHermitian := by +lemma IsHermitian.fromBlocks₁₁ [Fintype m] [DecidableEq m] {A : Matrix m m 𝕜} (B : Matrix m n 𝕜) + (D : Matrix n n 𝕜) (hA : A.IsHermitian) : + (Matrix.fromBlocks A B Bᴴ D).IsHermitian ↔ (D - Bᴴ * A⁻¹ * B).IsHermitian := by have hBAB : (Bᴴ * A⁻¹ * B).IsHermitian { apply isHermitian_conjTranspose_mul_mul apply hA.inv } @@ -66,42 +62,37 @@ lemma IsHermitian.fromBlocks₁₁ [Fintype m] [DecidableEq m] rw [← sub_add_cancel D] apply IsHermitian.add h hBAB } -lemma IsHermitian.fromBlocks₂₂ [Fintype n] [DecidableEq n] - (A : Matrix m m 𝕜) (B : Matrix m n 𝕜) {D : Matrix n n 𝕜} - (hD : D.IsHermitian) : - (Matrix.fromBlocks A B Bᴴ D).IsHermitian ↔ (A - B * D⁻¹ * Bᴴ).IsHermitian := by - rw [←isHermitian_submatrix_equiv (Equiv.sumComm n m), Equiv.sumComm_apply, +lemma IsHermitian.fromBlocks₂₂ [Fintype n] [DecidableEq n] (A : Matrix m m 𝕜) (B : Matrix m n 𝕜) + {D : Matrix n n 𝕜} (hD : D.IsHermitian) : + (Matrix.fromBlocks A B Bᴴ D).IsHermitian ↔ (A - B * D⁻¹ * Bᴴ).IsHermitian := by + rw [← isHermitian_submatrix_equiv (Equiv.sumComm n m), Equiv.sumComm_apply, fromBlocks_submatrix_sum_swap_sum_swap] convert IsHermitian.fromBlocks₁₁ _ _ hD <;> rw [conjTranspose_conjTranspose] -lemma PosSemidef.fromBlocks₁₁ [Fintype m] [DecidableEq m] [Fintype n] - {A : Matrix m m 𝕜} (B : Matrix m n 𝕜) (D : Matrix n n 𝕜) - (hA : A.PosDef) [Invertible A] : - (fromBlocks A B Bᴴ D).PosSemidef ↔ (D - Bᴴ * A⁻¹ * B).PosSemidef := by +lemma PosSemidef.fromBlocks₁₁ [Fintype m] [DecidableEq m] [Fintype n] {A : Matrix m m 𝕜} + (B : Matrix m n 𝕜) (D : Matrix n n 𝕜) (hA : A.PosDef) [Invertible A] : + (fromBlocks A B Bᴴ D).PosSemidef ↔ (D - Bᴴ * A⁻¹ * B).PosSemidef := by rw [PosSemidef, IsHermitian.fromBlocks₁₁ _ _ hA.1] constructor - { -- NOTE: refine λ h, ⟨h.1, λ x, _⟩, - intro h; refine' ⟨h.1, _⟩; intro x + { intro h; refine' ⟨h.1, _⟩; intro x have := h.2 (- ((A⁻¹ * B).mulVec x) ⊕ᵥ x) rw [dotProduct_mulVec, schur_complement_eq₁₁ B D _ _ hA.1, neg_add_self, dotProduct_zero, zero_add] at this rw [dotProduct_mulVec]; exact this } - { -- NOTE: refine λ h, ⟨h.1, λ x, _⟩, - intro h; refine' ⟨h.1, _⟩; intro x + { intro h; refine' ⟨h.1, _⟩; intro x rw [dotProduct_mulVec, ← Sum.elim_comp_inl_inr x, schur_complement_eq₁₁ B D _ _ hA.1] apply le_add_of_nonneg_of_le { rw [← dotProduct_mulVec] apply hA.posSemidef.2 } - { rw [← dotProduct_mulVec _ _ (x ∘ Sum.inr)] + { rw [← dotProduct_mulVec _ _ (x ∘ Sum.inr)] apply h.2 } } -lemma PosSemidef.fromBlocks₂₂ [Fintype m] [Fintype n] [DecidableEq n] - (A : Matrix m m 𝕜) (B : Matrix m n 𝕜) {D : Matrix n n 𝕜} - (hD : D.PosDef) [Invertible D] : - (fromBlocks A B Bᴴ D).PosSemidef ↔ (A - B * D⁻¹ * Bᴴ).PosSemidef := by - rw [←posSemidef_submatrix_equiv (Equiv.sumComm n m), Equiv.sumComm_apply, +lemma PosSemidef.fromBlocks₂₂ [Fintype m] [Fintype n] [DecidableEq n] (A : Matrix m m 𝕜) + (B : Matrix m n 𝕜) {D : Matrix n n 𝕜} (hD : D.PosDef) [Invertible D] : + (fromBlocks A B Bᴴ D).PosSemidef ↔ (A - B * D⁻¹ * Bᴴ).PosSemidef := by + rw [← posSemidef_submatrix_equiv (Equiv.sumComm n m), Equiv.sumComm_apply, fromBlocks_submatrix_sum_swap_sum_swap] convert @PosSemidef.fromBlocks₁₁ m n 𝕜 _ _ _ _ _ _ _ hD _ <;> rw [conjTranspose_conjTranspose] -end Matrix +end Matrix diff --git a/CvxLean/Lib/Math/Subadditivity.lean b/CvxLean/Lib/Math/Subadditivity.lean index 008fb6dd..6d31a33a 100644 --- a/CvxLean/Lib/Math/Subadditivity.lean +++ b/CvxLean/Lib/Math/Subadditivity.lean @@ -3,7 +3,6 @@ import Mathlib.LinearAlgebra.Matrix.Spectrum import Mathlib.LinearAlgebra.Eigenspace.Basic import Mathlib.LinearAlgebra.Matrix.LDL import Mathlib.LinearAlgebra.Matrix.DotProduct - import CvxLean.Lib.Math.LinearAlgebra.Matrix.PosDef import CvxLean.Lib.Math.LinearAlgebra.Matrix.Spectrum import CvxLean.Lib.Math.LinearAlgebra.Eigenspace @@ -43,7 +42,7 @@ lemma eigenvectorMatrix_inv_mul : hA.eigenvectorMatrixInv * hA.eigenvectorMatrix = 1 := by apply Basis.toMatrix_mul_toMatrix_flip --- NOTE: There is a spectral_theorem' +-- NOTE: There is a `spectral_theorem'`. theorem spectral_theorem'' : hA.eigenvectorMatrix * diagonal (IsROrC.ofReal ∘ hA.eigenvalues) * hA.eigenvectorMatrixᴴ = A := by rw [conjTranspose_eigenvectorMatrix, Matrix.mul_assoc, ← spectral_theorem, @@ -118,7 +117,7 @@ lemma PosSemidef.PosDef_iff_det_ne_zero [DecidableEq n] {M : Matrix n n ℝ} (hM matrices is positive definite. See `det_add_det_le_det_add` for the more general statement. The argument is taken from Andreas Thom's comment on mathoverflow: -https://mathoverflow.net/questions/65424/determinant-of-sum-of-positive-definite-matrices/65430#65430 -/ +https://mathoverflow.net/questions/65424/determinant-of-sum-of-positive-definite-matrices. -/ lemma det_add_det_le_det_add' [Nonempty n] (A B : Matrix n n ℝ) (hA : A.PosDef) (hB : B.PosSemidef) : A.det + B.det ≤ (A + B).det := by From fec4e5041bd5e27c3372ba515352b59aeb179504 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 15:50:46 -0500 Subject: [PATCH 32/52] doc: `Syntax` --- CvxLean/Meta/Util/Error.lean | 12 +- CvxLean/Syntax/Label.lean | 43 +++--- CvxLean/Syntax/Minimization.lean | 183 +++++++++++++++----------- CvxLean/Syntax/OptimizationParam.lean | 5 + CvxLean/Syntax/Options.lean | 8 +- CvxLean/Syntax/Parser.lean | 14 +- CvxLean/Syntax/Prod.lean | 76 ++++++----- 7 files changed, 199 insertions(+), 142 deletions(-) diff --git a/CvxLean/Meta/Util/Error.lean b/CvxLean/Meta/Util/Error.lean index ea50e67b..178c7cf7 100644 --- a/CvxLean/Meta/Util/Error.lean +++ b/CvxLean/Meta/Util/Error.lean @@ -4,20 +4,26 @@ import Lean Custom error messages. -/ -syntax "throwTacticBuilderError" (interpolatedStr(term) <|> term) : term +syntax "throwParserError " (interpolatedStr(term) <|> term) : term + +macro_rules + | `(throwParserError $msg:interpolatedStr) => `(throwError ("Parser error: " ++ (m! $msg))) + | `(throwParserError $msg:term) => `(throwError ("Parser error: " ++ $msg)) + +syntax "throwTacticBuilderError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwTacticBuilderError $msg:interpolatedStr) => `(throwError ("Tactic builder error: " ++ (m! $msg))) | `(throwTacticBuilderError $msg:term) => `(throwError ("Tactic builder error: " ++ $msg)) -syntax "throwRwConstrError" (interpolatedStr(term) <|> term) : term +syntax "throwRwConstrError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwRwConstrError $msg:interpolatedStr) => `(throwError ("`rw_constr` error: " ++ (m! $msg))) | `(throwRwConstrError $msg:term) => `(throwError ("`rw_constr` error: " ++ $msg)) -syntax "throwRwObjError" (interpolatedStr(term) <|> term) : term +syntax "throwRwObjError " (interpolatedStr(term) <|> term) : term macro_rules | `(throwRwObjError $msg:interpolatedStr) => `(throwError ("`rw_obj` error: " ++ (m! $msg))) diff --git a/CvxLean/Syntax/Label.lean b/CvxLean/Syntax/Label.lean index ba810e8a..af458ecd 100644 --- a/CvxLean/Syntax/Label.lean +++ b/CvxLean/Syntax/Label.lean @@ -1,17 +1,24 @@ import Lean import CvxLean.Syntax.Options +/-! +# Labels for named expressions + +To keep track of the names of optimization variables and the constraints, we use metadata and attach +a `CvxLeanLabel` with a name. +-/ + namespace CvxLean open Lean -syntax (name := namedConstraint) "{** " term " ** " ident " **}": term +syntax (name := namedConstraint) "{** " term " ** " ident " **}" : term namespace Meta -/-- Attach a CvxLean label to an expression. They are used to indicate variable - names in a domain type and names of constraints. -/ -def mkLabel (name : Name) (e : Expr) : Expr := +/-- Attach a CvxLean label to an expression. They are used to indicate variable names in a domain +type and names of constraints. -/ +def mkLabel (name : Name) (e : Expr) : Expr := mkMData (MData.empty.setName `CvxLeanLabel name) e variable [MonadControlT MetaM m] [Monad m] @@ -20,10 +27,10 @@ variable [MonadControlT MetaM m] [Monad m] def decomposeLabel (e : Expr) : m (Name × Expr) := do match e with | Expr.mdata m e => - match m.get? `CvxLeanLabel with - | some (name : Name) => return (name, e) - | none => decomposeLabel e - | _ => return (`_, e) + match m.get? `CvxLeanLabel with + | some (name : Name) => return (name, e) + | none => decomposeLabel e + | _ => return (`_, e) /-- Like `CvxLean.Meta.decomposeLabel` but only returns the label name. -/ def getLabelName (e : Expr) : m Name := do @@ -36,15 +43,15 @@ namespace Elab open Lean.Elab /-- Notation for attaching a name label to a term. -/ -@[term_elab namedConstraint] +@[term_elab namedConstraint] def elabNamedConstraint : Term.TermElab := fun stx expectedType? => do match stx with | `({** $t ** $id **}) => - match id.raw with - | Syntax.ident _ _ val _ => - let e ← Term.elabTerm t expectedType? - return Meta.mkLabel val e - | _ => throwUnsupportedSyntax + match id.raw with + | Syntax.ident _ _ val _ => + let e ← Term.elabTerm t expectedType? + return Meta.mkLabel val e + | _ => throwUnsupportedSyntax | _ => throwUnsupportedSyntax end Elab @@ -57,13 +64,13 @@ open Lean.PrettyPrinter.Delaborator SubExpr @[delab mdata] def delabNamedConstraint : Delab := do -- Omit delaboration if pretty printing option is disabled. if not (pp.CvxLean.labels.get (← getOptions)) then failure - -- Check if CvxLeanLabel meta data is attached to current expression. + -- Check if `CvxLeanLabel` metadata is attached to current expression. let Expr.mdata m e ← getExpr | unreachable! match m.get? `CvxLeanLabel with | some (name : Name) => - let stx ← descend e 0 (do delab) - let id := mkIdent name - `({** $stx ** $id**}) + let stx ← descend e 0 (do delab) + let id := mkIdent name + `({** $stx ** $id**}) | none => failure end Delab diff --git a/CvxLean/Syntax/Minimization.lean b/CvxLean/Syntax/Minimization.lean index 374c7ff3..346ca79a 100644 --- a/CvxLean/Syntax/Minimization.lean +++ b/CvxLean/Syntax/Minimization.lean @@ -1,37 +1,68 @@ import CvxLean.Lib.Minimization import CvxLean.Meta.Util.SubExpr +import CvxLean.Meta.Util.Error import CvxLean.Meta.Minimization import CvxLean.Syntax.Parser +/-! +# Syntax to define optimization problems + +This file defines how the custom optimization syntax is elaborated into a `Minimization` term. This +involves encoding variable names and constraint tags in the metadata. Variable names also tell us +how to replace their occurrences by the appropriate projection. We illustrate it with an example. +``` +optimization (x y : ℝ) + minimize 40 * x + 30 * y + subject to + c₁ : 12 ≤ x + y + c₂ : 16 ≤ 2 * x + y +``` +is elaborated into +``` +Minimization.mk + (fun p => 40 * p.1 + 30 * p.2) + (fun p => 12 ≤ p.1 + p.2 ∧ 16 ≤ 2 * p.1 + p.2) +``` +The full version with labels would be +``` +@Minimization.mk ({** ℝ ** `x **} × {** ℝ ** `y **}) ℝ + (fun p => {** 40 * p.1 + 30 * p.2 ** `c₁ **}) + (fun p => {** 12 ≤ p.1 + p.2 ∧ 16 ≤ 2 * p.1 + p.2 ** `c₂ **}) +``` +where the `{ ** e ** n }` notation is as defined in `Syntax/Label.lean`. + +We also define how to delaborate it so that it can be pretty printed back to the custom syntax. +-/ + namespace CvxLean open Lean -/-- This alias for negation is used to mark a minimization problem to be pretty -printed as maximization. -/ +/-- This alias for negation is used to mark a minimization problem to be pretty printed as +maximization. -/ def maximizeNeg := @Neg.neg namespace Elab open Lean.Elab Lean.Elab.Term Lean.Meta Lean.Parser.Term -/-- -/ -def decomposeBracketedBinder : Syntax → TermElabM (Array (Syntax × Syntax)) := +/-- Helper function for `elabVars`. -/ +private def decomposeBracketedBinder : Syntax → TermElabM (Array (Syntax × Syntax)) := fun stx => match stx[0] with | `(bracketedBinderF|($ids* : $ty)) => return ids.map (·.raw, ty.raw) | `(ident|$id) => return #[(id.raw, (←`(_)).raw)] -/-- -/ +/-- Get the names and types of the variables after the `optimization` keyword. -/ def elabVars (idents : Array (TSyntax `CvxLean.Parser.minimizationVar)) : TermElabM (Array (Lean.Name × Expr)) := do let idents ← idents.concatMapM decomposeBracketedBinder let idents ← idents.mapM fun (id, ty) => do match id with | Syntax.ident _ _ val _ => return (val, ← Term.elabTerm ty none) - | _ => throwError "parser error: expected identifier got {id}." + | _ => throwParserError "expected identifier got {id}." return idents -/-- -/ +/-- Extract names of "let" expressions after `with`. -/ def preElabLetVars (letVars : Array (TSyntax `CvxLean.Parser.letVar)) : TermElabM (Array (Lean.Name × TSyntax `Lean.Parser.Term.letDecl)) := do letVars.mapM fun stx => @@ -39,28 +70,21 @@ def preElabLetVars (letVars : Array (TSyntax `CvxLean.Parser.letVar)) : | `(CvxLean.Parser.letVar| with $letD:letDecl) => match letD with | `(letDecl| $id:ident := $_) => return (id.getId, letD) - | _ => throwError "parser error: expected identified let declaration got {letD}." - | _ => throwError "parser error: expected let declaration got {stx}." + | _ => throwParserError "expected identified let declaration got {letD}." + | _ => throwParserError "expected let declaration got {stx}." -/-- -/ +/-- Extract names and terms of constraints. We do it in two steps so that we can insert lets if +needed. -/ def preElabConstraints (constraints : TSyntax `CvxLean.Parser.constraints) : - TermElabM (Array (Lean.Name × TSyntax `term)) := do + TermElabM (Array (Lean.Name × TSyntax `term)) := do match constraints with | `(CvxLean.Parser.constraints| $constrs*) => do constrs.mapM fun cDecl => match cDecl with | `(CvxLean.Parser.constraint| $id:ident : $c) => return (id.getId, c) | `(CvxLean.Parser.constraint| _ : $c) => return (Name.anonymous, c) - | _ => throwError "parser error: expected constraint got {cDecl}." - | _ => throwError "parser error: expected constraints got {constraints}." - --- TODO: move -/-- -/ -partial def _root_.Lean.Syntax.gatherIdents : Syntax → Array Lean.Name - | .missing => #[] - | .ident _ _ n _ => #[n] - | .atom _ _ => #[] - | .node _ _ stxs => stxs.foldl (init := #[]) fun acc stx => acc ++ stx.gatherIdents + | _ => throwParserError "expected constraint got {cDecl}." + | _ => throwParserError "expected constraints got {constraints}." macro_rules | `(optimization $idents* $minOrMax:minOrMax $obj) => @@ -68,51 +92,58 @@ macro_rules -- TODO: allow dependently typed variables? -/-- Elaborate "optimization" problem syntax. -/ +/-- Collect all identifiers that appear in some syntax. -/ +partial def _root_.Lean.Syntax.gatherIdents : Syntax → Array Lean.Name + | .missing => #[] + | .ident _ _ n _ => #[n] + | .atom _ _ => #[] + | .node _ _ stxs => stxs.foldl (init := #[]) fun acc stx => acc ++ stx.gatherIdents + +/-- Elaborate `optimization` problem syntax, building a term of type `Minimization D R`. -/ @[term_elab «optimization»] def elabOptmiziation : Term.TermElab := fun stx _expectedType? => do match stx with | `(optimization $idents* $lets:letVar* $minOrMax:minOrMax $obj subject to $constraints) => - -- Determine names and types of the variables. - let vars ← elabVars idents - -- Construct domain type. - let domain := Meta.composeDomain vars.data - -- Construct let vars syntax. - let letsStx ← preElabLetVars lets - -- Introduce FVar for the domain. - withLocalDeclD `p domain fun p => do - -- Introduce FVars for the variables - Meta.withDomainLocalDecls domain p fun xs prs => do - -- Elaborate objFun. - let mut objStx := obj - let objIdents := Syntax.gatherIdents objStx - if letsStx.size > 0 then - for (letVar, letD) in letsStx do - if letVar ∈ objIdents then - objStx := ← `(let $letD:letDecl; $objStx) - let mut obj := Expr.replaceFVars (← Term.elabTerm objStx.raw none) xs prs - -- Add `maximizeNeg` constant to mark maximization problems and to negate the objective. - let minOrMaxStx := minOrMax.raw[0]! - if minOrMaxStx.isOfKind `maximize then - obj ← mkAppM ``maximizeNeg #[obj] - else if !(minOrMaxStx.isOfKind `minimize) then - throwError "expected minimize or maximize, got: {minOrMaxStx.getKind}" - obj ← mkLambdaFVars #[p] obj - -- Elaborate constraints. - let constraints ← preElabConstraints constraints - let constraints ← constraints.mapM fun (n, cStx) => do - let mut cStx := cStx - let cIdents := Syntax.gatherIdents cStx + -- Determine names and types of the variables. + let vars ← elabVars idents + -- Construct domain type. + let domain := Meta.composeDomain vars.data + -- Construct let vars syntax. + let letsStx ← preElabLetVars lets + -- Introduce FVar for the domain. + withLocalDeclD `p domain fun p => do + -- Introduce FVars for the variables + Meta.withDomainLocalDecls domain p fun xs prs => do + -- Elaborate objFun. + let mut objStx := obj + let objIdents := Syntax.gatherIdents objStx if letsStx.size > 0 then for (letVar, letD) in letsStx do - if letVar ∈ cIdents then - cStx := ← `(let $letD:letDecl; $cStx) - return Meta.mkLabel n (← Term.elabTerm cStx none) - let constraints ← mkLambdaFVars #[p] $ - Expr.replaceFVars (Meta.composeAnd constraints.data) xs prs - -- Put it all together. - let res ← mkAppM ``Minimization.mk #[obj, constraints] - check res - return ← instantiateMVars res + if letVar ∈ objIdents then + objStx := ← `(let $letD:letDecl; $objStx) + let mut obj := Expr.replaceFVars (← Term.elabTerm objStx.raw none) xs prs + -- Add `maximizeNeg` constant to mark maximization problems and to negate the objective. + let minOrMaxStx := minOrMax.raw[0]! + if minOrMaxStx.isOfKind `maximize then + obj ← mkAppM ``maximizeNeg #[obj] + else if !(minOrMaxStx.isOfKind `minimize) then + throwParserError "expected minimize or maximize, got: {minOrMaxStx.getKind}" + obj ← mkLambdaFVars #[p] obj + -- Elaborate constraints. + let constraints ← preElabConstraints constraints + let constraints ← constraints.mapM fun (n, cStx) => do + let mut cStx := cStx + let cIdents := Syntax.gatherIdents cStx + if letsStx.size > 0 then + for (letVar, letD) in letsStx do + if letVar ∈ cIdents then + cStx := ← `(let $letD:letDecl; $cStx) + return Meta.mkLabel n (← Term.elabTerm cStx none) + let constraints ← mkLambdaFVars #[p] $ + Expr.replaceFVars (Meta.composeAnd constraints.data) xs prs + -- Put it all together. + let res ← mkAppM ``Minimization.mk #[obj, constraints] + check res + return ← instantiateMVars res | _ => throwUnsupportedSyntax end Elab @@ -121,17 +152,16 @@ namespace Delab open Lean Lean.PrettyPrinter.Delaborator SubExpr Meta -/-- -/ +/-- Show label in an expression. -/ def delabVar (e : Expr) : DelabM (Lean.Name × Term) := do match e with | Expr.mdata m e => - match m.get? `CvxLeanLabel with - | some (name : Lean.Name) => - return (name, ← descend e 0 do delab) - | none => Alternative.failure + match m.get? `CvxLeanLabel with + | some (name : Lean.Name) => return (name, ← descend e 0 do delab) + | none => Alternative.failure | _ => return (`_, ← delab) -/-- -/ +/-- Show labels (variable names) and terms (types) in a domain type. -/ partial def delabDomain : DelabM (List (Lean.Name × Term)) := do let e ← getExpr match e with @@ -141,27 +171,27 @@ partial def delabDomain : DelabM (List (Lean.Name × Term)) := do return stx1 :: stx2 | _ => return [← delabVar e] -/-- -/ +/-- Show constraint with label if it has one. -/ partial def delabConstraint : DelabM (TSyntax ``Parser.constraint) := do match ← getExpr with | Expr.mdata m e => match m.get? `CvxLeanLabel with | some (name : Lean.Name) => - return mkNode ``Parser.constraint #[(mkIdent name).raw, mkAtom ":", (← descend e 0 do delab).raw] + return mkNode ``Parser.constraint #[(mkIdent name).raw, mkAtom ":", (← descend e 0 do delab).raw] | none => Alternative.failure | _ => return (← `(Parser.constraint|_ : $(← delab))) -/-- -/ +/-- Show all constraints with their labels. -/ partial def delabConstraints : DelabM (List (TSyntax ``Parser.constraint)) := do let e ← getExpr match e with | Expr.app (Expr.app (Expr.const `And _) _l) _r => - let l : TSyntax _ ← withNaryArg 0 delabConstraint - let r : List (TSyntax _) ← withNaryArg 1 delabConstraints - return l :: r + let l : TSyntax _ ← withNaryArg 0 delabConstraint + let r : List (TSyntax _) ← withNaryArg 1 delabConstraints + return l :: r | _ => return [← delabConstraint] -/-- -/ +/-- Delaborate with variable names replaced. -/ def withDomainBinding [Inhabited α] (domain : Expr) (x : DelabM α) : DelabM α := do guard (← getExpr).isLambda withBindingBody' `p fun p => do @@ -170,7 +200,7 @@ def withDomainBinding [Inhabited α] (domain : Expr) (x : DelabM α) : DelabM α let e ← replaceProjections e p.fvarId! xs withExpr e do x -/-- -/ +/-- Show minimization problem using the custom syntax, with variable names and constraitn tags. -/ @[delab app] partial def delabMinimization : Delab := do if not (pp.optMinimization.get (← getOptions)) then Alternative.failure @@ -204,9 +234,6 @@ partial def delabMinimization : Delab := do `(optimization $idents* minimize $objFun subject to $constraints) | _ => Alternative.failure -set_option trace.Meta.debug true -set_option pp.rawOnError true - end Delab end CvxLean diff --git a/CvxLean/Syntax/OptimizationParam.lean b/CvxLean/Syntax/OptimizationParam.lean index acdd26fc..cc7b19f6 100644 --- a/CvxLean/Syntax/OptimizationParam.lean +++ b/CvxLean/Syntax/OptimizationParam.lean @@ -1,6 +1,11 @@ import Lean import Lean.Environment +/-! +Definitions tagged with `optimization_param` are treated differently by `solve` as it attempts to +extract its value. +-/ + section OptimizationParam open Lean diff --git a/CvxLean/Syntax/Options.lean b/CvxLean/Syntax/Options.lean index 652ac5ad..15935be8 100644 --- a/CvxLean/Syntax/Options.lean +++ b/CvxLean/Syntax/Options.lean @@ -1,13 +1,17 @@ import Lean +/-! +Pretty-priner options to control how to show minimization problems and labels. +-/ + register_option pp.optMinimization : Bool := { defValue := true group := "pp" - descr := "(pretty printer) pretty-print minimization problems" + descr := "(pretty printer) pretty-print minimization problems." } register_option pp.CvxLean.labels : Bool := { defValue := false group := "pp" - descr := "(pretty printer) display CvxLean labels" + descr := "(pretty printer) display CvxLean labels." } diff --git a/CvxLean/Syntax/Parser.lean b/CvxLean/Syntax/Parser.lean index b1271220..36d0a9ce 100644 --- a/CvxLean/Syntax/Parser.lean +++ b/CvxLean/Syntax/Parser.lean @@ -1,12 +1,17 @@ import Lean +/-! +This file defines how to parse variables, constraints, objective functions and full optimization +problems. Syntax matching these definitions is elaborated in `Syntax/Minimization.lean`. +-/ + namespace CvxLean open Lean namespace Parser -open Lean.Parser +open Parser def minimizationVar : Parser := leading_parser ((ident <|> Term.bracketedBinder) >> ppSpace) @@ -33,12 +38,7 @@ scoped syntax (name := optimization) ppGroup("optimization " minimizationVar* letVar*) ppLine ppGroup(minOrMax term) (ppLine ppGroup("subject to " constraints))? - ppLine - : term - --- By using the "scoped" keyword, the syntax only works when opening the CvxLean --- namespace, but when the namespace is not open, "optimization", "minimize", --- "maximize", and "subject to" will not be keywords and can be used as names. + ppLine : term end Syntax diff --git a/CvxLean/Syntax/Prod.lean b/CvxLean/Syntax/Prod.lean index a28f3c8d..73eb1029 100644 --- a/CvxLean/Syntax/Prod.lean +++ b/CvxLean/Syntax/Prod.lean @@ -1,35 +1,41 @@ import Lean -/-- Syntax for projections of `Prod`. For example, it allows us to write `x#3` - for `x.2.2.1` and `x#4` for `x.2.2.2`. -/ +/-! +Custom syntax for projections. +-/ + +/-- Syntax for projections of `Prod`. For example, it allows us to write `x#3` for `x.2.2.1` and +`x#4` for `x.2.2.2`. -/ syntax (name := prodField) term "#" Lean.Parser.numLit : term namespace Lean.Elab -open Meta +open Meta Term -@[term_elab prodField] def elabProdField : Term.TermElab := +@[term_elab prodField] +def elabProdField : TermElab := fun stx _ => do match stx with | `($e # $t) => do - match t.raw[0]! with - | Syntax.atom _ val => - let some num := val.toNat? - | throwUnsupportedSyntax - let e ← Term.elabTerm e none - let ty ← instantiateMVars <| ← inferType e - return ← mkProj e ty num - | _ => throwUnsupportedSyntax + match t.raw[0]! with + | Syntax.atom _ val => + let some num := val.toNat? | throwUnsupportedSyntax + let e ← elabTerm e none + let ty ← instantiateMVars <| ← inferType e + return ← mkProj e ty num + | _ => throwUnsupportedSyntax | _ => throwUnsupportedSyntax -where mkProj (e ty : Expr) (n : Nat) (recursive := false) : Term.TermElabM Expr := - match ty, n with - | (Expr.app (Expr.app (Expr.const ``Prod lvls) α) β), 1 => do - return mkApp3 (mkConst ``Prod.fst lvls) α β e - | _, 1 => if recursive then return e else throwUnsupportedSyntax - | (Expr.app (Expr.app (Expr.const ``Prod lvls) α) β), (n + 1) => do - let esnd := mkApp3 (mkConst ``Prod.snd lvls) α β e - mkProj esnd β n true - | _, _ => throwUnsupportedSyntax +where + mkProj (e ty : Expr) (n : Nat) (recursive := false) : TermElabM Expr := + match ty, n with + | (.app (.app (.const ``Prod lvls) α) β), 1 => do + return mkApp3 (mkConst ``Prod.fst lvls) α β e + | _, 1 => + if recursive then return e else throwUnsupportedSyntax + | (.app (.app (.const ``Prod lvls) α) β), (n + 1) => do + let esnd := mkApp3 (mkConst ``Prod.snd lvls) α β e + mkProj esnd β n true + | _, _ => throwUnsupportedSyntax end Lean.Elab @@ -40,18 +46,20 @@ open SubExpr @[delab app] partial def delabProdField : Delab := do let (stx, n) ← aux (← getExpr) return ← `($(⟨stx⟩) # $(quote n)) -where aux (top : Expr) (first : Option Bool := none) : DelabM (Syntax × Nat) := do - /- `first` tells us whether the outermost projection was `.1` (`some true`) or - `.2` (`some false`). If this is not a recursive call, `first` is `none`. -/ - match first, ← getExpr with - | none, Expr.app (Expr.app (Expr.app (Expr.const ``Prod.fst _) _) _) _ => do - withNaryArg 2 do aux top true - | _, Expr.app (Expr.app (Expr.app (Expr.const ``Prod.snd _) _) _) _ => do - withNaryArg 2 do - let (stx, n) ← aux top (first == some true) - return (stx, n + 1) - | none, _ => failure - | true, _ => return (← delab, 1) - | false, _ => if (← Meta.inferType top).getAppFn.isConstOf ``Prod then failure else return (← delab, 1) +where + aux (top : Expr) (first : Option Bool := none) : DelabM (Syntax × Nat) := do + /- `first` tells us whether the outermost projection was `.1` (`some true`) or + `.2` (`some false`). If this is not a recursive call, `first` is `none`. -/ + match first, ← getExpr with + | none, .app (.app (.app (.const ``Prod.fst _) _) _) _ => do + withNaryArg 2 do aux top true + | _, .app (.app (.app (.const ``Prod.snd _) _) _) _ => do + withNaryArg 2 do + let (stx, n) ← aux top (first == some true) + return (stx, n + 1) + | none, _ => failure + | true, _ => return (← delab, 1) + | false, _ => + if (← Meta.inferType top).getAppFn.isConstOf ``Prod then failure else return (← delab, 1) end Lean.PrettyPrinter.Delaborator From 939dac5512a0a255c2ea4cf8727b9e88fae6da23 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 15:53:44 -0500 Subject: [PATCH 33/52] fix: `p` and `q` in `EquivalenceExpr` are now `lhs` and `rhs` --- CvxLean/Tactic/Basic/CleanUpComp.lean | 8 ++++---- CvxLean/Tactic/Basic/RemoveConstr.lean | 4 ++-- CvxLean/Tactic/Basic/RenameConstrs.lean | 2 +- CvxLean/Tactic/Basic/RenameVars.lean | 4 ++-- CvxLean/Tactic/Basic/ReorderConstrs.lean | 2 +- CvxLean/Tactic/PreDCP/PreDCP.lean | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CvxLean/Tactic/Basic/CleanUpComp.lean b/CvxLean/Tactic/Basic/CleanUpComp.lean index 180cd01a..3ce1202a 100644 --- a/CvxLean/Tactic/Basic/CleanUpComp.lean +++ b/CvxLean/Tactic/Basic/CleanUpComp.lean @@ -12,9 +12,9 @@ namespace Meta def cleanUpCompAux (e : Expr) (name : String) : MetaM Expr := do match e with | .app (.app (.app (.app (.app (.const ``Function.comp lvls) ty₁) ty₂) ty₃) f₁) f₂ => do - let f₂ ← instantiateMVars <| ← whnf f₂ - let f₁ ← instantiateMVars f₁ - return mkApp5 (mkConst ``Function.comp lvls) ty₁ ty₂ ty₃ f₁ f₂ + let f₂ ← instantiateMVars <| ← whnf f₂ + let f₁ ← instantiateMVars f₁ + return mkApp5 (mkConst ``Function.comp lvls) ty₁ ty₂ ty₃ f₁ f₂ | _ => throwError "{name} not of the form '... ∘ ...'" /-- -/ @@ -23,7 +23,7 @@ def cleanUpCompBuilder : EquivalenceBuilder := fun eqvExpr g => g.withContext do let newObjFun ← cleanUpCompAux lhsMinExpr.objFun "objFun" let newConstraints ← cleanUpCompAux lhsMinExpr.constraints "constr" let rhsMinExpr := { lhsMinExpr with objFun := newObjFun, constraints := newConstraints } - let newEqvExpr := { eqvExpr with p := lhsMinExpr.toExpr, q := rhsMinExpr.toExpr } + let newEqvExpr := { eqvExpr with lhs := lhsMinExpr.toExpr, rhs := rhsMinExpr.toExpr } if (← isDefEq (mkMVar g) newEqvExpr.toExpr) then throwError "`clean_up_comp` error: Failed to unify the goal." let simpComp ← ({} : SimpTheorems).addDeclToUnfold ``Function.comp diff --git a/CvxLean/Tactic/Basic/RemoveConstr.lean b/CvxLean/Tactic/Basic/RemoveConstr.lean index b9d762b0..e7b703ec 100644 --- a/CvxLean/Tactic/Basic/RemoveConstr.lean +++ b/CvxLean/Tactic/Basic/RemoveConstr.lean @@ -85,14 +85,14 @@ def removeConstrBuilder (id : Name) (proof : Syntax) : EquivalenceBuilder := fun return ← mkLambdaFVars #[p] <| ← mkAppM ``Iff.intro #[oldImpliesNew, newImpliesOld] -- Prove by rewriting. - let D := eqvExpr.domainP + let D := eqvExpr.domainLHS let R := eqvExpr.codomain let RPreorder := eqvExpr.codomainPreorder let fullProof ← mkAppOptM ``Minimization.Equivalence.rewrite_constraints #[D, R, RPreorder, lhsMinExpr.objFun, lhsMinExpr.constraints, newConstrs, iffProof] check fullProof let rhs := { lhsMinExpr with constraints := newConstrs } - if !(← isDefEq eqvExpr.q rhs.toExpr) then + if !(← isDefEq eqvExpr.rhs rhs.toExpr) then throwError "`remove_constr` error: failed to unify RHS." if let _ :: _ ← g.apply fullProof then diff --git a/CvxLean/Tactic/Basic/RenameConstrs.lean b/CvxLean/Tactic/Basic/RenameConstrs.lean index 2089c1ed..3de5a34c 100644 --- a/CvxLean/Tactic/Basic/RenameConstrs.lean +++ b/CvxLean/Tactic/Basic/RenameConstrs.lean @@ -28,7 +28,7 @@ def renameConstrsBuilder (names : Array Lean.Name) : EquivalenceBuilder := fun e return ← mkLambdaFVars #[p] $ composeAnd constraints.toList let rhsMinExpr := { lhsMinExpr with constraints := newConstrs } - if !(← isDefEq eqvExpr.q rhsMinExpr.toExpr) then + if !(← isDefEq eqvExpr.rhs rhsMinExpr.toExpr) then throwError "`rename_constrs` error: Failed to unify the goal." -- Close goal by reflexivity. diff --git a/CvxLean/Tactic/Basic/RenameVars.lean b/CvxLean/Tactic/Basic/RenameVars.lean index 81ad3988..ea89a641 100644 --- a/CvxLean/Tactic/Basic/RenameVars.lean +++ b/CvxLean/Tactic/Basic/RenameVars.lean @@ -11,7 +11,7 @@ namespace Meta /-- -/ def renameVarsBuilder (names : Array Lean.Name) : EquivalenceBuilder := fun eqvExpr g => do let lhsMinExpr ← eqvExpr.toMinimizationExprLHS - let vars ← decomposeDomain (← instantiateMVars eqvExpr.domainP) + let vars ← decomposeDomain (← instantiateMVars eqvExpr.domainLHS) -- Create new domain. let renamedVars ← manipulateVars vars names.data @@ -29,7 +29,7 @@ def renameVarsBuilder (names : Array Lean.Name) : EquivalenceBuilder := fun eqvE codomain := lhsMinExpr.codomain, objFun := newObjFun, constraints := newConstrs } - if !(← isDefEq eqvExpr.q rhsMinExpr.toExpr) then + if !(← isDefEq eqvExpr.rhs rhsMinExpr.toExpr) then throwError "`rename_vars` error: Failed to unify the goal." -- Close goal by reflexivity. diff --git a/CvxLean/Tactic/Basic/ReorderConstrs.lean b/CvxLean/Tactic/Basic/ReorderConstrs.lean index aac97b04..1f1eb553 100644 --- a/CvxLean/Tactic/Basic/ReorderConstrs.lean +++ b/CvxLean/Tactic/Basic/ReorderConstrs.lean @@ -35,7 +35,7 @@ def reorderConstrsBuilder (ids : Array Name) : EquivalenceBuilder := fun eqvExpr mkLambdaFVars #[p] constrBody let rhsMinExpr := { lhsMinExpr with constraints := newConstrs } - if !(← isDefEq eqvExpr.q rhsMinExpr.toExpr) then + if !(← isDefEq eqvExpr.rhs rhsMinExpr.toExpr) then throwError "`reorder_constrs` error: Failed to unify the goal." if let [g] ← g.apply (mkConst ``Minimization.Equivalence.rewrite_constraints) then diff --git a/CvxLean/Tactic/PreDCP/PreDCP.lean b/CvxLean/Tactic/PreDCP/PreDCP.lean index 43a0b053..927304a4 100644 --- a/CvxLean/Tactic/PreDCP/PreDCP.lean +++ b/CvxLean/Tactic/PreDCP/PreDCP.lean @@ -63,7 +63,7 @@ def evalStep (step : EggRewrite) (vars params : List Name) (paramsDecls : List L let fvars := Array.mk <| vars.map (fun v => mkFVar (FVarId.mk v)) let paramsFvars := Array.mk <| params.map (fun v => mkFVar (FVarId.mk v)) let paramsDeclsIds := Array.mk <| paramsDecls.map (fun decl => mkFVar decl.fvarId) - let D ← instantiateMVars eqvExpr.domainP + let D ← instantiateMVars eqvExpr.domainLHS expectedExpr ← withLocalDeclD `p D fun p => do Meta.withDomainLocalDecls D p fun xs prs => do From dffe7a7365346782f831adaf72a39389df172f4f Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 15:57:08 -0500 Subject: [PATCH 34/52] chore: remove SciLean test --- CvxLean/Test/SciLean/SciLeanTest.lean | 30 --------------------------- lakefile.lean | 4 ++-- 2 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 CvxLean/Test/SciLean/SciLeanTest.lean diff --git a/CvxLean/Test/SciLean/SciLeanTest.lean b/CvxLean/Test/SciLean/SciLeanTest.lean deleted file mode 100644 index 72040741..00000000 --- a/CvxLean/Test/SciLean/SciLeanTest.lean +++ /dev/null @@ -1,30 +0,0 @@ -import SciLean - -#check SciLean.realToFloat -#check SciLean.isomorph `RealToFloat - -section SciLeanTest - -open SciLean - -@[simp high] -axiom isomorphicType_equiv_zero - : (IsomorphicType.equiv `RealToFloat) (0 : ℝ) = (0 : Float) - -lemma l - : SciLean.isomorph `RealToFloat (fun (p : Real × (Fin 2 → Real)) => Real.exp p.1 + p.2 0 ≤ 0 ∧ Real.exp p.1 + p.2 1 ≤ 0) - = - fun (p : Float × (Fin 2 → Float)) => Float.exp p.1 + p.2 0 ≤ 0 ∧ Float.exp p.1 + p.2 1 ≤ 0 := -by - conv => - lhs - ftrans; ftrans; simp - -def f := (SciLean.isomorph `RealToFloat <| - fun (p : Real × (Fin 2 → Real)) => Real.exp p.1 + p.2 0) - rewrite_by - ftrans; ftrans; simp - -#eval f (1, fun _ => 0) - -end SciLeanTest diff --git a/lakefile.lean b/lakefile.lean index 8cffb30f..abd643cb 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -5,12 +5,12 @@ require mathlib from git "https://github.com/leanprover-community/mathlib4" @ "c838d28fb418158125f1551662ef55113d22eeec" -meta if get_config? env = some "dev" then +meta if get_config? env = some "scilean" then require scilean from git "https://github.com/verified-optimization/SciLean" @ "master" -meta if get_config? env = some "dev" then +meta if get_config? env = some "doc" then require «doc-gen4» from git "https://github.com/verified-optimization/doc-gen4" @ "main" From ff996060db03c76c3ed613de98df55217164fc3c Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 16:01:14 -0500 Subject: [PATCH 35/52] chore: remove `CvxLean.code-workspace` --- CvxLean.code-workspace | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 CvxLean.code-workspace diff --git a/CvxLean.code-workspace b/CvxLean.code-workspace deleted file mode 100644 index 876a1499..00000000 --- a/CvxLean.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -} \ No newline at end of file From 1dc1be10d95b2a7ed38573a4a6c61b5d6a8a2ed4 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 16:01:59 -0500 Subject: [PATCH 36/52] chore: ignore `*.code-workspace` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 68c3b793..2aa5a114 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /egg-pre-dcp/*.dot /egg-pre-dcp/*.svg *.olean +*.code-workspace /playground /evaluation From e4a674cce8e631a4d02a9f23cd89bea2240bad11 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 16:20:34 -0500 Subject: [PATCH 37/52] wip: more hearbeats needed in `pre_dcp`, issue with new `norm_num_clean_up`? --- CvxLean/Test/PreDCP/AlmostDGP.lean | 1 + CvxLean/Test/PreDCP/DGP.lean | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CvxLean/Test/PreDCP/AlmostDGP.lean b/CvxLean/Test/PreDCP/AlmostDGP.lean index 0c9c1a17..84dae507 100644 --- a/CvxLean/Test/PreDCP/AlmostDGP.lean +++ b/CvxLean/Test/PreDCP/AlmostDGP.lean @@ -76,6 +76,7 @@ def agp3 := h6 : sqrt x ≤ x ^ (2 : ℝ) - 6 * y / z h7 : x * y = z +set_option maxHeartbeats 1000000 in time_cmd reduction reda3/dcpa3 : agp3 := by change_of_variables! (u) (x ↦ Real.exp u) change_of_variables! (v) (y ↦ Real.exp v) diff --git a/CvxLean/Test/PreDCP/DGP.lean b/CvxLean/Test/PreDCP/DGP.lean index 4363dce8..7442a60e 100644 --- a/CvxLean/Test/PreDCP/DGP.lean +++ b/CvxLean/Test/PreDCP/DGP.lean @@ -98,6 +98,7 @@ def gp4 := h6 : x ^ (2 : ℝ) + 6 * y / z <= sqrt x h7 : x * y = z +set_option maxHeartbeats 1000000 in time_cmd reduction red4/dcp4 : gp4 := by change_of_variables! (u) (x ↦ exp u) change_of_variables! (v) (y ↦ exp v) @@ -133,6 +134,7 @@ def gp5 := h6 : x ^ (2 : ℝ) + 3 * y / z ≤ sqrt x h7 : x / y = z ^ (2 : ℝ) +set_option maxHeartbeats 1000000 in time_cmd reduction red5/dcp5 : gp5 := by change_of_variables! (u) (x ↦ exp u) change_of_variables! (v) (y ↦ exp v) @@ -171,6 +173,7 @@ def gp6 := h8 : 5 ≤ d / w h9 : d / w ≤ 6 +set_option maxHeartbeats 1000000 in time_cmd reduction red6/dcp6 : gp6 := by change_of_variables! (h') (h ↦ exp h') change_of_variables! (w') (w ↦ exp w') @@ -208,7 +211,7 @@ def gp7 := h5 : x + 2 * y + 3 * z ≤ 1 h6 : (1 / 2) * x * y = 1 -set_option maxHeartbeats 1000000 in +set_option maxHeartbeats 10000000 in time_cmd reduction red7/dcp7 : gp7 := by change_of_variables! (u) (x ↦ exp u) change_of_variables! (v) (y ↦ exp v) @@ -248,7 +251,7 @@ def gp8 := h11 : 1.1 * r ≤ sqrt (A / (2 * 3.14159) + r ^ (2 : ℝ)) h12 : sqrt (A / (2 * 3.14159) + r ^ (2 : ℝ)) ≤ 10 -set_option maxHeartbeats 1000000 in +set_option maxHeartbeats 10000000 in time_cmd reduction red8/dcp8 : gp8 := by change_of_variables! (h') (h ↦ exp h') change_of_variables! (w') (w ↦ exp w') From 4df5a9b8ce10eb367d39b15e92a9588229df27f6 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 16:31:41 -0500 Subject: [PATCH 38/52] feat: `norm_num` while converting --- CvxLean/Examples/TrussDesign.lean | 3 +-- CvxLean/Tactic/PreDCP/Egg/ToExpr.lean | 35 ++++++++++++++------------- CvxLean/Tactic/PreDCP/PreDCP.lean | 1 - CvxLean/Test/PreDCP/AlmostDGP.lean | 2 -- CvxLean/Test/PreDCP/DGP.lean | 5 ---- CvxLean/Test/PreDCP/Stanford.lean | 1 - CvxLean/Test/PreDCP/Unit.lean | 1 - 7 files changed, 19 insertions(+), 29 deletions(-) diff --git a/CvxLean/Examples/TrussDesign.lean b/CvxLean/Examples/TrussDesign.lean index a04f6693..bcfe5134 100644 --- a/CvxLean/Examples/TrussDesign.lean +++ b/CvxLean/Examples/TrussDesign.lean @@ -56,8 +56,7 @@ instance : ChangeOfVariables simp; rw [mul_comm _ (R ^ 2 - r ^ 2), ← mul_div, div_self (by positivity), mul_one] ring_nf; exact sqrt_sq hR } -set_option maxHeartbeats 1000000 - +set_option maxHeartbeats 1000000 in equivalence' eqv₁/trussDesignGP (hmin hmax wmin wmax Rmax σ F₁ F₂ : ℝ) : trussDesign hmin hmax wmin wmax Rmax σ F₁ F₂ := by -- Apply key change of variables. diff --git a/CvxLean/Tactic/PreDCP/Egg/ToExpr.lean b/CvxLean/Tactic/PreDCP/Egg/ToExpr.lean index cb4fc1d9..0a0d22be 100644 --- a/CvxLean/Tactic/PreDCP/Egg/ToExpr.lean +++ b/CvxLean/Tactic/PreDCP/Egg/ToExpr.lean @@ -36,23 +36,24 @@ partial def EggTree.toExpr (vars params : List String) : Tree String String → | Tree.leaf s => match Json.Parser.num s.mkIterator with | Parsec.ParseResult.success _ res => do - -- NOTE: This is not ideal, but it works if we use norm_num all the - -- time. - let divisionRingToOfScientific := - mkApp2 (mkConst ``DivisionRing.toOfScientific [levelZero]) - (mkConst ``Real) - (mkConst ``Real.instDivisionRingReal) - let realOfScientific := - mkApp2 (mkConst ``OfScientific.ofScientific [levelZero]) - (mkConst ``Real) - divisionRingToOfScientific - let num := mkApp3 realOfScientific - (mkNatLit res.mantissa.natAbs) (Lean.toExpr true) (mkNatLit res.exponent) - if res.mantissa < 0 then - return mkApp3 (mkConst ``Neg.neg [levelZero]) - (mkConst ``Real) (mkConst ``Real.instNegReal) num - else - return num + -- NOTE: not ideal, but `norm_num` should get us to where we want. + let divisionRingToOfScientific := + mkApp2 (mkConst ``DivisionRing.toOfScientific [levelZero]) + (mkConst ``Real) + (mkConst ``Real.instDivisionRingReal) + let realOfScientific := + mkApp2 (mkConst ``OfScientific.ofScientific [levelZero]) + (mkConst ``Real) + divisionRingToOfScientific + let num := mkApp3 realOfScientific + (mkNatLit res.mantissa.natAbs) (Lean.toExpr true) (mkNatLit res.exponent) + let expr := + if res.mantissa < 0 then + mkApp3 (mkConst ``Neg.neg [levelZero]) (mkConst ``Real) (mkConst ``Real.instNegReal) num + else num + let simpResult ← + Mathlib.Meta.NormNum.deriveSimp (ctx := ← Simp.Context.mkDefault) (e := expr) + return simpResult.expr | _ => throwError "`pre_dcp` tree to Expr conversion error: unexpected num {s}." -- Variables. | Tree.node "var" #[Tree.leaf s] => diff --git a/CvxLean/Tactic/PreDCP/PreDCP.lean b/CvxLean/Tactic/PreDCP/PreDCP.lean index 927304a4..7e3f365f 100644 --- a/CvxLean/Tactic/PreDCP/PreDCP.lean +++ b/CvxLean/Tactic/PreDCP/PreDCP.lean @@ -77,7 +77,6 @@ def evalStep (step : EggRewrite) (vars params : List Name) (paramsDecls : List L -- Finally, apply the tactic that should solve all proof obligations. A mix of approaches using -- `norm_num` in combination with the tactic provided by the user for this particular rewrite. let tacStx : Syntax ← `(tactic| intros; - try { norm_num_clean_up; $tacStx <;> norm_num_simp_pow } <;> try { $tacStx <;> norm_num_simp_pow } <;> try { norm_num_simp_pow }) diff --git a/CvxLean/Test/PreDCP/AlmostDGP.lean b/CvxLean/Test/PreDCP/AlmostDGP.lean index 84dae507..f6856cc0 100644 --- a/CvxLean/Test/PreDCP/AlmostDGP.lean +++ b/CvxLean/Test/PreDCP/AlmostDGP.lean @@ -76,7 +76,6 @@ def agp3 := h6 : sqrt x ≤ x ^ (2 : ℝ) - 6 * y / z h7 : x * y = z -set_option maxHeartbeats 1000000 in time_cmd reduction reda3/dcpa3 : agp3 := by change_of_variables! (u) (x ↦ Real.exp u) change_of_variables! (v) (y ↦ Real.exp v) @@ -107,7 +106,6 @@ def agp4 := h2 : 0 < y h3 : x * y ≤ 2 - x - y -set_option trace.Meta.debug true time_cmd reduction reda4/dcpa4 : agp4 := by change_of_variables! (u) (x ↦ Real.exp u) change_of_variables! (v) (y ↦ Real.exp v) diff --git a/CvxLean/Test/PreDCP/DGP.lean b/CvxLean/Test/PreDCP/DGP.lean index 7442a60e..eb5c4542 100644 --- a/CvxLean/Test/PreDCP/DGP.lean +++ b/CvxLean/Test/PreDCP/DGP.lean @@ -98,7 +98,6 @@ def gp4 := h6 : x ^ (2 : ℝ) + 6 * y / z <= sqrt x h7 : x * y = z -set_option maxHeartbeats 1000000 in time_cmd reduction red4/dcp4 : gp4 := by change_of_variables! (u) (x ↦ exp u) change_of_variables! (v) (y ↦ exp v) @@ -134,7 +133,6 @@ def gp5 := h6 : x ^ (2 : ℝ) + 3 * y / z ≤ sqrt x h7 : x / y = z ^ (2 : ℝ) -set_option maxHeartbeats 1000000 in time_cmd reduction red5/dcp5 : gp5 := by change_of_variables! (u) (x ↦ exp u) change_of_variables! (v) (y ↦ exp v) @@ -173,7 +171,6 @@ def gp6 := h8 : 5 ≤ d / w h9 : d / w ≤ 6 -set_option maxHeartbeats 1000000 in time_cmd reduction red6/dcp6 : gp6 := by change_of_variables! (h') (h ↦ exp h') change_of_variables! (w') (w ↦ exp w') @@ -211,7 +208,6 @@ def gp7 := h5 : x + 2 * y + 3 * z ≤ 1 h6 : (1 / 2) * x * y = 1 -set_option maxHeartbeats 10000000 in time_cmd reduction red7/dcp7 : gp7 := by change_of_variables! (u) (x ↦ exp u) change_of_variables! (v) (y ↦ exp v) @@ -251,7 +247,6 @@ def gp8 := h11 : 1.1 * r ≤ sqrt (A / (2 * 3.14159) + r ^ (2 : ℝ)) h12 : sqrt (A / (2 * 3.14159) + r ^ (2 : ℝ)) ≤ 10 -set_option maxHeartbeats 10000000 in time_cmd reduction red8/dcp8 : gp8 := by change_of_variables! (h') (h ↦ exp h') change_of_variables! (w') (w ↦ exp w') diff --git a/CvxLean/Test/PreDCP/Stanford.lean b/CvxLean/Test/PreDCP/Stanford.lean index a0bd6859..cc135f4a 100644 --- a/CvxLean/Test/PreDCP/Stanford.lean +++ b/CvxLean/Test/PreDCP/Stanford.lean @@ -24,7 +24,6 @@ def e367 := h3 : 0.001 ≤ x3 h4 : 0.001 ≤ x4 -set_option maxHeartbeats 1000000 in time_cmd reduction red367/dcp367 : e367 := by pre_dcp diff --git a/CvxLean/Test/PreDCP/Unit.lean b/CvxLean/Test/PreDCP/Unit.lean index f63b52d4..311460c5 100644 --- a/CvxLean/Test/PreDCP/Unit.lean +++ b/CvxLean/Test/PreDCP/Unit.lean @@ -1191,7 +1191,6 @@ def expSubRevConstr := hx : 0 < x h : exp (2 * x) / exp x ≤ 1 -set_option maxHeartbeats 1000000 in time_cmd equivalence expSubRevConstrRed/expSubRevConstrAuto : expSubRevConstr := by pre_dcp From 3919d13298647d03974264367f58859b4bffa0e6 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 16:47:30 -0500 Subject: [PATCH 39/52] wip: generate docs --- lakefile.lean | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lakefile.lean b/lakefile.lean index abd643cb..0916492a 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -1,6 +1,9 @@ import Lake + open System Lake DSL +package CvxLean + require mathlib from git "https://github.com/leanprover-community/mathlib4" @ "c838d28fb418158125f1551662ef55113d22eeec" @@ -12,28 +15,25 @@ require scilean from git meta if get_config? env = some "doc" then require «doc-gen4» from git - "https://github.com/verified-optimization/doc-gen4" @ + "https://github.com/verified-optimization/doc-gen4.git" @ "main" -package CvxLean - @[default_target] -lean_lib CvxLeanTest +lean_lib CvxLean @[default_target] -lean_lib CvxLean +lean_lib CvxLeanTest -def compileCargo (name : String) (manifestFile : FilePath) - (cargo : FilePath := "cargo") : LogIO Unit := do +def compileCargo (name : String) (manifestFile : FilePath) (cargo : FilePath := "cargo") : + LogIO Unit := do logInfo s!"Creating {name}" proc { cmd := cargo.toString args := #["build", "--release", "--manifest-path", manifestFile.toString] } -def buildCargo (targetFile : FilePath) (manifestFile : FilePath) -(targetDest : FilePath) (oFileJobs : Array (BuildJob FilePath)) : -SchedulerM (BuildJob FilePath) := +def buildCargo (targetFile : FilePath) (manifestFile : FilePath) (targetDest : FilePath) + (oFileJobs : Array (BuildJob FilePath)) : SchedulerM (BuildJob FilePath) := let name := targetFile.fileName.getD targetFile.toString buildFileAfterDepArray targetFile oFileJobs fun _ => do compileCargo name manifestFile From bdedb7ccb5c1761640fb36f18676a7d1d5a74fed Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 17:06:49 -0500 Subject: [PATCH 40/52] fix: labels in constraint in doc --- CvxLean/Syntax/Minimization.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CvxLean/Syntax/Minimization.lean b/CvxLean/Syntax/Minimization.lean index 346ca79a..c71da6a8 100644 --- a/CvxLean/Syntax/Minimization.lean +++ b/CvxLean/Syntax/Minimization.lean @@ -26,8 +26,8 @@ Minimization.mk The full version with labels would be ``` @Minimization.mk ({** ℝ ** `x **} × {** ℝ ** `y **}) ℝ - (fun p => {** 40 * p.1 + 30 * p.2 ** `c₁ **}) - (fun p => {** 12 ≤ p.1 + p.2 ∧ 16 ≤ 2 * p.1 + p.2 ** `c₂ **}) + (fun p => 40 * p.1 + 30 * p.2) + (fun p => {** 12 ≤ p.1 + p.2 ** `c₁ **} ∧ {** 16 ≤ 2 * p.1 + p.2 ** `c₂ **}) ``` where the `{ ** e ** n }` notation is as defined in `Syntax/Label.lean`. From 8b1548b23f6f4691f32742801e78f3889ec8ee35 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 17:55:46 -0500 Subject: [PATCH 41/52] style: no auto-implicit --- CvxLean/Command/Solve/Mosek/CBF.lean | 14 +++++++------- CvxLean/Command/Solve/Mosek/Sol.lean | 4 ++++ CvxLean/Lib/Equivalence.lean | 2 ++ CvxLean/Lib/Math/Data/Array.lean | 8 +++++--- CvxLean/Lib/Math/Data/Fin.lean | 2 +- CvxLean/Lib/Math/Data/List.lean | 2 ++ CvxLean/Lib/Math/Data/Matrix.lean | 16 ++++++++++------ CvxLean/Lib/Math/Data/Real.lean | 2 +- CvxLean/Lib/Math/Data/Vec.lean | 8 +++++--- .../Lib/Math/LinearAlgebra/Matrix/PosDef.lean | 2 +- CvxLean/Lib/Math/LogSumExp.lean | 2 +- CvxLean/Lib/Reduction.lean | 2 ++ CvxLean/Meta/Minimization.lean | 6 +++--- CvxLean/Meta/Util/Meta.lean | 3 ++- CvxLean/Syntax/Label.lean | 4 ++-- CvxLean/Syntax/Minimization.lean | 5 +++-- CvxLean/Syntax/Options.lean | 6 +++++- CvxLean/Tactic/DCP/AtomLibrary/Fns/Abs.lean | 2 +- CvxLean/Tactic/DCP/Atoms.lean | 2 +- CvxLean/Tactic/DCP/DiscrTree.lean | 10 ++++++++-- CvxLean/Tactic/DCP/OC.lean | 4 +++- CvxLean/Tactic/DCP/Tree.lean | 18 ++++++++++-------- CvxLean/Tactic/PreDCP/Egg/Runner.lean | 2 ++ CvxLean/Tactic/PreDCP/Egg/Sexp.lean | 16 ++++++++-------- CvxLean/Test/DCP/Label.lean | 2 +- lakefile.lean | 10 ++++++++-- 26 files changed, 98 insertions(+), 56 deletions(-) diff --git a/CvxLean/Command/Solve/Mosek/CBF.lean b/CvxLean/Command/Solve/Mosek/CBF.lean index ad684b96..d9eaea96 100644 --- a/CvxLean/Command/Solve/Mosek/CBF.lean +++ b/CvxLean/Command/Solve/Mosek/CBF.lean @@ -74,7 +74,7 @@ def empty : ConeProduct := ConeProduct.mk 0 0 [] def isEmpty (cp : ConeProduct) : Prop := cp.k = 0 -instance : Decidable (isEmpty cp) := Nat.decEq _ _ +instance {cp} : Decidable (isEmpty cp) := Nat.decEq _ _ def addCone (cp : ConeProduct) (c : Cone) : ConeProduct := ConeProduct.mk (cp.n + c.dim) (cp.k + 1) (cp.cones.concat c) @@ -97,7 +97,7 @@ def empty : DimensionList := DimensionList.mk 0 [] def isEmpty (dl : DimensionList) : Prop := dl.n = 0 -instance : Decidable (isEmpty dl) := Nat.decEq _ _ +instance {dl} : Decidable (isEmpty dl) := Nat.decEq _ _ def addDimension (dl : DimensionList) (d : Nat) : DimensionList := DimensionList.mk (dl.n + 1) (dl.dimensions.concat d) @@ -119,7 +119,7 @@ def empty : EncodedValue := EncodedValue.mk none def isEmpty (ev : EncodedValue) : Prop := ev.value.isNone -instance : Decidable (isEmpty ev) := decEq _ _ +instance {ev} : Decidable (isEmpty ev) := decEq _ _ end EncodedValue @@ -154,7 +154,7 @@ def empty : EncodedVector := EncodedVector.mk 0 [] def isEmpty (ev : EncodedVector) : Prop := ev.n = 0 -instance : Decidable (isEmpty ev) := Nat.decEq _ _ +instance {ev} : Decidable (isEmpty ev) := Nat.decEq _ _ def addEntry (ev : EncodedVector) (eve : EncodedVectorEntry) : EncodedVector := if eve.value < 0 || eve.value > 0 then @@ -215,7 +215,7 @@ def empty : EncodedMatrix := EncodedMatrix.mk 0 [] def isEmpty (em : EncodedMatrix) : Prop := em.n = 0 -instance : Decidable (isEmpty em) := Nat.decEq _ _ +instance {em} : Decidable (isEmpty em) := Nat.decEq _ _ def addEntry (em : EncodedMatrix) (eme : EncodedMatrixEntry) : EncodedMatrix := if eme.value < 0 || eme.value > 0 then @@ -281,7 +281,7 @@ def empty : EncodedMatrixList := EncodedMatrixList.mk 0 [] def isEmpty (eml : EncodedMatrixList) : Prop := eml.n = 0 -instance : Decidable (isEmpty eml) := Nat.decEq _ _ +instance {eml} : Decidable (isEmpty eml) := Nat.decEq _ _ def addEntry (eml : EncodedMatrixList) (emle : EncodedMatrixListEntry) : EncodedMatrixList := if emle.value > 0 || emle.value < 0 then @@ -351,7 +351,7 @@ def empty : EncodedMatrixListList := EncodedMatrixListList.mk 0 [] def isEmpty (emll : EncodedMatrixListList) : Prop := emll.n = 0 -instance : Decidable (isEmpty emll) := Nat.decEq _ _ +instance {emll} : Decidable (isEmpty emll) := Nat.decEq _ _ def addEntry (emll : EncodedMatrixListList) (emlle : EncodedMatrixListListEntry) : EncodedMatrixListList := diff --git a/CvxLean/Command/Solve/Mosek/Sol.lean b/CvxLean/Command/Solve/Mosek/Sol.lean index b75fa012..1b39eec4 100644 --- a/CvxLean/Command/Solve/Mosek/Sol.lean +++ b/CvxLean/Command/Solve/Mosek/Sol.lean @@ -198,6 +198,8 @@ private def optionFloat : Parsec (Option Float) := section Summary +variable {α} + /-- Skip the beginning of a summary line. -/ private def summaryIdentifier (id : String) : Parsec Unit := skipString id *> ws' *> skipChar ':' *> ws' @@ -228,6 +230,8 @@ end Summary section Constraints +variable {α} + /-- Skip the title in the constraints section. -/ private def constraintsTitle : Parsec Unit := skipString "CONSTRAINTS" <* ws' <* endOfLine diff --git a/CvxLean/Lib/Equivalence.lean b/CvxLean/Lib/Equivalence.lean index 1dcc4069..5bc28ce2 100644 --- a/CvxLean/Lib/Equivalence.lean +++ b/CvxLean/Lib/Equivalence.lean @@ -202,6 +202,8 @@ end StrongEquivalence namespace Equivalence +variable {f : D → R} {cs : D → Prop} + /- Mapping the objective function by monotonic functions yields an equivalence. Also, mapping the whole domain by a function with a right inverse. -/ section Maps diff --git a/CvxLean/Lib/Math/Data/Array.lean b/CvxLean/Lib/Math/Data/Array.lean index a23b44ba..b3cdba82 100644 --- a/CvxLean/Lib/Math/Data/Array.lean +++ b/CvxLean/Lib/Math/Data/Array.lean @@ -3,7 +3,9 @@ import Mathlib.Algebra.Group.Defs open Lean -instance {α} : Inhabited (Array α) where +variable {α β γ} + +instance : Inhabited (Array α) where default := Array.empty def Array.zeros [Zero α] (n : Nat) : Array α := @@ -19,8 +21,8 @@ def Array.filterIdx (as : Array α) (f : Nat → Bool) := Id.run do bs := bs.push (as.get ⟨i, h.2⟩) return bs -@[specialize] def Array.zipWithMAux (f : α → β → MetaM γ) - (as : Array α) (bs : Array β) (i : Nat) (cs : Array γ) : +@[specialize] +def Array.zipWithMAux (f : α → β → MetaM γ) (as : Array α) (bs : Array β) (i : Nat) (cs : Array γ) : MetaM (Array γ) := do if h : i < as.size then let a := as.get ⟨i, h⟩; diff --git a/CvxLean/Lib/Math/Data/Fin.lean b/CvxLean/Lib/Math/Data/Fin.lean index 65b44275..33c8c0dd 100644 --- a/CvxLean/Lib/Math/Data/Fin.lean +++ b/CvxLean/Lib/Math/Data/Fin.lean @@ -10,7 +10,7 @@ variable {n : ℕ} instance [i : Fact (0 < n)] : OfNat (Fin n) 0 := ⟨⟨0, i.out⟩⟩ -instance {n m : ℕ} : OfNat (Fin n.succ ⊕ Fin m.succ) (x) where +instance {n m x : ℕ} : OfNat (Fin n.succ ⊕ Fin m.succ) (x) where ofNat := if x <= n then Sum.inl (Fin.ofNat x) else Sum.inr (Fin.ofNat (x - n.succ)) end Fin diff --git a/CvxLean/Lib/Math/Data/List.lean b/CvxLean/Lib/Math/Data/List.lean index cf6fbd4b..ddb8100d 100644 --- a/CvxLean/Lib/Math/Data/List.lean +++ b/CvxLean/Lib/Math/Data/List.lean @@ -2,6 +2,8 @@ import Mathlib.Algebra.Ring.Basic namespace List +variable {α} + def findIdx' (p : α → Prop) [DecidablePred p] : List α → Option ℕ | [] => none | (a::l) => if p a then some 0 else (findIdx' p l).map Nat.succ diff --git a/CvxLean/Lib/Math/Data/Matrix.lean b/CvxLean/Lib/Math/Data/Matrix.lean index ca1ce1a7..0b453671 100644 --- a/CvxLean/Lib/Math/Data/Matrix.lean +++ b/CvxLean/Lib/Math/Data/Matrix.lean @@ -13,6 +13,8 @@ here, which are used by the real-to-float procedure. namespace Matrix +variable {α} {m n} + def const (k : α) : Matrix m n α := fun _ _ => k @@ -25,15 +27,15 @@ instance [Preorder α] : Preorder (Matrix m n α) where def abs (A : Matrix m n ℝ) : Matrix m n ℝ := fun i j => |A i j| -theorem vecCons_zero_zero {n} [Zero R] : vecCons (0 : R) (0 : Fin n → R) = 0 := by +theorem vecCons_zero_zero {n} [Zero α] : vecCons (0 : α) (0 : Fin n → α) = 0 := by ext i ; refine' Fin.cases _ _ i <;> simp [vecCons] -theorem smul_vecCons {n} [Zero R] [SMulZeroClass ℝ R] (x : ℝ) (y : R) (v : Fin n → R) : +theorem smul_vecCons {n} [Zero α] [SMulZeroClass ℝ α] (x : ℝ) (y : α) (v : Fin n → α) : x • vecCons y v = vecCons (x • y) (x • v) := by ext i ; refine' Fin.cases _ _ i <;> simp [vecCons] -theorem add_vecCons {n} [Zero R] [SMulZeroClass ℝ R] [Add R] (x : R) (v : Fin n → R) (y : R) - (w : Fin n → R) : vecCons x v + vecCons y w = vecCons (x + y) (v + w) := by +theorem add_vecCons {n} [Zero α] [SMulZeroClass ℝ α] [Add α] (x : α) (v : Fin n → α) (y : α) + (w : Fin n → α) : vecCons x v + vecCons y w = vecCons (x + y) (v + w) := by ext i ; refine' Fin.cases _ _ i <;> simp [vecCons] open BigOperators @@ -48,6 +50,8 @@ namespace Computable Computable operations on matrices used in `RealToFloat`. -/ +variable {n m l : ℕ} + def toArray (A : Matrix (Fin n) (Fin n) Float) : Array (Array Float) := (Array.range n).map <| fun i => if h : i < n then Vec.Computable.toArray (A ⟨i, h⟩) else Array.mk <| List.replicate n 0 @@ -63,10 +67,10 @@ def mulVec (M : Matrix (Fin m) (Fin n) Float) (v : (Fin n) → Float) : Fin m def vecMul (x : Fin m → Float) (M : Matrix (Fin m) (Fin n) Float) : Fin n → Float := fun j => x ⬝ᵥᶜ fun i => M i j -def transpose (M : Matrix m n α) : Matrix n m α := +def transpose {m n} (M : Matrix m n α) : Matrix n m α := fun i j => M j i -def diag (M : Matrix n n α) : n → α := +def diag {n} (M : Matrix n n α) : n → α := fun i => M i i def mul (M : Matrix (Fin l) (Fin m) Float) (N : Matrix (Fin m) (Fin n) Float) : diff --git a/CvxLean/Lib/Math/Data/Real.lean b/CvxLean/Lib/Math/Data/Real.lean index b7a26cd6..127f4ab5 100644 --- a/CvxLean/Lib/Math/Data/Real.lean +++ b/CvxLean/Lib/Math/Data/Real.lean @@ -16,7 +16,7 @@ some components of our automated procedures such as pattern-matching in `dcp` or noncomputable instance (priority := high) : HPow ℝ ℝ ℝ := by infer_instance @[default_instance] -noncomputable instance (priority := high) : HPow (Fin n → ℝ) ℝ (Fin n → ℝ) := by infer_instance +noncomputable instance (priority := high) {n} : HPow (Fin n → ℝ) ℝ (Fin n → ℝ) := by infer_instance section Functions diff --git a/CvxLean/Lib/Math/Data/Vec.lean b/CvxLean/Lib/Math/Data/Vec.lean index 9b31d6f4..b03958de 100644 --- a/CvxLean/Lib/Math/Data/Vec.lean +++ b/CvxLean/Lib/Math/Data/Vec.lean @@ -10,19 +10,21 @@ versions of vector operations are defined here, which are used by the real-to-fl namespace Vec -noncomputable instance (priority := high) : NormedAddCommGroup (Fin n → ℝ) := +noncomputable instance (priority := high) {n} : NormedAddCommGroup (Fin n → ℝ) := PiLp.normedAddCommGroup 2 _ -noncomputable instance (priority := high) : NormedAddGroup (Fin n → ℝ) := +noncomputable instance (priority := high) {n} : NormedAddGroup (Fin n → ℝ) := (PiLp.normedAddCommGroup 2 _).toNormedAddGroup -noncomputable instance (priority := high) : InnerProductSpace ℝ (Fin n → ℝ) := +noncomputable instance (priority := high) {n} : InnerProductSpace ℝ (Fin n → ℝ) := PiLp.innerProductSpace _ /-! Named functions on vectors. Each of them corresponds to an atom. -/ +universe u v w + variable {m : Type u} {n : Type v} [Fintype m] [Fintype n] {α : Type w} /-- See `CvxLean.Tactic.DCP.AtomLibrary.Fns.Abs`. -/ diff --git a/CvxLean/Lib/Math/LinearAlgebra/Matrix/PosDef.lean b/CvxLean/Lib/Math/LinearAlgebra/Matrix/PosDef.lean index e0388f23..a1270e84 100644 --- a/CvxLean/Lib/Math/LinearAlgebra/Matrix/PosDef.lean +++ b/CvxLean/Lib/Math/LinearAlgebra/Matrix/PosDef.lean @@ -82,7 +82,7 @@ lemma IsHermitian.nonsingular_inv [DecidableEq n] {M : Matrix n n 𝕜} (hM : M. refine' (Matrix.inv_eq_right_inv _).symm rw [conjTranspose_nonsing_inv, hM.eq, mul_nonsing_inv _ hMdet] -lemma conj_symm {M : Matrix n n 𝕜} (hM : M.IsHermitian) : +lemma conj_symm {x : n → 𝕜} {M : Matrix n n 𝕜} (hM : M.IsHermitian) : star (star x ⬝ᵥ mulVec M x) = star x ⬝ᵥ mulVec M x := by nth_rewrite 1 [star_dotProduct, star_mulVec] rw [star_star, dotProduct_mulVec, hM.eq] diff --git a/CvxLean/Lib/Math/LogSumExp.lean b/CvxLean/Lib/Math/LogSumExp.lean index f25e8885..addaaf9a 100644 --- a/CvxLean/Lib/Math/LogSumExp.lean +++ b/CvxLean/Lib/Math/LogSumExp.lean @@ -9,7 +9,7 @@ namespace Vec open Real -lemma sum_exp_eq_sum_div (x : Fin n → ℝ) (t : ℝ) : +lemma sum_exp_eq_sum_div {n} (x : Fin n → ℝ) (t : ℝ) : Vec.sum (Vec.exp (x - Vec.const n t)) = (Vec.sum (Vec.exp x)) / (Real.exp t) := by unfold Vec.sum rw [Finset.sum_div] diff --git a/CvxLean/Lib/Reduction.lean b/CvxLean/Lib/Reduction.lean index 31ae0fb6..cbf5778c 100644 --- a/CvxLean/Lib/Reduction.lean +++ b/CvxLean/Lib/Reduction.lean @@ -61,6 +61,8 @@ instance : Trans (@Reduction D E R _) (@Equivalence E F R _) (@Reduction D F R _ instance : Trans (@Equivalence D E R _) (@Reduction E F R _) (@Reduction D F R _) := { trans := fun E R => Reduction.trans (ofEquivalence E) R } +variable {f : D → R} {cs : D → Prop} + section Maps /-- Weaker version of `Equivalence.map_objFun`. For a reduction we only need the map to be diff --git a/CvxLean/Meta/Minimization.lean b/CvxLean/Meta/Minimization.lean index 04d66a47..e4fd0173 100644 --- a/CvxLean/Meta/Minimization.lean +++ b/CvxLean/Meta/Minimization.lean @@ -4,7 +4,7 @@ import CvxLean.Lib.Minimization /-! Infrastructure to work with the `Minimization` and `Solution` types as expressions. Crucially, we -define functions to compose and decompose domans and constraints, taking into account their names. +define functions to compose and decompose domains and constraints, taking into account their names. -/ namespace CvxLean @@ -13,7 +13,7 @@ namespace Meta open Lean Lean.Meta -variable [MonadControlT MetaM m] [Monad m] +variable {m} [MonadControlT MetaM m] [Monad m] /-- Structure holding all the components in the `Minimization` type. -/ structure MinimizationExpr where @@ -132,7 +132,7 @@ partial def mkProjections (domain : Expr) (p : Expr) : m (List (Name × Expr × /-- Introduce let declarations into the context, corresponding to the projections of `p`. The argument `domain` specifies the type of `p`. CvxLeanLabels in the `domain` are used to determine the names of the new variables. -/ -def withDomainLocalDecls [Inhabited α] (domain : Expr) (p : Expr) +def withDomainLocalDecls {α} [Inhabited α] (domain : Expr) (p : Expr) (x : Array Expr → Array Expr → m α) : m α := do let pr := (← mkProjections domain p).toArray withLetDecls (pr.map fun (n, ty, val) => (n, fun _ => return ty, fun _ => return val)) fun xs => do diff --git a/CvxLean/Meta/Util/Meta.lean b/CvxLean/Meta/Util/Meta.lean index 3d9c0674..d1a787aa 100644 --- a/CvxLean/Meta/Util/Meta.lean +++ b/CvxLean/Meta/Util/Meta.lean @@ -6,7 +6,8 @@ Helper `Meta`-related functions. namespace Lean.Meta -variable [MonadControlT MetaM m] [Monad m] +variable {m} [MonadControlT MetaM m] [Monad m] +variable {α} /-- Open lambda-expression by introducing a new local declaration. Similar to `lambdaTelescope`, but for only one variable. -/ diff --git a/CvxLean/Syntax/Label.lean b/CvxLean/Syntax/Label.lean index af458ecd..3817d016 100644 --- a/CvxLean/Syntax/Label.lean +++ b/CvxLean/Syntax/Label.lean @@ -21,7 +21,7 @@ type and names of constraints. -/ def mkLabel (name : Name) (e : Expr) : Expr := mkMData (MData.empty.setName `CvxLeanLabel name) e -variable [MonadControlT MetaM m] [Monad m] +variable {m} [MonadControlT MetaM m] [Monad m] /-- Get the name and expression from metadata labelled with `CvxLeanLabel`. -/ def decomposeLabel (e : Expr) : m (Name × Expr) := do @@ -63,7 +63,7 @@ open Lean.PrettyPrinter.Delaborator SubExpr /-- Display labelled terms using the `{** term ** name **}` syntax. -/ @[delab mdata] def delabNamedConstraint : Delab := do -- Omit delaboration if pretty printing option is disabled. - if not (pp.CvxLean.labels.get (← getOptions)) then failure + if not (pp.labels.get (← getOptions)) then failure -- Check if `CvxLeanLabel` metadata is attached to current expression. let Expr.mdata m e ← getExpr | unreachable! match m.get? `CvxLeanLabel with diff --git a/CvxLean/Syntax/Minimization.lean b/CvxLean/Syntax/Minimization.lean index c71da6a8..1896ed86 100644 --- a/CvxLean/Syntax/Minimization.lean +++ b/CvxLean/Syntax/Minimization.lean @@ -177,7 +177,8 @@ partial def delabConstraint : DelabM (TSyntax ``Parser.constraint) := do | Expr.mdata m e => match m.get? `CvxLeanLabel with | some (name : Lean.Name) => - return mkNode ``Parser.constraint #[(mkIdent name).raw, mkAtom ":", (← descend e 0 do delab).raw] + return mkNode ``Parser.constraint + #[(mkIdent name).raw, mkAtom ":", (← descend e 0 do delab).raw] | none => Alternative.failure | _ => return (← `(Parser.constraint|_ : $(← delab))) @@ -192,7 +193,7 @@ partial def delabConstraints : DelabM (List (TSyntax ``Parser.constraint)) := do | _ => return [← delabConstraint] /-- Delaborate with variable names replaced. -/ -def withDomainBinding [Inhabited α] (domain : Expr) (x : DelabM α) : DelabM α := do +def withDomainBinding {α} [Inhabited α] (domain : Expr) (x : DelabM α) : DelabM α := do guard (← getExpr).isLambda withBindingBody' `p fun p => do withDomainLocalDecls domain p fun xs _prs => do diff --git a/CvxLean/Syntax/Options.lean b/CvxLean/Syntax/Options.lean index 15935be8..090aebf2 100644 --- a/CvxLean/Syntax/Options.lean +++ b/CvxLean/Syntax/Options.lean @@ -4,14 +4,18 @@ import Lean Pretty-priner options to control how to show minimization problems and labels. -/ +namespace CvxLean + register_option pp.optMinimization : Bool := { defValue := true group := "pp" descr := "(pretty printer) pretty-print minimization problems." } -register_option pp.CvxLean.labels : Bool := { +register_option pp.labels : Bool := { defValue := false group := "pp" descr := "(pretty printer) display CvxLean labels." } + +end CvxLean diff --git a/CvxLean/Tactic/DCP/AtomLibrary/Fns/Abs.lean b/CvxLean/Tactic/DCP/AtomLibrary/Fns/Abs.lean index f5736ac2..ecad63c3 100644 --- a/CvxLean/Tactic/DCP/AtomLibrary/Fns/Abs.lean +++ b/CvxLean/Tactic/DCP/AtomLibrary/Fns/Abs.lean @@ -25,7 +25,7 @@ feasibility (c_neg : by unfold posOrthCone rw [←neg_le_iff_add_nonneg'] - exact neg_abs_le_self x) + exact neg_abs_le x) optimality by apply abs_le.2 rw [←sub_nonneg, sub_neg_eq_add, add_comm, ←sub_nonneg (b := x)] diff --git a/CvxLean/Tactic/DCP/Atoms.lean b/CvxLean/Tactic/DCP/Atoms.lean index 5d4746d8..50f775ff 100644 --- a/CvxLean/Tactic/DCP/Atoms.lean +++ b/CvxLean/Tactic/DCP/Atoms.lean @@ -76,7 +76,7 @@ where /-- -/ -def withCopyOfMonoXs [Inhabited α] (xs : Array Expr) (argKinds : Array ArgKind) (f : Array Expr → Array Expr → Array ArgKind → TermElabM α) : TermElabM α := do +def withCopyOfMonoXs {α} [Inhabited α] (xs : Array Expr) (argKinds : Array ArgKind) (f : Array Expr → Array Expr → Array ArgKind → TermElabM α) : TermElabM α := do -- Determine subset of monotone arguments and their behavior. let mut argDeclInfo : Array (Lean.Name × (Array Expr → TermElabM Expr)) := #[] let mut monoXs := #[] diff --git a/CvxLean/Tactic/DCP/DiscrTree.lean b/CvxLean/Tactic/DCP/DiscrTree.lean index 619b01d4..3ca7a186 100644 --- a/CvxLean/Tactic/DCP/DiscrTree.lean +++ b/CvxLean/Tactic/DCP/DiscrTree.lean @@ -72,11 +72,13 @@ def Key.arity : Key → Nat | Key.proj .. => 1 | _ => 0 -instance : Inhabited (Trie α) := ⟨Trie.node #[] #[]⟩ +instance {α} : Inhabited (Trie α) := ⟨Trie.node #[] #[]⟩ namespace DiscrTree +variable {α} + def empty : DiscrTree α := { root := {} } partial def Trie.format [ToMessageData α] : Trie α → MessageData @@ -381,6 +383,8 @@ end DiscrTree -- From AESOP: namespace Trie +variable {m} {α} {σ} + unsafe def foldMUnsafe [Monad m] (initialKeys : Array Key) (f : σ → Array Key → α → m σ) (init : σ) : Trie α → m σ | Trie.node vs children => do @@ -402,6 +406,8 @@ end Trie namespace DiscrTree +variable {m} {α} {σ} + @[inline] def foldM [Monad m] (f : σ → Array Key → α → m σ) (init : σ) (t : DiscrTree α) : m σ := @@ -427,4 +433,4 @@ def toArray (t : DiscrTree α) : Array (Array Key × α) := end DiscrTree -end CvxLean \ No newline at end of file +end CvxLean diff --git a/CvxLean/Tactic/DCP/OC.lean b/CvxLean/Tactic/DCP/OC.lean index c129b2a8..aaca13e5 100644 --- a/CvxLean/Tactic/DCP/OC.lean +++ b/CvxLean/Tactic/DCP/OC.lean @@ -11,6 +11,8 @@ deriving Inhabited namespace OC +variable {α β γ δ ε ζ η θ} + def map (f : α → β) (oc : OC α) : OC β := OC.mk (f oc.objFun) (oc.constr.map f) @@ -57,7 +59,7 @@ def map6M [Inhabited α] [Inhabited β] [Inhabited γ] [Inhabited δ] [Inhabited constr := constr.push $ ← h a.constr[i]! b.constr[i]! c.constr[i]! d.constr[i]! e.constr[i]! f.constr[i]! return OC.mk objFun constr -def map7M [Inhabited α] [Inhabited β] [Inhabited γ] [Inhabited δ] [Inhabited ε] [Inhabited ζ] [Inhabited η] +def map7M [Inhabited α] [Inhabited β] [Inhabited γ] [Inhabited δ] [Inhabited ε] [Inhabited ζ] [Inhabited η] (h : α → β → γ → δ → ε → ζ → η → MetaM θ) (a : OC α) (b : OC β) (c : OC γ) (d : OC δ) (e : OC ε) (f : OC ζ) (g : OC η) : MetaM (OC θ) := do let objFun ← h a.objFun b.objFun c.objFun d.objFun e.objFun f.objFun g.objFun diff --git a/CvxLean/Tactic/DCP/Tree.lean b/CvxLean/Tactic/DCP/Tree.lean index fca83aba..171ab64e 100644 --- a/CvxLean/Tactic/DCP/Tree.lean +++ b/CvxLean/Tactic/DCP/Tree.lean @@ -10,19 +10,21 @@ deriving Inhabited namespace Tree +variable {α β γ δ} + open Lean partial def toMessageData [ToMessageData α] [ToMessageData β] (x : Tree α β) : MessageData := match x with | Tree.node val children => - let children := children.map toMessageData - MessageData.paren ( - "node:" ++ ToMessageData.toMessageData val ++ "[" ++ (MessageData.joinSep children.toList ", ") ++ "]" - ) + let children := children.map toMessageData + MessageData.paren <| + "node:" ++ ToMessageData.toMessageData val ++ + "[" ++ (MessageData.joinSep children.toList ", ") ++ "]" | Tree.leaf val => "leaf:" ++ ToMessageData.toMessageData val -instance [ToMessageData α] [ToMessageData β] : ToMessageData (Tree α β) := { +instance [ToMessageData α] [ToMessageData β] : ToMessageData (Tree α β) where toMessageData := toMessageData -} + partial def zip [Inhabited α] [Inhabited β] [ToMessageData α] [ToMessageData β] [ToMessageData γ] [ToMessageData δ] : Tree α γ → Tree β δ → MetaM (Tree (α × β) (γ × δ)) @@ -41,10 +43,10 @@ partial def fold [Inhabited γ] (init : γ) (f : γ → α → γ) : Tree α β res ← fold res f child res := f res val return res - | leaf val => init + | leaf _ => init def val : Tree α α → α - | node val children => val + | node val _ => val | leaf val => val partial def size : Tree α β → Nat diff --git a/CvxLean/Tactic/PreDCP/Egg/Runner.lean b/CvxLean/Tactic/PreDCP/Egg/Runner.lean index aeab39d5..497b5413 100644 --- a/CvxLean/Tactic/PreDCP/Egg/Runner.lean +++ b/CvxLean/Tactic/PreDCP/Egg/Runner.lean @@ -17,6 +17,8 @@ def _root_.Lean.Json.getArr! (j : Json) : Array Json := | Json.arr a => a | _ => #[] +variable {ε α} + def _root_.MetaM.ofExcept [ToString ε]: Except ε α -> MetaM α := fun e => match e with diff --git a/CvxLean/Tactic/PreDCP/Egg/Sexp.lean b/CvxLean/Tactic/PreDCP/Egg/Sexp.lean index 4cb4db4e..19bbc5b4 100644 --- a/CvxLean/Tactic/PreDCP/Egg/Sexp.lean +++ b/CvxLean/Tactic/PreDCP/Egg/Sexp.lean @@ -59,8 +59,8 @@ inductive SexpError | notSingleSexp (s: String) (xs: List Sexp) : SexpError deriving BEq, Repr -instance : ToString SexpError where - toString +instance : ToString SexpError where + toString | .unmatchedOpenParen ix => s!"Unmatched open parenthesis at {ix}" | .unmatchedCloseParen ix => s!"Unmatched close parenthesis at {ix}" | .notSingleSexp s xs => s!"not a single sexp '{s}', parsed as: '{xs}'" @@ -87,9 +87,9 @@ def SexpM.pushTok (tok: SexpTok): SexpM Unit := do def SexpM.pushSexp (sexp: Sexp): SexpM Unit := do let state ← get - if state.stack.length == 0 then + if state.stack.length == 0 then set { state with stack := [], sexps := sexp :: state.sexps } - else + else set { state with stack := (SexpTok.sexp sexp) :: state.stack } def SexpM.incrementDepth: SexpM Unit := @@ -98,7 +98,7 @@ def SexpM.incrementDepth: SexpM Unit := def SexpM.decrementDepth: SexpM Unit := modify (fun state => { state with depth := state.depth - 1 }) -instance [Inhabited α] : Inhabited (SexpM α) := by infer_instance +instance {α} [Inhabited α] : Inhabited (SexpM α) := by infer_instance def SexpM.pop: SexpM SexpTok := do let state ← get @@ -111,7 +111,7 @@ def SexpM.pop: SexpM SexpTok := do -- Remove elements from the stack of tokens `List SexpToken` till we find a `SexpToken.opening`. -- When we do, return (1) the position of the open paren, (2) the list of SexpTokens left on the stack, and (3) the list of Sexps -- Until then, accumulate the `SexpToken.sexp`s into `sexps`. -def stackPopTillOpen (stk : List SexpTok) (sexps : List Sexp := []) : +def stackPopTillOpen (stk : List SexpTok) (sexps : List Sexp := []) : Option (String.Pos × (List SexpTok) × (List Sexp)) := match stk with | [] => .none @@ -159,7 +159,7 @@ partial def SexpM.parse : SexpM Unit := do | .none => do let state ← get match stackPopTillOpen state.stack with - | some (openPos, _, _) => throw <| + | some (openPos, _, _) => throw <| SexpError.unmatchedOpenParen ({ s := state.it.s, i := openPos }) | none => return () @@ -170,7 +170,7 @@ def parseSexpList (s: String): Except SexpError (List Sexp) := | .ok () state => .ok state.sexps.reverse | .error e _ => .error e -/-- Parse a single s-expression, and error if found no sexp or multiple sexps. +/-- Parse a single s-expression, and error if found no sexp or multiple sexps. -/ def parseSingleSexp (s: String) : Except SexpError Sexp := do match (← parseSexpList s) with diff --git a/CvxLean/Test/DCP/Label.lean b/CvxLean/Test/DCP/Label.lean index d7f2a4d3..7a6d1f79 100644 --- a/CvxLean/Test/DCP/Label.lean +++ b/CvxLean/Test/DCP/Label.lean @@ -4,7 +4,7 @@ def testLabel := {** 0 ** x **} #print testLabel -set_option pp.CvxLean.labels true +set_option pp.labels true #print testLabel diff --git a/lakefile.lean b/lakefile.lean index 0916492a..0102f20c 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -2,7 +2,13 @@ import Lake open System Lake DSL -package CvxLean +package CvxLean where + leanOptions := #[ + ⟨`pp.unicode.fun, true⟩, + ⟨`pp.proofs.withType, false⟩, + ⟨`autoImplicit, false⟩, + ⟨`relaxedAutoImplicit, false⟩ + ] require mathlib from git "https://github.com/leanprover-community/mathlib4" @ @@ -13,7 +19,7 @@ require scilean from git "https://github.com/verified-optimization/SciLean" @ "master" -meta if get_config? env = some "doc" then +meta if get_config? doc = some "on" then require «doc-gen4» from git "https://github.com/verified-optimization/doc-gen4.git" @ "main" From abbf7e565ca288bba599ddc9027d296a001f1e49 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Sun, 4 Feb 2024 18:06:08 -0500 Subject: [PATCH 42/52] wip: no `pp` options, for now --- CvxLean/Syntax/Label.lean | 2 +- CvxLean/Syntax/Minimization.lean | 64 ++++++++++++++++---------------- CvxLean/Syntax/Options.lean | 22 +++++------ CvxLean/Test/DCP/Label.lean | 2 +- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/CvxLean/Syntax/Label.lean b/CvxLean/Syntax/Label.lean index 3817d016..b5298077 100644 --- a/CvxLean/Syntax/Label.lean +++ b/CvxLean/Syntax/Label.lean @@ -63,7 +63,7 @@ open Lean.PrettyPrinter.Delaborator SubExpr /-- Display labelled terms using the `{** term ** name **}` syntax. -/ @[delab mdata] def delabNamedConstraint : Delab := do -- Omit delaboration if pretty printing option is disabled. - if not (pp.labels.get (← getOptions)) then failure + --if !(pp.labels.get (← getOptions)) then failure -- Check if `CvxLeanLabel` metadata is attached to current expression. let Expr.mdata m e ← getExpr | unreachable! match m.get? `CvxLeanLabel with diff --git a/CvxLean/Syntax/Minimization.lean b/CvxLean/Syntax/Minimization.lean index 1896ed86..1eb049ca 100644 --- a/CvxLean/Syntax/Minimization.lean +++ b/CvxLean/Syntax/Minimization.lean @@ -159,7 +159,7 @@ def delabVar (e : Expr) : DelabM (Lean.Name × Term) := do match m.get? `CvxLeanLabel with | some (name : Lean.Name) => return (name, ← descend e 0 do delab) | none => Alternative.failure - | _ => return (`_, ← delab) + | _ => return (`_, ← delab) /-- Show labels (variable names) and terms (types) in a domain type. -/ partial def delabDomain : DelabM (List (Lean.Name × Term)) := do @@ -175,11 +175,11 @@ partial def delabDomain : DelabM (List (Lean.Name × Term)) := do partial def delabConstraint : DelabM (TSyntax ``Parser.constraint) := do match ← getExpr with | Expr.mdata m e => - match m.get? `CvxLeanLabel with - | some (name : Lean.Name) => - return mkNode ``Parser.constraint - #[(mkIdent name).raw, mkAtom ":", (← descend e 0 do delab).raw] - | none => Alternative.failure + match m.get? `CvxLeanLabel with + | some (name : Lean.Name) => + return mkNode ``Parser.constraint + #[(mkIdent name).raw, mkAtom ":", (← descend e 0 do delab).raw] + | none => Alternative.failure | _ => return (← `(Parser.constraint|_ : $(← delab))) /-- Show all constraints with their labels. -/ @@ -204,35 +204,35 @@ def withDomainBinding {α} [Inhabited α] (domain : Expr) (x : DelabM α) : Dela /-- Show minimization problem using the custom syntax, with variable names and constraitn tags. -/ @[delab app] partial def delabMinimization : Delab := do - if not (pp.optMinimization.get (← getOptions)) then Alternative.failure + --if !(pp.opt.get (← getOptions)) then Alternative.failure match ← getExpr with | .app (.app (.app (.app (.const `Minimization.mk _) domain) _codomain) _objFun) constraints => - let idents ← withType <| withNaryArg 0 do - let tys ← delabDomain - let tys ← tys.mapM fun (name, stx) => do `(Parser.minimizationVar| ($(mkIdent name) : $stx)) - return tys.toArray - let (objFun, isMax) ← withNaryArg 2 do withDomainBinding domain do - match ← getExpr with - | .app (.app (.app (.const ``maximizeNeg _) _) _) e => - withExpr e do - return (← delab, true) - | _ => - return (← delab, false) - let noConstrs ← withLambdaBody constraints fun _ constrsBody => do - isDefEq constrsBody (mkConst ``True) - let constraints := ← withNaryArg 3 do - let cs ← withDomainBinding domain delabConstraints - return mkNode ``Parser.constraints #[mkNullNode <| cs.toArray.map (·.raw)] - if noConstrs then - if isMax then - `(optimization $idents* maximize $objFun) - else - `(optimization $idents* minimize $objFun) - else - if isMax then - `(optimization $idents* maximize $objFun subject to $constraints) + let idents ← withType <| withNaryArg 0 do + let tys ← delabDomain + let tys ← tys.mapM fun (name, stx) => do `(Parser.minimizationVar| ($(mkIdent name) : $stx)) + return tys.toArray + let (objFun, isMax) ← withNaryArg 2 do withDomainBinding domain do + match ← getExpr with + | .app (.app (.app (.const ``maximizeNeg _) _) _) e => + withExpr e do + return (← delab, true) + | _ => + return (← delab, false) + let noConstrs ← withLambdaBody constraints fun _ constrsBody => do + isDefEq constrsBody (mkConst ``True) + let constraints := ← withNaryArg 3 do + let cs ← withDomainBinding domain delabConstraints + return mkNode ``Parser.constraints #[mkNullNode <| cs.toArray.map (·.raw)] + if noConstrs then + if isMax then + `(optimization $idents* maximize $objFun) + else + `(optimization $idents* minimize $objFun) else - `(optimization $idents* minimize $objFun subject to $constraints) + if isMax then + `(optimization $idents* maximize $objFun subject to $constraints) + else + `(optimization $idents* minimize $objFun subject to $constraints) | _ => Alternative.failure end Delab diff --git a/CvxLean/Syntax/Options.lean b/CvxLean/Syntax/Options.lean index 090aebf2..d7c1c4af 100644 --- a/CvxLean/Syntax/Options.lean +++ b/CvxLean/Syntax/Options.lean @@ -1,21 +1,21 @@ import Lean /-! -Pretty-priner options to control how to show minimization problems and labels. +Pretty-printer options to control how to show minimization problems and labels. -/ namespace CvxLean -register_option pp.optMinimization : Bool := { - defValue := true - group := "pp" - descr := "(pretty printer) pretty-print minimization problems." -} +-- register_option pp.opt : Bool := { +-- defValue := true +-- group := "pp" +-- descr := "(pretty printer) pretty-print minimization problems." +-- } -register_option pp.labels : Bool := { - defValue := false - group := "pp" - descr := "(pretty printer) display CvxLean labels." -} +-- register_option pp.labels : Bool := { +-- defValue := false +-- group := "pp" +-- descr := "(pretty printer) display CvxLean labels." +-- } end CvxLean diff --git a/CvxLean/Test/DCP/Label.lean b/CvxLean/Test/DCP/Label.lean index 7a6d1f79..0684116e 100644 --- a/CvxLean/Test/DCP/Label.lean +++ b/CvxLean/Test/DCP/Label.lean @@ -4,7 +4,7 @@ def testLabel := {** 0 ** x **} #print testLabel -set_option pp.labels true +--set_option pp.labels true #print testLabel From 88a378355ab173e311ff159c43ac30545c9c11db Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Mon, 5 Feb 2024 20:32:43 -0500 Subject: [PATCH 43/52] wip: generate docs in CI --- .github/workflows/docs.yml | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..b855f087 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,80 @@ +name: docs + +on: + push: + branches: ["main"] + + pull_request: + branches: ["main"] + + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Install elan + run: | + set -o pipefail + curl -sSfL https://github.com/leanprover/elan/releases/download/v3.0.0/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz + ./elan-init -y --default-toolchain leanprover/lean4:v4.4.0-rc1 + echo "$HOME/.elan/bin" >> $GITHUB_PATH + - name: Install Rust and Cargo + run: | + curl --proto '=https' -sSf https://sh.rustup.rs > rustup-init.sh + sh rustup-init.sh -y --default-toolchain none + - name: Checkout + uses: actions/checkout@v3 + - name: Build egg-pre-dcp and CvxLean + run: | + ./build.sh + - name: Create dummy docs project + run: | + # Taken from https://github.com/leanprover-community/mathlib4_docs + + # Workaround for the lake issue + elan default leanprover/lean4:nightly + lake new workaround + cd workaround + cp -f ../mathlib4/lean-toolchain . + echo 'require «doc-gen4» from git "https://github.com/leanprover/doc-gen4" @ "main"' >> lakefile.lean + echo 'require CvxLean from ".." / "CvxLean"' >> lakefile.lean + + # doc-gen4 expects to work on github repos with at least one commit + git config user.email "docs@verified-optimization.github.io" + git config user.name "docs CI" + git add . + git commit -m "workaround" + git remote add origin "https://github.com/verified-optimization/workaround" + + mkdir -p .lake/packages + cp -r ../mathlib4/.lake/packages/* .lake/packages + lake update + - name: Build doc-gen4 + working-directory: workaround + run: | + lake build doc-gen4 + - name: Generate docs + working-directory: workaround + run: | + lake build CvxLean:docs + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: 'workaround/.lake/build/doc' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 + - name: Clean up + if: always() + run: | + rm -rf CvxLean + rm -rf workaround + rm -rf $HOME/.elan + rm -rf $HOME/.cache/mathlib From 00c57030219f734a580f52a1cbf340fa00c543d9 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Mon, 5 Feb 2024 20:52:04 -0500 Subject: [PATCH 44/52] chore: update toolchain in CI --- .github/workflows/docs.yml | 4 ++-- .github/workflows/main.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b855f087..06616bed 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -23,7 +23,7 @@ jobs: run: | set -o pipefail curl -sSfL https://github.com/leanprover/elan/releases/download/v3.0.0/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz - ./elan-init -y --default-toolchain leanprover/lean4:v4.4.0-rc1 + ./elan-init -y --default-toolchain leanprover/lean4:v4.6.0-rc1 echo "$HOME/.elan/bin" >> $GITHUB_PATH - name: Install Rust and Cargo run: | @@ -39,7 +39,7 @@ jobs: # Taken from https://github.com/leanprover-community/mathlib4_docs # Workaround for the lake issue - elan default leanprover/lean4:nightly + elan default leanprover/lean4:v4.6.0-rc1 lake new workaround cd workaround cp -f ../mathlib4/lean-toolchain . diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d770aaf1..b1f480b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: run: | set -o pipefail curl -sSfL https://github.com/leanprover/elan/releases/download/v3.0.0/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz - ./elan-init -y --default-toolchain leanprover/lean4:v4.4.0-rc1 + ./elan-init -y --default-toolchain leanprover/lean4:v4.6.0-rc1 echo "$HOME/.elan/bin" >> $GITHUB_PATH - name: Install Rust and Cargo run: | From 885019761c7a82631416ffcffb64ef558468a010 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Tue, 6 Feb 2024 08:34:33 -0500 Subject: [PATCH 45/52] fix: path in docs CI --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 06616bed..28805784 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -54,7 +54,7 @@ jobs: git remote add origin "https://github.com/verified-optimization/workaround" mkdir -p .lake/packages - cp -r ../mathlib4/.lake/packages/* .lake/packages + cp -r ../CvxLean/.lake/packages/* .lake/packages lake update - name: Build doc-gen4 working-directory: workaround From 81abd802b4c0940b2c08ff2ce16a3657af39e59b Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Tue, 6 Feb 2024 08:52:19 -0500 Subject: [PATCH 46/52] fix: implicit argument order in real-to-float conversion --- CvxLean/Command/Solve/Float/Coeffs.lean | 1 - CvxLean/Lib/Math/Data/Matrix.lean | 6 +++--- CvxLean/Test/Solve/Problems/LogDet.lean | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CvxLean/Command/Solve/Float/Coeffs.lean b/CvxLean/Command/Solve/Float/Coeffs.lean index 2ab81d45..904c7c0c 100644 --- a/CvxLean/Command/Solve/Float/Coeffs.lean +++ b/CvxLean/Command/Solve/Float/Coeffs.lean @@ -312,7 +312,6 @@ unsafe def determineCoeffsFromExpr (minExpr : MinimizationExpr) : let (ea, eb) ← determineScalarCoeffsAux e p floatDomain data := data.addSOConstraint ea eb idx := idx + 1 - -- TODO: Unroll? | .app (.app (.app (.app (.app (.const ``Real.Matrix.posOrthCone _) (.app (.const ``Fin _) m)) (.app (.const ``Fin _) n)) _) _) e => do let m : Nat ← evalExpr Nat (mkConst ``Nat) m diff --git a/CvxLean/Lib/Math/Data/Matrix.lean b/CvxLean/Lib/Math/Data/Matrix.lean index 0b453671..57e2c31f 100644 --- a/CvxLean/Lib/Math/Data/Matrix.lean +++ b/CvxLean/Lib/Math/Data/Matrix.lean @@ -13,7 +13,7 @@ here, which are used by the real-to-float procedure. namespace Matrix -variable {α} {m n} +variable {m n} {α} def const (k : α) : Matrix m n α := fun _ _ => k @@ -67,10 +67,10 @@ def mulVec (M : Matrix (Fin m) (Fin n) Float) (v : (Fin n) → Float) : Fin m def vecMul (x : Fin m → Float) (M : Matrix (Fin m) (Fin n) Float) : Fin n → Float := fun j => x ⬝ᵥᶜ fun i => M i j -def transpose {m n} (M : Matrix m n α) : Matrix n m α := +def transpose {m n} {α} (M : Matrix m n α) : Matrix n m α := fun i j => M j i -def diag {n} (M : Matrix n n α) : n → α := +def diag {n} {α} (M : Matrix n n α) : n → α := fun i => M i i def mul (M : Matrix (Fin l) (Fin m) Float) (N : Matrix (Fin m) (Fin n) Float) : diff --git a/CvxLean/Test/Solve/Problems/LogDet.lean b/CvxLean/Test/Solve/Problems/LogDet.lean index fc6791dd..54798e4a 100644 --- a/CvxLean/Test/Solve/Problems/LogDet.lean +++ b/CvxLean/Test/Solve/Problems/LogDet.lean @@ -11,7 +11,6 @@ noncomputable def logDet1 := c1 : 10 ≤ log X.det c2 : X.PosDef -set_option trace.Meta.debug true solve logDet1 #eval logDet1.status -- "PRIMAL_AND_DUAL_FEASIBLE" From 9549aac69cb7a80cb7425fc0e01914561ca0a688 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Tue, 6 Feb 2024 08:52:35 -0500 Subject: [PATCH 47/52] fix: no auto-implicit in tests --- CvxLean/Test/DCP/Basic.lean | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CvxLean/Test/DCP/Basic.lean b/CvxLean/Test/DCP/Basic.lean index 28c508ec..d283a6d0 100644 --- a/CvxLean/Test/DCP/Basic.lean +++ b/CvxLean/Test/DCP/Basic.lean @@ -59,6 +59,8 @@ def testNorm2 : Solution <| dcp sorry +variable {m} + def testVecExp : Solution <| optimization (x y : Fin m → ℝ) minimize (0 : ℝ) @@ -103,7 +105,7 @@ def testLog : Solution <| dcp sorry -def testLogDet : Solution <| +def testLogDet {n : ℕ} : Solution <| optimization (M : Matrix (Fin n) (Fin n) ℝ) minimize (0 : ℝ) subject to @@ -116,6 +118,8 @@ end Atoms section Misc +variable {d : ℝ} + def testGrantsThesis (a b c : ℝ) (ha : 0 ≤ a) (hb : 0 ≤ b) (hc : 0 ≤ c) : Solution <| optimization (x y : ℝ) minimize (c * x) From 29bd7ed4e05d4ca97914915715a127d7aa4a6b3f Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Tue, 6 Feb 2024 08:52:46 -0500 Subject: [PATCH 48/52] wip: do not show label --- CvxLean/Syntax/Label.lean | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/CvxLean/Syntax/Label.lean b/CvxLean/Syntax/Label.lean index b5298077..26c6778e 100644 --- a/CvxLean/Syntax/Label.lean +++ b/CvxLean/Syntax/Label.lean @@ -62,16 +62,17 @@ open Lean.PrettyPrinter.Delaborator SubExpr /-- Display labelled terms using the `{** term ** name **}` syntax. -/ @[delab mdata] def delabNamedConstraint : Delab := do - -- Omit delaboration if pretty printing option is disabled. - --if !(pp.labels.get (← getOptions)) then failure - -- Check if `CvxLeanLabel` metadata is attached to current expression. - let Expr.mdata m e ← getExpr | unreachable! - match m.get? `CvxLeanLabel with - | some (name : Name) => - let stx ← descend e 0 (do delab) - let id := mkIdent name - `({** $stx ** $id**}) - | none => failure + failure + -- -- Omit delaboration if pretty printing option is disabled. + -- if !(pp.labels.get (← getOptions)) then failure + -- -- Check if `CvxLeanLabel` metadata is attached to current expression. + -- let Expr.mdata m e ← getExpr | unreachable! + -- match m.get? `CvxLeanLabel with + -- | some (name : Name) => + -- let stx ← descend e 0 (do delab) + -- let id := mkIdent name + -- `({** $stx ** $id**}) + -- | none => failure end Delab From e0a2a7902b7081daf0b73fe6f65f50e9242cb49b Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Tue, 6 Feb 2024 09:17:20 -0500 Subject: [PATCH 49/52] fix: order of arguments in `vecMul` and `mulVec` --- CvxLean/Lib/Math/Data/Matrix.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CvxLean/Lib/Math/Data/Matrix.lean b/CvxLean/Lib/Math/Data/Matrix.lean index 57e2c31f..6db3105a 100644 --- a/CvxLean/Lib/Math/Data/Matrix.lean +++ b/CvxLean/Lib/Math/Data/Matrix.lean @@ -61,10 +61,10 @@ def dotProduct (v w : Fin n → Float) : Float := infixl:72 " ⬝ᵥᶜ " => Matrix.Computable.dotProduct -def mulVec (M : Matrix (Fin m) (Fin n) Float) (v : (Fin n) → Float) : Fin m → Float := +def mulVec (M : Matrix (Fin n) (Fin m) Float) (v : (Fin m) → Float) : Fin n → Float := fun i => (fun j => M i j) ⬝ᵥᶜ v -def vecMul (x : Fin m → Float) (M : Matrix (Fin m) (Fin n) Float) : Fin n → Float := +def vecMul (x : Fin n → Float) (M : Matrix (Fin n) (Fin m) Float) : Fin m → Float := fun j => x ⬝ᵥᶜ fun i => M i j def transpose {m n} {α} (M : Matrix m n α) : Matrix n m α := From 95dab21687753c66ebe9c588c93e1e14fb952e21 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Tue, 6 Feb 2024 09:18:57 -0500 Subject: [PATCH 50/52] fix: copy-paste issues in docs CI --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 28805784..1668e016 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -42,7 +42,7 @@ jobs: elan default leanprover/lean4:v4.6.0-rc1 lake new workaround cd workaround - cp -f ../mathlib4/lean-toolchain . + cp -f ../CvxLean/lean-toolchain . echo 'require «doc-gen4» from git "https://github.com/leanprover/doc-gen4" @ "main"' >> lakefile.lean echo 'require CvxLean from ".." / "CvxLean"' >> lakefile.lean From 4515c5a35157829f606fb9586b5a91da8f671889 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Tue, 6 Feb 2024 09:28:22 -0500 Subject: [PATCH 51/52] fix: explicit checkout path in docs action --- .github/workflows/docs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1668e016..7e3ec0ca 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -31,6 +31,9 @@ jobs: sh rustup-init.sh -y --default-toolchain none - name: Checkout uses: actions/checkout@v3 + with: + repository: verified-optimization/CvxLean + path: CvxLean - name: Build egg-pre-dcp and CvxLean run: | ./build.sh From 5b96d36d9967b8ca605f734422a67378509f5759 Mon Sep 17 00:00:00 2001 From: Ramon Fernandez Mir Date: Tue, 6 Feb 2024 09:30:48 -0500 Subject: [PATCH 52/52] fix: working directory in build --- .github/workflows/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7e3ec0ca..194ff989 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -35,6 +35,7 @@ jobs: repository: verified-optimization/CvxLean path: CvxLean - name: Build egg-pre-dcp and CvxLean + working-directory: CvxLean run: | ./build.sh - name: Create dummy docs project