"""
    solve_sos_barrier_2d(A_red, x_vars, entry_halfspaces, unsafe_halfspaces;
                         barrier_degree=4, multiplier_degree=2,
                         eps_cap=1.0, solver_attrs=("printlevel" => 0))

Find a degree-`barrier_degree` polynomial barrier certificate `B(x)` for
the linear closed-loop `dx/dt = A_red x` on a 2-D state projection,
using the Prajna–Jadbabaie SOS formulation.

A *barrier certificate* `B(x)` proves forward invariance of `{B(x) ≤ 0}`:
- `B ≤ -ε` on `X_entry` (the certified-safe entry set)
- `B ≥ +ε` on each unsafe halfspace
- `dB/dt = ∇B · A_red·x ≤ 0` everywhere

If such a `B` exists with `ε > 0`, the entry set never reaches the
unsafe set under the closed-loop flow.

# Why ε is here

The naive feasibility version (no slack, no objective) silently returns
`B ≡ 0` — vacuously satisfies `B ≤ 0` and `B ≥ 0` with `σ = 0`. We
maximize `ε ∈ [0, eps_cap]` to force strict separation; `eps_cap` is
finite because `B` has free scale (without a cap the primal is
unbounded → `DUAL_INFEASIBLE`). We only care whether `ε* > 0`; its
magnitude is just the unit of `B`.

# Arguments

- `A_red::AbstractMatrix` — 2×2 closed-loop dynamics matrix.
- `x_vars` — tuple `(x1, x2)` of `DynamicPolynomials.PolyVar`s. Caller
  must `@polyvar` these so the polynomial expressions in
  `entry_halfspaces` and `unsafe_halfspaces` reference the same variables.
- `entry_halfspaces::Vector{<:AbstractPolynomialLike}` — list of
  polynomials `g_e(x)` such that `X_entry = ⋂ {g_e(x) ≥ 0}`.
- `unsafe_halfspaces::Vector{<:AbstractPolynomialLike}` — list of
  polynomials `g_u(x)` such that each `X_unsafe_i = {g_u_i(x) ≥ 0}`.
  The certificate proves separation from the *union* of these regions.

# Keyword arguments

- `barrier_degree::Int=4` — degree of the barrier polynomial `B`.
- `multiplier_degree::Int=2` — degree of the SOS multiplier polynomials.
- `eps_cap::Real=1.0` — upper bound on `ε` (just controls the scale of
  the returned `B`).
- `solver_attrs` — passed to `optimizer_with_attributes(CSDP.Optimizer, ...)`.

# Returns

NamedTuple `(; status, B, ε, model)`:
- `status` — `MOI.TerminationStatusCode`. `OPTIMAL` with `ε > 1e-8` means
  a real certificate.
- `B` — the optimized polynomial value, or `nothing` on failure.
- `ε` — the achieved separation slack, or `nothing` on failure.
- `model` — the JuMP `SOSModel`, returned for callers that want to
  inspect multipliers.

# Caveats

- The Lie-derivative condition is checked *globally* (∇B·f ∈ SOS), not
  just on `{B = 0}`. This is convex but stronger than needed; it
  effectively requires `B` to be a Lyapunov function. Use the bilinear
  Putinar form for a tighter test (out of scope here — needs alternation).
- No disturbance term. Extend by adding `B_w·w` worst-case to the Lie
  inequality and using a Putinar multiplier on the disturbance polytope.
- The 2-D projection is a modeling simplification — cross-coupling from
  dropped states is not certified. Norm-check the dropped block at the
  caller site.
"""
function solve_sos_barrier_2d(A_red, x_vars, entry_halfspaces, unsafe_halfspaces;
                              barrier_degree::Int=4,
                              multiplier_degree::Int=2,
                              eps_cap::Real=1.0,
                              solver_attrs=("printlevel" => 0,))
    x1, x2 = x_vars
    f_nom = A_red * [x1; x2]

    solver = optimizer_with_attributes(CSDP.Optimizer, solver_attrs...)
    model = SOSModel(solver)

    monos_B = monomials([x1, x2], 0:barrier_degree)
    @variable(model, B_poly, Poly(monos_B))

    monos_σ = monomials([x1, x2], 0:multiplier_degree)

    # Slack variable: forces strict separation. Cap is finite because B
    # has free scale (otherwise primal unbounded → DUAL_INFEASIBLE).
    @variable(model, 0 <= ε <= eps_cap)
    @objective(model, Max, ε)

    # (a) B + ε ≤ 0 on X_entry.
    σ_e_terms = sum(begin
        σ = @variable(model, [1:1], SOSPoly(monos_σ))[1]
        σ * g
    end for g in entry_halfspaces)
    @constraint(model, -B_poly - ε - σ_e_terms in SOSCone())

    # (b) B - ε ≥ 0 on each X_unsafe_i (separately — union semantics).
    for g_u in unsafe_halfspaces
        σ_u = @variable(model, [1:1], SOSPoly(monos_σ))[1]
        @constraint(model, B_poly - ε - σ_u * g_u in SOSCone())
    end

    # (c) Lie derivative ≤ 0 globally.
    dB_dx = [differentiate(B_poly, x1), differentiate(B_poly, x2)]
    lie = dB_dx[1] * f_nom[1] + dB_dx[2] * f_nom[2]
    @constraint(model, -lie in SOSCone())

    optimize!(model)
    status = termination_status(model)

    if status == MOI.OPTIMAL
        ε_val = value(ε)
        B_val = value(B_poly)
        return (; status, B=B_val, ε=ε_val, model)
    else
        return (; status, B=nothing, ε=nothing, model)
    end
end
