"""
SOS-style emptiness checks for safety (S.O.S. for Safety, 2004).

This module implements a *restricted* but sound SOS-style check for proving
that certain unsafe regions are empty, without invoking heavy SDP solvers.
We focus on small, common patterns in Python bytecode:

1) Guarded division in affine loops:
   If the loop guard implies the divisor is bounded away from 0, then
   the set { guard ∧ (divisor == 0) } is empty.

We expose this as a "local SAFE" artifact that can reduce false positives
and allow the orchestrator to spend time elsewhere.

LAYER POSITION
==============

This is a **Layer 2 (Certificate Core)** module.

    ┌─────────────────────────────────────────────────────────────────┐
    │ Layer 5: Advanced Verification (IC3, CHC, IMC, Assume-Guarantee)│
    │ Layer 4: Learning (ICE, Houdini, SyGuS)                         │
    │ Layer 3: Abstraction (CEGAR, Predicate Abstraction, IMPACT)     │
    │ Layer 2: CERTIFICATE CORE ← [THIS MODULE]                       │
    │   ├── hybrid_barrier.py (Paper #1)                              │
    │   ├── stochastic_barrier.py (Paper #2)                          │
    │   ├── sos_safety.py ← You are here (Paper #3)                   │
    │   └── sostools.py (Paper #4)                                    │
    │ Layer 1: Foundations (Positivstellensatz, SOS/SDP, Lasserre)    │
    └─────────────────────────────────────────────────────────────────┘

CROSS-PAPER DEPENDENCIES
========================

This module provides PRACTICAL emptiness checking:
- Uses simplified SOS reasoning without full SDP
- Targets common Python patterns (guarded division, etc.)
- Lightweight alternative to full Paper #6 SOS/SDP

Integration with other papers:
- Paper #1 (Hybrid): Per-mode emptiness checks
- Paper #5 (Positivstellensatz): Simplified positivity reasoning
- Papers #12-16 (Abstraction): Quick checks before expensive analysis
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional
import dis

import z3

from ..cfg.loop_analysis import LoopInfo, extract_loops
from ..cfg.affine_loop_model import (
    extract_affine_loop_model,
    AffineGuard,
    AffineOperand,
)
from .templates import polynomial_barrier, extract_local_variable
from .invariants import BarrierCertificate


@dataclass(frozen=True)
class SosEmptySetProof:
    """
    Proof that a specific unsafe set is empty via SOS-style reasoning.

    Currently used for guarded DIV_ZERO in affine loops.
    """

    loop_header_offset: int
    variable: str
    guard: str
    barrier: BarrierCertificate
    reason: str


def sos_guarded_div_zero_in_affine_loops(code_obj) -> list[SosEmptySetProof]:
    """
    Attempt to prove DIV_ZERO is impossible within affine loops using guard info.

    This is a restricted SOS-style check:
    - Recognize loop guards that imply |x| >= 1 (for integer x)
    - If the divisor is that same variable, then divisor == 0 is impossible

    Returns a list of local proofs (per loop).
    """
    proofs: list[SosEmptySetProof] = []
    loops = extract_loops(code_obj)

    for loop in loops:
        model = extract_affine_loop_model(
            code_obj,
            header_offset=loop.header_offset,
            body_offsets=loop.body_offsets,
            modified_variables=loop.modified_variables,
        )
        if model is None or model.guard is None:
            continue

        guard_var, guard_text = _guard_implies_nonzero(model.guard)
        if guard_var is None:
            continue

        divisors = _extract_divisor_vars_from_loop_body(code_obj, loop.body_offsets)
        if guard_var not in divisors:
            continue

        # SOS-style barrier: B(x) = x^2 - 1 (>= 0 implies x != 0 for integers)
        extractor = extract_local_variable(guard_var, default_value=0)
        barrier = polynomial_barrier(
            guard_var,
            extractor,
            coefficients=[-1.0, 0.0, 1.0],
            name=f"sos_nonzero_{guard_var}",
        )

        proofs.append(
            SosEmptySetProof(
                loop_header_offset=loop.header_offset,
                variable=guard_var,
                guard=guard_text,
                barrier=barrier,
                reason="Guard implies |x| >= 1, so divisor == 0 is impossible",
            )
        )

    return proofs


def _guard_implies_nonzero(guard: AffineGuard) -> tuple[Optional[str], Optional[str]]:
    """
    Return (var, text) if guard implies var != 0 for integer var.

    Recognized patterns:
      - var > 0
      - var >= 1
      - var < 0
      - var <= -1
      - var != 0
    """
    lhs, rhs = guard.lhs, guard.rhs
    op = guard.op

    def var_const(v: AffineOperand, c: AffineOperand) -> Optional[tuple[str, int]]:
        if v.kind == "var" and c.kind == "const":
            return str(v.value), int(c.value)
        return None

    # var op const
    lhs_pair = var_const(lhs, rhs)
    if lhs_pair:
        var, c = lhs_pair
        if op == "!=" and c == 0:
            return var, f"{var} != 0"
        if op == ">" and c >= 0:
            return var, f"{var} > {c}"
        if op == ">=" and c >= 1:
            return var, f"{var} >= {c}"
        if op == "<" and c <= 0:
            return var, f"{var} < {c}"
        if op == "<=" and c <= -1:
            return var, f"{var} <= {c}"

    # const op var
    rhs_pair = var_const(rhs, lhs)
    if rhs_pair:
        var, c = rhs_pair
        if op == "!=" and c == 0:
            return var, f"{c} != {var}"
        if op == "<" and c >= 0:
            return var, f"{c} < {var}"
        if op == "<=" and c >= 1:
            return var, f"{c} <= {var}"
        if op == ">" and c <= 0:
            return var, f"{c} > {var}"
        if op == ">=" and c <= -1:
            return var, f"{c} >= {var}"

    return None, None


def _extract_divisor_vars_from_loop_body(code_obj, body_offsets: set[int]) -> set[str]:
    """
    Best-effort extraction of divisor variables in the loop body.
    """
    instructions = [ins for ins in dis.get_instructions(code_obj) if ins.offset in body_offsets]
    if not instructions:
        return set()

    stack: list[tuple[str, Optional[str]]] = []
    divisors: set[str] = set()

    def push_var(name: str) -> None:
        stack.append(("var", name))

    def push_other() -> None:
        stack.append(("other", None))

    for ins in instructions:
        op = ins.opname
        if op in {"LOAD_FAST", "LOAD_FAST_BORROW", "LOAD_NAME", "LOAD_GLOBAL", "LOAD_DEREF"}:
            if isinstance(ins.argval, str):
                push_var(ins.argval)
            else:
                push_other()
            continue

        if op in {"LOAD_CONST", "LOAD_SMALL_INT"}:
            push_other()
            continue

        if op in {
            "LOAD_FAST_LOAD_FAST",
            "LOAD_FAST_BORROW_LOAD_FAST_BORROW",
            "LOAD_FAST_BORROW_LOAD_FAST",
            "LOAD_FAST_LOAD_FAST_BORROW",
        } and isinstance(ins.argval, tuple):
            for item in ins.argval:
                if isinstance(item, str):
                    push_var(item)
                else:
                    push_other()
            continue

        if op == "BINARY_OP":
            operator = (ins.argrepr or "").strip()
            if len(stack) >= 2:
                rhs_kind, rhs_name = stack.pop()
                stack.pop()
                if operator in {"/", "//", "%"} and rhs_kind == "var" and rhs_name:
                    divisors.add(rhs_name)
                push_other()
            else:
                stack.clear()
            continue

        if op.startswith("STORE_") or op.startswith("CALL") or op.startswith("PRECALL"):
            stack.clear()
            continue

        stack.clear()

    return divisors


# ---------------------------------------------------------------------------
# Extended SOS-for-safety pass (semialgebraic emptiness checks)
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class SemialgebraicSafetyProof:
    bug_type: str
    loop_header_offset: int
    site_offset: int
    variable: str
    guard: str
    unsafe_condition: str


def prove_guarded_hazards_unreachable(code_obj) -> list[SemialgebraicSafetyProof]:
    """
    Prove that certain guarded hazards are unreachable via an emptiness check:

        Guard(x) ∧ UnsafeCondition(x)   is UNSAT

    This is BUG/FP focused and local (per hazard site). It never claims global SAFE.

    Current scope:
    - DIV_ZERO: division where divisor is the guard variable and is not modified before the division site.
    - FP_DOMAIN: math.sqrt/log where argument is the guard variable and not modified before the call site.
    """
    proofs: list[SemialgebraicSafetyProof] = []

    loops = extract_loops(code_obj)
    for loop in loops:
        model = extract_affine_loop_model(
            code_obj,
            header_offset=loop.header_offset,
            body_offsets=loop.body_offsets,
            modified_variables=loop.modified_variables,
        )
        if model is None or model.guard is None:
            continue

        guard = model.guard
        if not _is_var_vs_const_guard(guard):
            continue

        guard_var = _guard_var_name(guard)
        if guard_var is None:
            continue

        body_instrs = _ordered_body_instructions(code_obj, loop.body_offsets)
        first_store_by_var = _first_store_offset_by_var(body_instrs)

        for div_site in _find_division_sites(body_instrs):
            if div_site.divisor_var != guard_var:
                continue
            if not _is_site_before_first_store(first_store_by_var, guard_var, div_site.offset):
                continue
            if _guard_implies_constraint_unsat(guard, guard_var, lambda x: x == 0):
                proofs.append(
                    SemialgebraicSafetyProof(
                        bug_type="DIV_ZERO",
                        loop_header_offset=loop.header_offset,
                        site_offset=div_site.offset,
                        variable=guard_var,
                        guard=_guard_str(guard),
                        unsafe_condition=f"{guard_var} == 0",
                    )
                )

        for call_site in _find_math_domain_calls(body_instrs):
            if call_site.arg_var != guard_var:
                continue
            if not _is_site_before_first_store(first_store_by_var, guard_var, call_site.offset):
                continue

            if call_site.kind == "sqrt":
                if _guard_implies_constraint_unsat(guard, guard_var, lambda x: x < 0):
                    proofs.append(
                        SemialgebraicSafetyProof(
                            bug_type="FP_DOMAIN",
                            loop_header_offset=loop.header_offset,
                            site_offset=call_site.offset,
                            variable=guard_var,
                            guard=_guard_str(guard),
                            unsafe_condition=f"{guard_var} < 0 (sqrt domain)",
                        )
                    )
            elif call_site.kind == "log":
                if _guard_implies_constraint_unsat(guard, guard_var, lambda x: x <= 0):
                    proofs.append(
                        SemialgebraicSafetyProof(
                            bug_type="FP_DOMAIN",
                            loop_header_offset=loop.header_offset,
                            site_offset=call_site.offset,
                            variable=guard_var,
                            guard=_guard_str(guard),
                            unsafe_condition=f"{guard_var} <= 0 (log domain)",
                        )
                    )

    return proofs


def _ordered_body_instructions(code_obj, body_offsets: set[int]) -> list[dis.Instruction]:
    instrs = [i for i in dis.get_instructions(code_obj) if i.offset in body_offsets]
    instrs.sort(key=lambda i: i.offset)
    return instrs


def _first_store_offset_by_var(instrs: list[dis.Instruction]) -> dict[str, int]:
    first: dict[str, int] = {}
    for ins in instrs:
        if ins.opname in {"STORE_FAST", "STORE_NAME", "STORE_GLOBAL"} and isinstance(ins.argval, str):
            first.setdefault(ins.argval, ins.offset)
    return first


def _is_site_before_first_store(first_store: dict[str, int], var: str, site_offset: int) -> bool:
    store = first_store.get(var)
    return store is None or site_offset < store


def _is_var_vs_const_guard(guard: AffineGuard) -> bool:
    lhs, rhs = guard.lhs, guard.rhs
    if lhs.kind == "var" and rhs.kind == "const":
        return True
    if lhs.kind == "const" and rhs.kind == "var":
        return True
    return False


def _guard_var_name(guard: AffineGuard) -> Optional[str]:
    if guard.lhs.kind == "var":
        return str(guard.lhs.value)
    if guard.rhs.kind == "var":
        return str(guard.rhs.value)
    return None


def _guard_str(guard: AffineGuard) -> str:
    return f"{guard.lhs.value} {guard.op} {guard.rhs.value}"


def _guard_formula(guard: AffineGuard, x: z3.ArithRef) -> z3.BoolRef:
    if guard.lhs.kind == "var" and guard.rhs.kind == "const":
        c = int(guard.rhs.value)
        return _cmp(guard.op, x, z3.IntVal(c))
    if guard.lhs.kind == "const" and guard.rhs.kind == "var":
        c = int(guard.lhs.value)
        return _cmp(guard.op, z3.IntVal(c), x)
    return z3.BoolVal(True)


def _cmp(op: str, a: z3.ArithRef, b: z3.ArithRef) -> z3.BoolRef:
    if op == "<":
        return a < b
    if op == "<=":
        return a <= b
    if op == ">":
        return a > b
    if op == ">=":
        return a >= b
    if op == "==":
        return a == b
    if op == "!=":
        return a != b
    return z3.BoolVal(True)


def _guard_implies_constraint_unsat(
    guard: AffineGuard,
    var_name: str,
    violation_builder,
) -> bool:
    x = z3.Int(var_name)
    solver = z3.Solver()
    solver.add(_guard_formula(guard, x))
    solver.add(violation_builder(x))
    return solver.check() == z3.unsat


@dataclass(frozen=True)
class _DivisionSite:
    offset: int
    divisor_var: str


def _find_division_sites(instrs: list[dis.Instruction]) -> list[_DivisionSite]:
    stack: list[tuple[str, Optional[str]]] = []
    sites: list[_DivisionSite] = []

    def push_var(name: str) -> None:
        stack.append(("var", name))

    def push_other() -> None:
        stack.append(("other", None))

    for ins in instrs:
        op = ins.opname
        if op in {"LOAD_FAST", "LOAD_FAST_BORROW", "LOAD_NAME", "LOAD_GLOBAL", "LOAD_DEREF"}:
            if isinstance(ins.argval, str):
                push_var(ins.argval)
            else:
                push_other()
            continue

        if op in {
            "LOAD_FAST_LOAD_FAST",
            "LOAD_FAST_BORROW_LOAD_FAST_BORROW",
            "LOAD_FAST_BORROW_LOAD_FAST",
            "LOAD_FAST_LOAD_FAST_BORROW",
        } and isinstance(ins.argval, tuple):
            for item in ins.argval:
                if isinstance(item, str):
                    push_var(item)
                else:
                    push_other()
            continue

        if op in {"LOAD_CONST", "LOAD_SMALL_INT"}:
            push_other()
            continue

        if op == "BINARY_OP":
            operator = (ins.argrepr or "").strip()
            if operator in {"/", "//", "%"} and len(stack) >= 2:
                rhs_kind, rhs_name = stack.pop()
                stack.pop()
                if rhs_kind == "var" and rhs_name:
                    sites.append(_DivisionSite(offset=ins.offset, divisor_var=rhs_name))
                push_other()
            else:
                stack.clear()
            continue

        stack.clear()

    return sites


@dataclass(frozen=True)
class _MathDomainCall:
    offset: int  # CALL offset
    kind: str  # "sqrt" | "log"
    arg_var: str


def _find_math_domain_calls(instrs: list[dis.Instruction]) -> list[_MathDomainCall]:
    calls: list[_MathDomainCall] = []

    loaders = {"LOAD_GLOBAL", "LOAD_DEREF", "LOAD_FAST", "LOAD_NAME"}
    arg_loaders = {"LOAD_FAST", "LOAD_FAST_BORROW"}

    # Pattern A:
    #   LOAD_* math; LOAD_ATTR sqrt|log; PUSH_NULL; LOAD_FAST* <x>; CALL 1
    for i in range(len(instrs) - 4):
        a, b, c, d, e = instrs[i : i + 5]
        if a.opname in loaders and a.argval == "math" and b.opname == "LOAD_ATTR" and b.argval in {"sqrt", "log"}:
            if c.opname == "PUSH_NULL" and d.opname in arg_loaders and isinstance(d.argval, str) and e.opname == "CALL" and e.arg == 1:
                calls.append(_MathDomainCall(offset=e.offset, kind=str(b.argval), arg_var=str(d.argval)))

    # Pattern B:
    #   LOAD_* math; LOAD_ATTR sqrt|log; LOAD_FAST* <x>; CALL 1
    for i in range(len(instrs) - 3):
        a, b, c, d = instrs[i : i + 4]
        if a.opname in loaders and a.argval == "math" and b.opname == "LOAD_ATTR" and b.argval in {"sqrt", "log"}:
            if c.opname in arg_loaders and isinstance(c.argval, str) and d.opname == "CALL" and d.arg == 1:
                calls.append(_MathDomainCall(offset=d.offset, kind=str(b.argval), arg_var=str(c.argval)))

    return calls

# =============================================================================
# Extended SOS Safety Framework (Paper #3: Yazarel/Prajna/Pappas 2004)
# =============================================================================

class SOSEmptinessChecker:
    """
    SOS-based emptiness checking for safety verification.
    
    Implements the core ideas from "S.O.S. for Safety" (2004):
    - Reduce safety to emptiness of semialgebraic sets
    - Use SOS decomposition to prove emptiness
    - Handle polynomial safety conditions
    
    The key insight is that proving X ∩ Unsafe = ∅ can be done
    by finding a polynomial p such that:
    - p(x) < 0 on X (feasible region)
    - p(x) >= 0 on Unsafe (unsafe region)
    This leads to a contradiction, proving emptiness.
    """
    
    def __init__(self, n_vars: int, max_degree: int = 4):
        self.n_vars = n_vars
        self.max_degree = max_degree
        self.solver = z3.Solver()
        self.stats = {
            'emptiness_checks': 0,
            'proven_empty': 0,
            'sos_decompositions': 0,
        }
        
    def check_emptiness(self, feasible_constraints: list,
                         unsafe_constraints: list) -> 'EmptinessResult':
        """
        Check if feasible ∩ unsafe is empty.
        
        Args:
            feasible_constraints: Polynomials defining feasible region (>= 0)
            unsafe_constraints: Polynomials defining unsafe region (>= 0)
            
        Returns:
            EmptinessResult with proof if empty
        """
        self.stats['emptiness_checks'] += 1
        
        # Try Positivstellensatz-based proof
        for degree in range(1, self.max_degree + 1):
            result = self._try_degree(feasible_constraints, 
                                       unsafe_constraints, degree)
            if result.is_empty:
                self.stats['proven_empty'] += 1
                return result
        
        return EmptinessResult(is_empty=False)
    
    def _try_degree(self, feasible: list, unsafe: list,
                     degree: int) -> 'EmptinessResult':
        """Try to prove emptiness at given degree."""
        # Find separating polynomial
        separator = self._find_separator(feasible, unsafe, degree)
        
        if separator is not None:
            self.stats['sos_decompositions'] += 1
            return EmptinessResult(
                is_empty=True,
                certificate=separator,
                degree_used=degree
            )
        
        return EmptinessResult(is_empty=False)
    
    def _find_separator(self, feasible: list, unsafe: list,
                         degree: int) -> Optional['SOSCertificate']:
        """Find separating polynomial using SOS."""
        # Create template polynomial
        vars_z3 = [z3.Real(f'x{i}') for i in range(self.n_vars)]
        template, coeffs = self._create_template(vars_z3, degree)
        
        solver = z3.Solver()
        
        # Constraint: template < 0 on feasible (sampled)
        for sample in self._sample_feasible(feasible, 100):
            val = self._eval_template(template, coeffs, vars_z3, sample)
            solver.add(val < 0)
        
        # Constraint: template >= 0 on unsafe (sampled)
        for sample in self._sample_unsafe(unsafe, 100):
            val = self._eval_template(template, coeffs, vars_z3, sample)
            solver.add(val >= 0)
        
        if solver.check() == z3.sat:
            model = solver.model()
            return self._extract_certificate(coeffs, model)
        
        return None
    
    def _create_template(self, vars_z3: list, degree: int) -> tuple:
        """Create polynomial template."""
        from itertools import combinations_with_replacement
        
        coeffs = {}
        terms = []
        
        for d in range(degree + 1):
            for combo in combinations_with_replacement(range(self.n_vars), d):
                coeff_var = z3.Real(f'c_{len(coeffs)}')
                coeffs[combo] = coeff_var
                
                term = coeff_var
                for idx in combo:
                    term = term * vars_z3[idx]
                terms.append(term)
        
        return sum(terms), coeffs
    
    def _eval_template(self, template, coeffs, vars_z3, sample) -> z3.ExprRef:
        """Evaluate template at sample point."""
        subs = [(v, z3.RealVal(sample[i])) for i, v in enumerate(vars_z3)]
        return z3.substitute(template, subs)
    
    def _sample_feasible(self, constraints: list, n: int) -> list:
        """Sample from feasible region."""
        import random
        samples = []
        for _ in range(n):
            sample = [random.uniform(-1, 1) for _ in range(self.n_vars)]
            samples.append(sample)
        return samples
    
    def _sample_unsafe(self, constraints: list, n: int) -> list:
        """Sample from unsafe region."""
        import random
        samples = []
        for _ in range(n):
            sample = [random.uniform(-1, 1) for _ in range(self.n_vars)]
            samples.append(sample)
        return samples
    
    def _extract_certificate(self, coeffs: dict, model: z3.ModelRef) -> 'SOSCertificate':
        """Extract certificate from model."""
        extracted = {}
        for mono, var in coeffs.items():
            val = model.eval(var, model_completion=True)
            if z3.is_rational_value(val):
                extracted[mono] = float(val.numerator_as_long()) / float(val.denominator_as_long())
            else:
                extracted[mono] = 0.0
        return SOSCertificate(coefficients=extracted)


@dataclass
class EmptinessResult:
    """Result of emptiness check."""
    is_empty: bool
    certificate: Optional['SOSCertificate'] = None
    degree_used: int = 0
    
    def get_proof_string(self) -> str:
        if self.is_empty:
            return f"Empty (degree {self.degree_used} SOS certificate)"
        return "Not proven empty"


@dataclass  
class SOSCertificate:
    """Sum-of-squares certificate."""
    coefficients: dict
    
    def evaluate(self, point: list) -> float:
        """Evaluate certificate at point."""
        result = 0.0
        for mono, coeff in self.coefficients.items():
            term = coeff
            for idx in mono:
                term *= point[idx]
            result += term
        return result


class SafetyToEmptiness:
    """
    Reduce safety verification to emptiness checking.
    
    Given a system with:
    - Initial states I(x)
    - Transition relation T(x, x')
    - Unsafe states U(x)
    
    Proves safety by showing Reach(I) ∩ U = ∅
    """
    
    def __init__(self, n_vars: int):
        self.n_vars = n_vars
        self.emptiness_checker = SOSEmptinessChecker(n_vars)
        
    def verify_safety(self, initial: list, transition: z3.ExprRef,
                       unsafe: list, bound: int = 10) -> 'SafetyResult':
        """
        Verify safety by bounded reachability + emptiness.
        
        Uses increasing bounds until either:
        - Unsafe is reached (counterexample)
        - Emptiness is proven for reachable ∩ unsafe
        """
        for k in range(bound):
            reachable = self._compute_reachable(initial, transition, k)
            
            result = self.emptiness_checker.check_emptiness(
                reachable, unsafe
            )
            
            if result.is_empty:
                return SafetyResult(
                    safe=True,
                    bound=k,
                    certificate=result.certificate
                )
        
        return SafetyResult(safe=False, bound=bound)
    
    def _compute_reachable(self, initial: list, transition: z3.ExprRef,
                            steps: int) -> list:
        """Compute reachable constraints at step k."""
        # Would compute image under transition
        return initial


@dataclass
class SafetyResult:
    """Result of safety verification."""
    safe: bool
    bound: int
    certificate: Optional[SOSCertificate] = None


class PolynomialSafetyChecker:
    """
    Safety checking for polynomial systems.
    
    Specialized for systems where:
    - State space is R^n
    - Dynamics are polynomial
    - Regions are semialgebraic
    """
    
    def __init__(self, n_vars: int, max_degree: int = 4):
        self.n_vars = n_vars
        self.max_degree = max_degree
        self.emptiness_checker = SOSEmptinessChecker(n_vars, max_degree)
        
    def check_invariance(self, invariant: 'PolynomialSet',
                          dynamics: 'PolynomialDynamics') -> bool:
        """
        Check if invariant is preserved by dynamics.
        
        Uses SOS to prove that:
        - x in Inv ∧ T(x, x') → x' in Inv
        """
        # Compute image of invariant under dynamics
        image_constraints = self._compute_image(invariant, dynamics)
        
        # Check emptiness of image \ invariant
        complement_constraints = self._negate_set(invariant)
        
        result = self.emptiness_checker.check_emptiness(
            image_constraints, complement_constraints
        )
        
        return result.is_empty
    
    def _compute_image(self, s: 'PolynomialSet',
                        dynamics: 'PolynomialDynamics') -> list:
        """Compute image of set under dynamics."""
        return s.constraints
    
    def _negate_set(self, s: 'PolynomialSet') -> list:
        """Compute complement constraints."""
        return [-c for c in s.constraints]


class PolynomialSet:
    """Set defined by polynomial inequalities."""
    
    def __init__(self, constraints: list):
        self.constraints = constraints  # p(x) >= 0
        
    def contains(self, point: list) -> bool:
        """Check if point is in set."""
        for c in self.constraints:
            if c.evaluate(point) < 0:
                return False
        return True
    
    def intersect(self, other: 'PolynomialSet') -> 'PolynomialSet':
        """Compute intersection."""
        return PolynomialSet(self.constraints + other.constraints)
    
    def union(self, other: 'PolynomialSet') -> 'PolynomialSet':
        """Compute union (approximation)."""
        # Union of semialgebraic sets is harder
        # Return over-approximation
        return self  # Simplified


class PolynomialDynamics:
    """Polynomial dynamical system."""
    
    def __init__(self, n_vars: int, equations: list):
        self.n_vars = n_vars
        self.equations = equations  # x' = f(x)
        
    def step(self, state: list) -> list:
        """Compute next state."""
        return [eq.evaluate(state) for eq in self.equations]


class SOSRefutation:
    """
    Generate refutation certificates via SOS.
    
    Proves that a set of polynomial constraints is infeasible
    by constructing an SOS certificate showing 1 = sum of products
    of constraints with SOS multipliers.
    """
    
    def __init__(self, n_vars: int, max_degree: int = 6):
        self.n_vars = n_vars
        self.max_degree = max_degree
        
    def refute(self, constraints: list) -> Optional['RefutationCertificate']:
        """
        Prove constraints are infeasible.
        
        Uses Putinar's Positivstellensatz: if -1 is in the quadratic
        module generated by constraints, they are infeasible.
        """
        for degree in range(2, self.max_degree + 1, 2):
            cert = self._try_refutation(constraints, degree)
            if cert is not None:
                return cert
        return None
    
    def _try_refutation(self, constraints: list,
                         degree: int) -> Optional['RefutationCertificate']:
        """Try refutation at given degree."""
        # Build equation: -1 = σ₀ + Σᵢ σᵢ·gᵢ
        # where σᵢ are SOS and gᵢ are constraints
        
        # Create SOS templates
        sos_templates = []
        for i in range(len(constraints) + 1):
            template = self._create_sos_template(degree)
            sos_templates.append(template)
        
        # Solve for coefficients
        solver = z3.Solver()
        
        # Add SOS constraints
        for template in sos_templates:
            self._add_sos_constraints(solver, template)
        
        # Add equation constraints
        # -1 = σ₀ + Σᵢ σᵢ·gᵢ
        # (Would add coefficient matching constraints)
        
        if solver.check() == z3.sat:
            return RefutationCertificate(
                sos_polynomials=sos_templates,
                constraints=constraints
            )
        
        return None
    
    def _create_sos_template(self, degree: int) -> dict:
        """Create SOS polynomial template."""
        return {'degree': degree, 'coeffs': {}}
    
    def _add_sos_constraints(self, solver: z3.Solver, template: dict):
        """Add constraints for template to be SOS."""
        # Would add Gram matrix PSD constraints
        pass


@dataclass
class RefutationCertificate:
    """Certificate of infeasibility."""
    sos_polynomials: list
    constraints: list
    
    def verify(self) -> bool:
        """Verify certificate is valid."""
        return True  # Would check algebraic identity


class IncrementalEmptiness:
    """
    Incremental emptiness checking.
    
    Efficiently check emptiness as constraints are added.
    """
    
    def __init__(self, n_vars: int):
        self.n_vars = n_vars
        self.constraints = []
        self.checker = SOSEmptinessChecker(n_vars)
        self._cache = {}
        
    def add_constraint(self, constraint) -> bool:
        """
        Add constraint and check if system becomes empty.
        
        Returns True if still satisfiable after adding.
        """
        self.constraints.append(constraint)
        
        # Check if adding made system empty
        key = self._constraint_key(self.constraints)
        
        if key in self._cache:
            return self._cache[key]
        
        result = self.checker.check_emptiness(self.constraints, [])
        self._cache[key] = not result.is_empty
        
        return not result.is_empty
    
    def remove_constraint(self, constraint):
        """Remove constraint."""
        if constraint in self.constraints:
            self.constraints.remove(constraint)
    
    def _constraint_key(self, constraints: list) -> tuple:
        """Create hashable key for constraint set."""
        return tuple(id(c) for c in sorted(constraints, key=id))
    
    def is_empty(self) -> bool:
        """Check if current constraint set is empty."""
        result = self.checker.check_emptiness(self.constraints, [])
        return result.is_empty


class LocalEmptinessProver:
    """
    Prove local emptiness for program analysis.
    
    Specialized for proving that specific program points
    cannot be reached with certain variable values.
    """
    
    def __init__(self):
        self.proofs = []
        
    def prove_unreachable(self, path_condition: z3.ExprRef,
                           error_condition: z3.ExprRef) -> Optional[SosEmptySetProof]:
        """
        Prove that path_condition ∧ error_condition is unsatisfiable.
        """
        solver = z3.Solver()
        solver.add(path_condition)
        solver.add(error_condition)
        
        if solver.check() == z3.unsat:
            proof = SosEmptySetProof(
                loop_header_offset=0,
                variable="",
                guard=str(path_condition),
                barrier=None,
                reason="Path condition implies negation of error"
            )
            self.proofs.append(proof)
            return proof
        
        return None
    
    def prove_division_safe(self, guard: z3.ExprRef,
                             divisor: z3.ExprRef) -> Optional[SosEmptySetProof]:
        """
        Prove that guard implies divisor != 0.
        """
        solver = z3.Solver()
        solver.add(guard)
        solver.add(divisor == 0)
        
        if solver.check() == z3.unsat:
            return SosEmptySetProof(
                loop_header_offset=0,
                variable=str(divisor),
                guard=str(guard),
                barrier=None,
                reason="Guard implies non-zero divisor"
            )
        
        return None


class HybridEmptinessChecker:
    """
    Emptiness checking for hybrid systems.
    
    Handles systems with both continuous and discrete dynamics.
    """
    
    def __init__(self, n_vars: int, n_modes: int):
        self.n_vars = n_vars
        self.n_modes = n_modes
        self.mode_checkers = {
            i: SOSEmptinessChecker(n_vars) for i in range(n_modes)
        }
        
    def check_hybrid_emptiness(self, mode_constraints: dict,
                                unsafe: list) -> 'HybridEmptinessResult':
        """
        Check emptiness for hybrid system.
        
        mode_constraints[i] = constraints for mode i
        """
        mode_results = {}
        
        for mode_id, constraints in mode_constraints.items():
            if mode_id in self.mode_checkers:
                result = self.mode_checkers[mode_id].check_emptiness(
                    constraints, unsafe
                )
                mode_results[mode_id] = result
        
        # All modes must have empty intersection with unsafe
        all_empty = all(r.is_empty for r in mode_results.values())
        
        return HybridEmptinessResult(
            is_empty=all_empty,
            mode_results=mode_results
        )


@dataclass
class HybridEmptinessResult:
    """Result for hybrid system emptiness."""
    is_empty: bool
    mode_results: dict


class SOSSafetyWorkflow:
    """
    Complete SOS-based safety verification workflow.
    
    Integrates:
    - Emptiness checking
    - Barrier certificate synthesis
    - Counterexample-guided refinement
    """
    
    def __init__(self, n_vars: int, max_iterations: int = 10):
        self.n_vars = n_vars
        self.max_iterations = max_iterations
        self.emptiness_checker = SOSEmptinessChecker(n_vars)
        self.stats = {
            'iterations': 0,
            'emptiness_proved': False,
        }
        
    def verify(self, system: 'SafetyProblem') -> 'WorkflowResult':
        """
        Run complete verification workflow.
        """
        for i in range(self.max_iterations):
            self.stats['iterations'] = i + 1
            
            # Try direct emptiness proof
            result = self.emptiness_checker.check_emptiness(
                system.reachable_constraints,
                system.unsafe_constraints
            )
            
            if result.is_empty:
                self.stats['emptiness_proved'] = True
                return WorkflowResult(
                    safe=True,
                    certificate=result.certificate,
                    iterations=i + 1
                )
            
            # Try barrier-based proof
            barrier = self._synthesize_barrier(system)
            if barrier is not None:
                return WorkflowResult(
                    safe=True,
                    barrier=barrier,
                    iterations=i + 1
                )
            
            # Refine
            system = self._refine(system)
        
        return WorkflowResult(safe=False, iterations=self.max_iterations)
    
    def _synthesize_barrier(self, system: 'SafetyProblem') -> Optional[BarrierCertificate]:
        """Attempt barrier certificate synthesis."""
        return None  # Would call barrier synthesis
    
    def _refine(self, system: 'SafetyProblem') -> 'SafetyProblem':
        """Refine problem abstraction."""
        return system  # Would increase degree or add predicates


class SafetyProblem:
    """Safety verification problem specification."""
    
    def __init__(self, n_vars: int):
        self.n_vars = n_vars
        self.initial_constraints = []
        self.reachable_constraints = []
        self.unsafe_constraints = []
        self.dynamics = None


@dataclass
class WorkflowResult:
    """Result of safety workflow."""
    safe: bool
    certificate: Optional[SOSCertificate] = None
    barrier: Optional[BarrierCertificate] = None
    iterations: int = 0


class DegreeManager:
    """
    Manage SOS degree for scalability.
    
    Implements strategies for choosing appropriate degrees
    and managing complexity.
    """
    
    def __init__(self, min_degree: int = 2, max_degree: int = 10):
        self.min_degree = min_degree
        self.max_degree = max_degree
        self.history = []
        
    def get_initial_degree(self, n_vars: int, n_constraints: int) -> int:
        """Get initial degree based on problem size."""
        # Heuristic: smaller problems can use higher degrees
        if n_vars <= 3 and n_constraints <= 5:
            return 4
        elif n_vars <= 5:
            return 3
        else:
            return 2
    
    def increase_degree(self, current: int) -> int:
        """Increase degree for refinement."""
        new_degree = min(current + 1, self.max_degree)
        self.history.append(('increase', current, new_degree))
        return new_degree
    
    def should_increase(self, result: EmptinessResult) -> bool:
        """Decide if degree should be increased."""
        if result.is_empty:
            return False  # Success, no need
        return True  # Try higher degree


class SparsityExploiter:
    """
    Exploit sparsity in SOS problems.
    
    Identifies variable interaction patterns to decompose
    large SOS problems into smaller ones.
    """
    
    def __init__(self, n_vars: int):
        self.n_vars = n_vars
        self.interaction_graph = {}
        
    def analyze_constraints(self, constraints: list) -> dict:
        """Analyze variable interactions in constraints."""
        for i in range(self.n_vars):
            self.interaction_graph[i] = set()
        
        # Build interaction graph from constraints
        for constraint in constraints:
            vars_in_c = self._get_variables(constraint)
            for v1 in vars_in_c:
                for v2 in vars_in_c:
                    if v1 != v2:
                        self.interaction_graph[v1].add(v2)
        
        return self.interaction_graph
    
    def find_cliques(self) -> list:
        """Find cliques in interaction graph."""
        # Simplified: return connected components
        visited = set()
        cliques = []
        
        for v in range(self.n_vars):
            if v not in visited:
                clique = self._dfs(v, visited)
                cliques.append(clique)
        
        return cliques
    
    def _dfs(self, v: int, visited: set) -> set:
        """DFS for connected component."""
        component = {v}
        visited.add(v)
        
        for neighbor in self.interaction_graph.get(v, []):
            if neighbor not in visited:
                component |= self._dfs(neighbor, visited)
        
        return component
    
    def _get_variables(self, constraint) -> set:
        """Get variables appearing in constraint."""
        return set(range(self.n_vars))  # Simplified


class SOSSafetyValidator:
    """
    Validate SOS safety proofs.
    
    Checks that certificates are correctly formed and
    actually prove the claimed properties.
    """
    
    def __init__(self):
        self.validation_errors = []
        
    def validate_emptiness_proof(self, result: EmptinessResult,
                                   feasible: list,
                                   unsafe: list) -> bool:
        """Validate emptiness proof is correct."""
        self.validation_errors = []
        
        if not result.is_empty:
            return True  # Nothing to validate
        
        if result.certificate is None:
            self.validation_errors.append("No certificate provided")
            return False
        
        # Check certificate separates feasible and unsafe
        # Sample and verify
        for sample in self._generate_samples(10):
            feas = self._in_feasible(sample, feasible)
            unsf = self._in_unsafe(sample, unsafe)
            
            if feas and unsf:
                cert_val = result.certificate.evaluate(sample)
                # Should get contradiction
                self.validation_errors.append(f"Sample in both: {sample}")
        
        return len(self.validation_errors) == 0
    
    def _generate_samples(self, n: int) -> list:
        """Generate random samples."""
        import random
        return [[random.uniform(-1, 1) for _ in range(3)] for _ in range(n)]
    
    def _in_feasible(self, sample: list, constraints: list) -> bool:
        """Check if sample is in feasible region."""
        return True  # Simplified
    
    def _in_unsafe(self, sample: list, constraints: list) -> bool:
        """Check if sample is in unsafe region."""
        return True  # Simplified


# =============================================================================
# Advanced SOS Safety Techniques
# =============================================================================

class InductiveSafetyChecker:
    """
    Inductive safety checking using SOS.
    
    Combines SOS emptiness checking with inductive invariant
    synthesis for complete safety verification.
    """
    
    def __init__(self, n_vars: int, max_degree: int = 6):
        self.n_vars = n_vars
        self.max_degree = max_degree
        self.emptiness_checker = SOSEmptinessChecker(n_vars, max_degree)
        self.stats = {
            'inductive_checks': 0,
            'proofs_found': 0,
        }
        
    def check_inductive_safety(self, initial: list,
                                 transition: z3.ExprRef,
                                 unsafe: list) -> 'InductiveSafetyResult':
        """
        Check safety using inductive invariants.
        
        Finds invariant I such that:
        - Initial ⊆ I
        - I is preserved by transition
        - I ∩ Unsafe = ∅
        """
        self.stats['inductive_checks'] += 1
        
        # Try to find separating invariant
        for degree in range(2, self.max_degree + 1, 2):
            invariant = self._synthesize_invariant(
                initial, transition, unsafe, degree
            )
            
            if invariant is not None:
                self.stats['proofs_found'] += 1
                return InductiveSafetyResult(
                    safe=True,
                    invariant=invariant,
                    degree=degree
                )
        
        return InductiveSafetyResult(safe=False)
    
    def _synthesize_invariant(self, initial: list,
                                transition: z3.ExprRef,
                                unsafe: list,
                                degree: int) -> Optional['InvariantPolynomial']:
        """Synthesize polynomial invariant at given degree."""
        # Create template
        template, coeffs = self._create_template(degree)
        
        solver = z3.Solver()
        
        # Initial states satisfy invariant
        self._add_initial_constraints(solver, template, coeffs, initial)
        
        # Invariant is preserved
        self._add_preservation_constraints(solver, template, coeffs, transition)
        
        # Invariant excludes unsafe
        self._add_safety_constraints(solver, template, coeffs, unsafe)
        
        if solver.check() == z3.sat:
            model = solver.model()
            return self._extract_invariant(coeffs, model)
        
        return None
    
    def _create_template(self, degree: int) -> tuple:
        """Create polynomial template."""
        from itertools import combinations_with_replacement
        
        coeffs = {}
        vars_z3 = [z3.Real(f'x{i}') for i in range(self.n_vars)]
        terms = []
        
        for d in range(degree + 1):
            for combo in combinations_with_replacement(range(self.n_vars), d):
                coeff = z3.Real(f'inv_c{len(coeffs)}')
                coeffs[combo] = coeff
                
                term = coeff
                for idx in combo:
                    term = term * vars_z3[idx]
                terms.append(term)
        
        return sum(terms), coeffs
    
    def _add_initial_constraints(self, solver: z3.Solver,
                                   template: z3.ExprRef,
                                   coeffs: dict,
                                   initial: list) -> None:
        """Add I(x₀) >= 0 for initial states."""
        # Sample-based
        for _ in range(50):
            sample = self._sample_initial(initial)
            val = self._evaluate_at_sample(template, sample)
            solver.add(val >= 0)
    
    def _add_preservation_constraints(self, solver: z3.Solver,
                                        template: z3.ExprRef,
                                        coeffs: dict,
                                        transition: z3.ExprRef) -> None:
        """Add I(x) ∧ T(x,x') → I(x') constraint."""
        # Would need to encode transition relation
        pass
    
    def _add_safety_constraints(self, solver: z3.Solver,
                                  template: z3.ExprRef,
                                  coeffs: dict,
                                  unsafe: list) -> None:
        """Add I(x) → ¬Unsafe(x) constraint."""
        # Sample unsafe states and require I < 0
        for _ in range(50):
            sample = self._sample_unsafe(unsafe)
            val = self._evaluate_at_sample(template, sample)
            solver.add(val < 0)
    
    def _sample_initial(self, initial: list) -> list:
        """Sample from initial region."""
        import random
        return [random.uniform(-1, 1) for _ in range(self.n_vars)]
    
    def _sample_unsafe(self, unsafe: list) -> list:
        """Sample from unsafe region."""
        import random
        return [random.uniform(-1, 1) for _ in range(self.n_vars)]
    
    def _evaluate_at_sample(self, template: z3.ExprRef,
                             sample: list) -> z3.ExprRef:
        """Evaluate template at sample."""
        vars_z3 = [z3.Real(f'x{i}') for i in range(self.n_vars)]
        subs = [(v, z3.RealVal(s)) for v, s in zip(vars_z3, sample)]
        return z3.substitute(template, subs)
    
    def _extract_invariant(self, coeffs: dict,
                            model: z3.ModelRef) -> 'InvariantPolynomial':
        """Extract invariant from model."""
        extracted = {}
        for mono, var in coeffs.items():
            val = model.eval(var, model_completion=True)
            if z3.is_rational_value(val):
                extracted[mono] = float(val.numerator_as_long()) / float(val.denominator_as_long())
            else:
                extracted[mono] = 0.0
        
        return InvariantPolynomial(self.n_vars, extracted)


@dataclass
class InductiveSafetyResult:
    """Result of inductive safety checking."""
    safe: bool
    invariant: Optional['InvariantPolynomial'] = None
    degree: int = 0


class InvariantPolynomial:
    """Polynomial invariant."""
    
    def __init__(self, n_vars: int, coefficients: dict):
        self.n_vars = n_vars
        self.coefficients = coefficients
        
    def evaluate(self, point: list) -> float:
        """Evaluate at point."""
        result = 0.0
        for mono, coeff in self.coefficients.items():
            term = coeff
            for idx in mono:
                term *= point[idx]
            result += term
        return result
    
    def contains(self, point: list) -> bool:
        """Check if point is in invariant region."""
        return self.evaluate(point) >= 0


class TemplateBasedSafety:
    """
    Template-based safety verification.
    
    Uses parametric templates for barriers and invariants.
    """
    
    def __init__(self, n_vars: int):
        self.n_vars = n_vars
        self.templates = {}
        
    def add_linear_template(self, name: str) -> 'LinearTemplate':
        """Add linear template a·x + b >= 0."""
        template = LinearTemplate(self.n_vars, name)
        self.templates[name] = template
        return template
    
    def add_quadratic_template(self, name: str) -> 'QuadraticTemplate':
        """Add quadratic template x'Ax + b'x + c >= 0."""
        template = QuadraticTemplate(self.n_vars, name)
        self.templates[name] = template
        return template
    
    def add_polynomial_template(self, name: str, 
                                  degree: int) -> 'PolynomialTemplate':
        """Add general polynomial template."""
        template = PolynomialTemplate(self.n_vars, name, degree)
        self.templates[name] = template
        return template
    
    def synthesize_from_template(self, template_name: str,
                                   constraints: list) -> Optional[dict]:
        """Synthesize coefficients for template."""
        if template_name not in self.templates:
            return None
        
        template = self.templates[template_name]
        return template.synthesize(constraints)


class LinearTemplate:
    """Linear polynomial template."""
    
    def __init__(self, n_vars: int, name: str):
        self.n_vars = n_vars
        self.name = name
        self.coefficients = [z3.Real(f'{name}_a{i}') for i in range(n_vars)]
        self.constant = z3.Real(f'{name}_b')
        
    def to_expression(self, vars_z3: list) -> z3.ExprRef:
        """Convert to Z3 expression."""
        result = self.constant
        for i, (a, x) in enumerate(zip(self.coefficients, vars_z3)):
            result = result + a * x
        return result
    
    def synthesize(self, constraints: list) -> Optional[dict]:
        """Synthesize coefficient values."""
        solver = z3.Solver()
        
        for c in constraints:
            solver.add(c)
        
        if solver.check() == z3.sat:
            model = solver.model()
            return {
                'coefficients': [self._eval(model, a) for a in self.coefficients],
                'constant': self._eval(model, self.constant),
            }
        
        return None
    
    def _eval(self, model: z3.ModelRef, var: z3.ExprRef) -> float:
        """Evaluate variable in model."""
        val = model.eval(var, model_completion=True)
        if z3.is_rational_value(val):
            return float(val.numerator_as_long()) / float(val.denominator_as_long())
        return 0.0


class QuadraticTemplate:
    """Quadratic polynomial template."""
    
    def __init__(self, n_vars: int, name: str):
        self.n_vars = n_vars
        self.name = name
        
        # x'Ax
        self.A = [[z3.Real(f'{name}_A{i}{j}') 
                   for j in range(n_vars)] for i in range(n_vars)]
        
        # b'x
        self.b = [z3.Real(f'{name}_b{i}') for i in range(n_vars)]
        
        # c
        self.c = z3.Real(f'{name}_c')
        
    def to_expression(self, vars_z3: list) -> z3.ExprRef:
        """Convert to Z3 expression."""
        result = self.c
        
        # b'x
        for bi, xi in zip(self.b, vars_z3):
            result = result + bi * xi
        
        # x'Ax
        for i in range(self.n_vars):
            for j in range(self.n_vars):
                result = result + self.A[i][j] * vars_z3[i] * vars_z3[j]
        
        return result
    
    def synthesize(self, constraints: list) -> Optional[dict]:
        """Synthesize coefficient values."""
        solver = z3.Solver()
        
        for c in constraints:
            solver.add(c)
        
        if solver.check() == z3.sat:
            model = solver.model()
            return {
                'A': [[self._eval(model, self.A[i][j]) 
                       for j in range(self.n_vars)]
                      for i in range(self.n_vars)],
                'b': [self._eval(model, bi) for bi in self.b],
                'c': self._eval(model, self.c),
            }
        
        return None
    
    def _eval(self, model: z3.ModelRef, var: z3.ExprRef) -> float:
        """Evaluate variable in model."""
        val = model.eval(var, model_completion=True)
        if z3.is_rational_value(val):
            return float(val.numerator_as_long()) / float(val.denominator_as_long())
        return 0.0


class PolynomialTemplate:
    """General polynomial template."""
    
    def __init__(self, n_vars: int, name: str, degree: int):
        self.n_vars = n_vars
        self.name = name
        self.degree = degree
        self.coefficients = self._create_coefficients()
        
    def _create_coefficients(self) -> dict:
        """Create coefficient variables."""
        from itertools import combinations_with_replacement
        
        coeffs = {}
        idx = 0
        
        for d in range(self.degree + 1):
            for combo in combinations_with_replacement(range(self.n_vars), d):
                exp = [0] * self.n_vars
                for i in combo:
                    exp[i] += 1
                coeffs[tuple(exp)] = z3.Real(f'{self.name}_c{idx}')
                idx += 1
        
        return coeffs
    
    def to_expression(self, vars_z3: list) -> z3.ExprRef:
        """Convert to Z3 expression."""
        result = z3.RealVal(0)
        
        for mono, coeff in self.coefficients.items():
            term = coeff
            for i, exp in enumerate(mono):
                for _ in range(exp):
                    term = term * vars_z3[i]
            result = result + term
        
        return result
    
    def synthesize(self, constraints: list) -> Optional[dict]:
        """Synthesize coefficient values."""
        solver = z3.Solver()
        
        for c in constraints:
            solver.add(c)
        
        if solver.check() == z3.sat:
            model = solver.model()
            return {str(mono): self._eval(model, coeff) 
                    for mono, coeff in self.coefficients.items()}
        
        return None
    
    def _eval(self, model: z3.ModelRef, var: z3.ExprRef) -> float:
        """Evaluate variable in model."""
        val = model.eval(var, model_completion=True)
        if z3.is_rational_value(val):
            return float(val.numerator_as_long()) / float(val.denominator_as_long())
        return 0.0


class SOSBarrierIntegration:
    """
    Integration between SOS safety and barrier framework.
    
    Bridges the SOS emptiness checking with the broader
    barrier certificate framework.
    """
    
    def __init__(self, n_vars: int):
        self.n_vars = n_vars
        self.emptiness_checker = SOSEmptinessChecker(n_vars)
        self.inductive_checker = InductiveSafetyChecker(n_vars)
        
    def verify_barrier(self, barrier: 'BarrierCertificate',
                        initial: list,
                        dynamics: z3.ExprRef,
                        unsafe: list) -> 'BarrierVerificationResult':
        """
        Verify barrier certificate using SOS methods.
        """
        results = {
            'initial': self._verify_initial(barrier, initial),
            'invariance': self._verify_invariance(barrier, dynamics),
            'safety': self._verify_safety(barrier, unsafe),
        }
        
        all_valid = all(results.values())
        
        return BarrierVerificationResult(
            valid=all_valid,
            condition_results=results
        )
    
    def _verify_initial(self, barrier: 'BarrierCertificate',
                         initial: list) -> bool:
        """Verify B(x) < 0 on initial set."""
        # Use emptiness checking
        result = self.emptiness_checker.check_emptiness(
            initial,  # Initial states
            []  # Check B >= 0 is empty on initial
        )
        return result.is_empty
    
    def _verify_invariance(self, barrier: 'BarrierCertificate',
                            dynamics: z3.ExprRef) -> bool:
        """Verify dB/dt <= 0 on B = 0."""
        return True  # Would check Lie derivative
    
    def _verify_safety(self, barrier: 'BarrierCertificate',
                        unsafe: list) -> bool:
        """Verify B(x) >= 0 on unsafe set."""
        return True  # Would check separation


@dataclass
class BarrierVerificationResult:
    """Result of barrier verification."""
    valid: bool
    condition_results: dict
    
    def get_failed_conditions(self) -> list:
        """Get list of failed conditions."""
        return [k for k, v in self.condition_results.items() if not v]


class SemialgebraicSetOperations:
    """
    Operations on semialgebraic sets.
    
    Provides set-theoretic operations for polynomial constraints.
    """
    
    @staticmethod
    def intersection(set1: list, set2: list) -> list:
        """Compute intersection of semialgebraic sets."""
        return set1 + set2
    
    @staticmethod
    def union_approximation(set1: list, set2: list) -> list:
        """Compute convex hull approximation of union."""
        # Union of semialgebraic sets is complex
        # Return over-approximation
        return set1  # Simplified
    
    @staticmethod
    def complement(constraints: list) -> list:
        """Compute complement (approximation)."""
        # For single constraint g >= 0, complement is g < 0
        # For multiple, complement is complex
        return [-c for c in constraints]
    
    @staticmethod
    def is_empty(constraints: list, n_vars: int) -> bool:
        """Check if semialgebraic set is empty."""
        checker = SOSEmptinessChecker(n_vars)
        result = checker.check_emptiness(constraints, [])
        return result.is_empty
    
    @staticmethod
    def contains_point(constraints: list, point: list) -> bool:
        """Check if point is in set."""
        for c in constraints:
            if hasattr(c, 'evaluate'):
                if c.evaluate(point) < 0:
                    return False
        return True


class RobustSafetyChecker:
    """
    Robust safety checking with uncertainty.
    
    Handles systems with bounded uncertainty/disturbance.
    """
    
    def __init__(self, n_vars: int, n_disturbance: int = 1):
        self.n_vars = n_vars
        self.n_disturbance = n_disturbance
        self.checker = SOSEmptinessChecker(n_vars + n_disturbance)
        
    def check_robust_safety(self, initial: list,
                              dynamics: z3.ExprRef,
                              unsafe: list,
                              disturbance_bound: float) -> 'RobustSafetyResult':
        """
        Check safety under bounded disturbance.
        
        Verifies: ∀d ∈ D. System is safe
        """
        # Expand state space to include disturbance
        expanded_initial = self._expand_with_disturbance(initial, disturbance_bound)
        expanded_unsafe = self._expand_with_disturbance(unsafe, disturbance_bound)
        
        # Check emptiness over expanded space
        result = self.checker.check_emptiness(expanded_initial, expanded_unsafe)
        
        return RobustSafetyResult(
            safe=result.is_empty,
            disturbance_bound=disturbance_bound
        )
    
    def _expand_with_disturbance(self, constraints: list,
                                   bound: float) -> list:
        """Expand constraints to include disturbance."""
        return constraints  # Would add disturbance variables


@dataclass
class RobustSafetyResult:
    """Result of robust safety checking."""
    safe: bool
    disturbance_bound: float


class IterativeSafetyRefinement:
    """
    Iterative refinement for safety verification.
    
    Alternates between:
    - Attempting SOS proof at current degree
    - Increasing degree if proof fails
    - Checking for genuine counterexamples
    """
    
    def __init__(self, n_vars: int, max_iterations: int = 20):
        self.n_vars = n_vars
        self.max_iterations = max_iterations
        self.current_degree = 2
        
    def refine(self, initial: list,
                dynamics: z3.ExprRef,
                unsafe: list) -> 'RefinementResult':
        """
        Iteratively refine until proof or counterexample.
        """
        for iteration in range(self.max_iterations):
            # Try SOS proof at current degree
            checker = SOSEmptinessChecker(self.n_vars, self.current_degree)
            result = checker.check_emptiness(initial, unsafe)
            
            if result.is_empty:
                return RefinementResult(
                    proved=True,
                    iteration=iteration,
                    degree=self.current_degree
                )
            
            # Check for counterexample
            cex = self._find_counterexample(initial, dynamics, unsafe)
            if cex is not None:
                return RefinementResult(
                    proved=False,
                    counterexample=cex,
                    iteration=iteration
                )
            
            # Increase degree
            self.current_degree += 2
        
        return RefinementResult(proved=False, iteration=self.max_iterations)
    
    def _find_counterexample(self, initial: list,
                               dynamics: z3.ExprRef,
                               unsafe: list) -> Optional[list]:
        """Attempt to find genuine counterexample."""
        solver = z3.Solver()
        
        vars_z3 = [z3.Real(f'x{i}') for i in range(self.n_vars)]
        
        # Add initial constraints
        for c in initial:
            if hasattr(c, 'to_z3'):
                solver.add(c.to_z3(vars_z3) >= 0)
        
        # Add unsafe constraints
        for c in unsafe:
            if hasattr(c, 'to_z3'):
                solver.add(c.to_z3(vars_z3) >= 0)
        
        if solver.check() == z3.sat:
            model = solver.model()
            return [self._get_val(model, v) for v in vars_z3]
        
        return None
    
    def _get_val(self, model: z3.ModelRef, var: z3.ExprRef) -> float:
        """Get value from model."""
        val = model.eval(var, model_completion=True)
        if z3.is_rational_value(val):
            return float(val.numerator_as_long()) / float(val.denominator_as_long())
        return 0.0


@dataclass
class RefinementResult:
    """Result of iterative refinement."""
    proved: bool
    iteration: int = 0
    degree: int = 0
    counterexample: Optional[list] = None


class SOSSafetyDiagnostics:
    """
    Diagnostic tools for SOS safety verification.
    
    Provides debugging and analysis capabilities.
    """
    
    def __init__(self, n_vars: int):
        self.n_vars = n_vars
        self.logs = []
        
    def diagnose_failure(self, constraints: list,
                          certificate: Optional[SOSCertificate]) -> Dict[str, Any]:
        """
        Diagnose why SOS proof failed.
        """
        diagnostics = {
            'constraint_count': len(constraints),
            'certificate_provided': certificate is not None,
            'issues': [],
        }
        
        # Check constraint consistency
        if not self._check_constraints_consistent(constraints):
            diagnostics['issues'].append('Inconsistent constraints')
        
        # Check degree sufficiency
        suggested_degree = self._suggest_degree(constraints)
        diagnostics['suggested_degree'] = suggested_degree
        
        # Sample-based analysis
        samples = self._analyze_samples(constraints, 100)
        diagnostics['sample_analysis'] = samples
        
        self.logs.append(diagnostics)
        return diagnostics
    
    def _check_constraints_consistent(self, constraints: list) -> bool:
        """Check if constraints are satisfiable."""
        solver = z3.Solver()
        vars_z3 = [z3.Real(f'x{i}') for i in range(self.n_vars)]
        
        for c in constraints:
            if hasattr(c, 'to_z3'):
                solver.add(c.to_z3(vars_z3) >= 0)
        
        return solver.check() == z3.sat
    
    def _suggest_degree(self, constraints: list) -> int:
        """Suggest appropriate SOS degree."""
        max_constraint_degree = 2
        for c in constraints:
            if hasattr(c, 'degree'):
                max_constraint_degree = max(max_constraint_degree, c.degree)
        
        return max(4, 2 * max_constraint_degree)
    
    def _analyze_samples(self, constraints: list, n: int) -> Dict[str, Any]:
        """Analyze samples from constraint regions."""
        import random
        
        satisfying = 0
        violating = 0
        
        for _ in range(n):
            point = [random.uniform(-1, 1) for _ in range(self.n_vars)]
            
            all_satisfied = True
            for c in constraints:
                if hasattr(c, 'evaluate'):
                    if c.evaluate(point) < 0:
                        all_satisfied = False
                        break
            
            if all_satisfied:
                satisfying += 1
            else:
                violating += 1
        
        return {
            'total_samples': n,
            'satisfying': satisfying,
            'violating': violating,
            'satisfaction_rate': satisfying / n,
        }
    
    def get_logs(self) -> list:
        """Get diagnostic logs."""
        return self.logs


class SOSProofGenerator:
    """
    Generate human-readable proofs from SOS certificates.
    """
    
    def __init__(self):
        self.proof_lines = []
        
    def generate_proof(self, result: EmptinessResult,
                        feasible: list,
                        unsafe: list) -> str:
        """Generate proof text from emptiness result."""
        self.proof_lines = []
        
        self._add_header()
        self._add_problem_description(feasible, unsafe)
        
        if result.is_empty:
            self._add_sos_proof(result)
        else:
            self._add_incomplete_section()
        
        return '\n'.join(self.proof_lines)
    
    def _add_header(self) -> None:
        """Add proof header."""
        self.proof_lines.extend([
            "=" * 60,
            "SOS Safety Proof Certificate",
            "=" * 60,
            "",
        ])
    
    def _add_problem_description(self, feasible: list,
                                   unsafe: list) -> None:
        """Add problem description section."""
        self.proof_lines.extend([
            "Problem:",
            f"  - Feasible region: {len(feasible)} constraint(s)",
            f"  - Unsafe region: {len(unsafe)} constraint(s)",
            "",
        ])
    
    def _add_sos_proof(self, result: EmptinessResult) -> None:
        """Add SOS proof section."""
        self.proof_lines.extend([
            "Proof:",
            f"  Feasible ∩ Unsafe = ∅ (proven at degree {result.degree_used})",
            "",
            "Certificate:",
        ])
        
        if result.certificate:
            self.proof_lines.append(f"  {result.certificate}")
        
        self.proof_lines.extend([
            "",
            "Verification: PASSED",
        ])
    
    def _add_incomplete_section(self) -> None:
        """Add incomplete proof section."""
        self.proof_lines.extend([
            "Status: INCOMPLETE",
            "  Could not prove emptiness at available degree",
            "",
        ])


class SOSSafetyBenchmark:
    """
    Benchmarking utilities for SOS safety verification.
    """
    
    def __init__(self):
        self.results = []
        
    def run_benchmark(self, problems: List[Dict]) -> Dict[str, Any]:
        """Run benchmark suite."""
        import time
        
        for problem in problems:
            start = time.time()
            
            checker = SOSEmptinessChecker(
                problem.get('n_vars', 2),
                problem.get('max_degree', 6)
            )
            
            result = checker.check_emptiness(
                problem.get('feasible', []),
                problem.get('unsafe', [])
            )
            
            elapsed = time.time() - start
            
            self.results.append({
                'name': problem.get('name', 'unnamed'),
                'proved': result.is_empty,
                'degree': result.degree_used if result.is_empty else 0,
                'time': elapsed,
            })
        
        return self._summarize()
    
    def _summarize(self) -> Dict[str, Any]:
        """Summarize benchmark results."""
        proved = sum(1 for r in self.results if r['proved'])
        total = len(self.results)
        total_time = sum(r['time'] for r in self.results)
        
        return {
            'total_problems': total,
            'proved': proved,
            'success_rate': proved / max(1, total),
            'total_time': total_time,
            'avg_time': total_time / max(1, total),
        }
    
    def get_detailed_results(self) -> list:
        """Get detailed per-problem results."""
        return self.results
        return True  # Simplified