#!/usr/bin/env julia
#
# barrier_lyapunov.jl — Lyapunov-ellipsoid barrier cert.
# Julia port of reachability/barrier_lyapunov.m.
#
# Sweeps Qbar(T_c) weight to find the tightest quadratic barrier;
# reports per-halfspace margins against inv2_holds.
#
# Expected result: every halfspace exceeded by orders of magnitude.
# This is the structural anisotropy limitation, not a code bug.

using Pkg
Pkg.activate(joinpath(@__DIR__, "..", ".."))

using Printf
using LinearAlgebra
using MatrixEquations
using JSON

include(joinpath(@__DIR__, "..", "..", "src", "pke_params.jl"))
include(joinpath(@__DIR__, "..", "..", "src", "pke_th_rhs.jl"))
include(joinpath(@__DIR__, "..", "..", "src", "pke_linearize.jl"))
include(joinpath(@__DIR__, "..", "..", "src", "load_predicates.jl"))

plant = pke_params()
x_op  = pke_initial_conditions(plant)
pred  = load_predicates(plant)

# --- Closed-loop ---
A, B, B_w, _, _, _ = pke_linearize(plant)
Q_lqr = Diagonal([1.0, 1e-3, 1e-3, 1e-3, 1e-3, 1e-3, 1e-3, 1e-2, 1e2, 1.0])
R_lqr = 1e6 * ones(1, 1)
X_ric, _, _ = arec(A, reshape(B, :, 1), R_lqr, Matrix(Q_lqr))
K    = (R_lqr \ reshape(B, 1, :)) * X_ric
A_cl = A - reshape(B, :, 1) * K

# --- Lyapunov ---
# lyapc solves A' P + P A + Q = 0 for P.  Julia's MatrixEquations uses
# lyapc(A', Q) to solve A' P + P A = -Q (note: sign conventions vary).
# Check: for a Hurwitz A, lyapc(A, Q) with Q > 0 returns P > 0.
Qbar = Diagonal([1.0, 1e-4, 1e-4, 1e-4, 1e-4, 1e-4, 1e-4, 1.0, 1e2, 1.0])
P    = lyapc(Matrix(A_cl)', Matrix(Qbar))
@assert all(eigvals(P) .> 0) "P not positive definite"

# --- Disturbance bound ---
w_bar = 0.15 * plant.P0

# Invariant level via the derivation in the MATLAB file.
P_half     = Hermitian(sqrt(Hermitian(P)))
P_half_inv = inv(P_half)
mu         = minimum(eigvals(Symmetric(P_half_inv * Matrix(Qbar) * P_half_inv)))
g_bound    = sqrt(B_w' * P * B_w)
c_inv      = (2 * w_bar * g_bound / mu)^2

delta_entry = [0.01 * x_op[1];
               0.001 .* abs.(x_op[2:7]);
               0.1; 0.1; 0.1]
c_entry = delta_entry' * abs.(P) * delta_entry

gamma = max(c_inv, c_entry)

println("\n=== Lyapunov barrier certificate ===")
@printf "  lambda_min(P)            = %.3e\n"  minimum(eigvals(P))
@printf "  lambda_max(P)            = %.3e\n"  maximum(eigvals(P))
@printf "  sqrt(B_w' P B_w)         = %.3e\n"  g_bound
@printf "  mu (Qbar eig on P-metric)= %.3e\n"  mu
@printf "  w_bar  (15%% P0)          = %.3e W\n"  w_bar
@printf "  c_inv  (invariant level) = %.3e\n"  c_inv
@printf "  c_entry (from X_entry)   = %.3e\n"  c_entry
@printf "  gamma                    = %.3e\n"  gamma

# --- inv2_holds per-halfspace barrier check ---
inv2 = pred.mode_invariants[:inv2_holds]
A_inv = inv2.A_poly
b_inv = inv2.b_poly
comps = inv2.components
b_dev = b_inv .- A_inv * x_op

Pinv = inv(P)
println("\n=== Lyapunov barrier vs inv2_holds halfspaces ===")
for k in 1:size(A_inv, 1)
    a = A_inv[k, :]
    max_adx = sqrt(gamma * (a' * Pinv * a))
    margin  = b_dev[k] - max_adx
    status  = margin < 0 ? "*** TOO LOOSE ***" : "OK"
    @printf "  [%-22s]  headroom = %8.3f  |  max a'dx = %8.3f  |  margin = %+8.3f  %s\n" comps[k] b_dev[k] max_adx margin status
end

# --- Qbar(T_c) sweep for tightness ---
println("\n=== Qbar(T_c) weight sweep ===")
e9 = zeros(10); e9[9] = 1.0
weights = [1e1, 1e2, 1e3, 1e4, 1e5, 1e6]
let best_dTc = Inf, best_w = NaN, best_gamma = NaN, best_P = nothing
    for wTc in weights
        Qbs = copy(Matrix(Qbar))
        Qbs[9, 9] = wTc
        Ps = try
            lyapc(Matrix(A_cl)', Qbs)
        catch
            continue
        end
        any(eigvals(Ps) .<= 0) && continue
        Phs = Hermitian(sqrt(Hermitian(Ps)))
        Phi = inv(Phs)
        mu_s = minimum(eigvals(Symmetric(Phi * Qbs * Phi)))
        g_s  = sqrt(B_w' * Ps * B_w)
        ci_s = (2 * w_bar * g_s / mu_s)^2
        ce_s = delta_entry' * abs.(Ps) * delta_entry
        gs   = max(ci_s, ce_s)
        Pinv_s = inv(Ps)
        dTc  = sqrt(gs * (e9' * Pinv_s * e9))
        @printf "  Qbar(9,9) = %.0e  ->  gamma = %.3e,  max|dT_c| = %7.3f K\n" wTc gs dTc
        if dTc < best_dTc
            best_dTc = dTc; best_w = wTc; best_gamma = gs; best_P = Ps
        end
    end
    @printf "  Best: Qbar(9,9) = %.0e  ->  max|dT_c| = %.3f K\n" best_w best_dTc
end
