"""Distance certificates for Q2; does not change Q1's angular polygon model.""" from math import cos, sin, radians, isfinite from .localization import Measurement def first_bearing_candidate_disk(measurement: Measurement, max_range_m: float = 1500, guaranteed_range_m: float = 1000) -> dict: """Enclose the entire truncated first-bearing sector, then erode its disk. For half-angle <=45 degrees the sector MEC has center distance and radius a=R/(2*cos(delta)). Intersecting with Omega only shrinks the source set. A missing sufficient disk is NOT proof that Q is empty. The certificate guarantees distance only: directional transmitters may still be invisible. """ if any(isinstance(v, bool) or not isinstance(v, (int, float)) or not isfinite(v) or v <= 0 for v in (max_range_m, guaranteed_range_m)): raise ValueError("接收半径必须为有限正数") if measurement.half_angle_deg > 45: raise ValueError("此窄角域证书仅支持半角不超过45度;题设为1度") delta, theta = radians(measurement.half_angle_deg), radians(measurement.bearing_deg % 360) a = max_range_m/(2*cos(delta)) center = [measurement.x+a*cos(theta), measurement.y+a*sin(theta)] available = guaranteed_range_m-a return {"source_model": "first_bearing_sector_with_range_upper_bound", "sector_enclosing_circle": {"center": center, "radius_m": a}, "sufficient_candidate_disk": {"center": center, "radius_m": available} if available >= 0 else None, "candidate_nonempty_certified": available >= 0, "distance_only": True, "max_range_m": max_range_m, "guaranteed_range_m": guaranteed_range_m}