129 lines
3.7 KiB
Python
129 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
|
|
|
|
class LocalProblem(BaseModel):
|
|
robot_id: str
|
|
target_pos: float
|
|
initial_pos: float = 0.0
|
|
current_pos: float | None = None
|
|
dual_var: float = 0.0
|
|
max_step: float = 15.0
|
|
energy_budget: float = 100.0
|
|
weight: float = 1.0
|
|
version: int = 0
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
@field_validator("max_step", "energy_budget", "weight")
|
|
@classmethod
|
|
def _positive(cls, value: float) -> float:
|
|
if value <= 0:
|
|
raise ValueError("must be positive")
|
|
return value
|
|
|
|
@model_validator(mode="after")
|
|
def _default_current_pos(self) -> "LocalProblem":
|
|
if self.current_pos is None:
|
|
self.current_pos = self.initial_pos
|
|
return self
|
|
|
|
@property
|
|
def travel_distance(self) -> float:
|
|
current_pos = self.current_pos if self.current_pos is not None else self.initial_pos
|
|
return abs(current_pos - self.initial_pos)
|
|
|
|
def apply_updates(self, updates: dict[str, Any]) -> None:
|
|
for key, value in updates.items():
|
|
if hasattr(self, key):
|
|
setattr(self, key, value)
|
|
|
|
|
|
class SharedVariables(BaseModel):
|
|
version: int = 0
|
|
global_consensus: float = 0.0
|
|
delta_clock: int = 0
|
|
last_applied_sequences: dict[str, int] = Field(default_factory=dict)
|
|
|
|
|
|
class DualVariables(BaseModel):
|
|
values: dict[str, float] = Field(default_factory=dict)
|
|
|
|
|
|
class PlanDelta(BaseModel):
|
|
author: str
|
|
contract_id: str
|
|
sequence: int
|
|
base_version: int = 0
|
|
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
target_robot_id: str | None = None
|
|
updates: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
@field_validator("sequence")
|
|
@classmethod
|
|
def _non_negative(cls, value: int) -> int:
|
|
if value < 0:
|
|
raise ValueError("sequence must be non-negative")
|
|
return value
|
|
|
|
@field_validator("base_version")
|
|
@classmethod
|
|
def _base_version_non_negative(cls, value: int) -> int:
|
|
if value < 0:
|
|
raise ValueError("base_version must be non-negative")
|
|
return value
|
|
|
|
|
|
class SafetyPolicy(BaseModel):
|
|
max_travel_distance: float = 15.0
|
|
min_separation: float = 1.0
|
|
energy_budget: float = 100.0
|
|
bounded_staleness: int = 2
|
|
|
|
@field_validator("max_travel_distance", "min_separation", "energy_budget")
|
|
@classmethod
|
|
def _non_negative(cls, value: float) -> float:
|
|
if value < 0:
|
|
raise ValueError("must be non-negative")
|
|
return value
|
|
|
|
@field_validator("bounded_staleness")
|
|
@classmethod
|
|
def _bounded_staleness(cls, value: int) -> int:
|
|
if value < 0:
|
|
raise ValueError("must be non-negative")
|
|
return value
|
|
|
|
def verify(self, start_pos: float, new_pos: float) -> bool:
|
|
return abs(new_pos - start_pos) <= self.max_travel_distance
|
|
|
|
|
|
class AuditLogEntry(BaseModel):
|
|
iteration: int
|
|
shared_version: int
|
|
consensus: float
|
|
primal_residual: float
|
|
dual_residual: float
|
|
applied_deltas: list[str] = Field(default_factory=list)
|
|
notes: list[str] = Field(default_factory=list)
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
|
|
|
|
class ConvergenceCertificate(BaseModel):
|
|
converged: bool
|
|
iterations: int
|
|
epsilon: float
|
|
final_primal_residual: float
|
|
final_dual_residual: float
|
|
|
|
|
|
class SwarmSolution(BaseModel):
|
|
consensus: float
|
|
robots: list[LocalProblem]
|
|
shared: SharedVariables
|
|
audit_log: list[AuditLogEntry]
|
|
certificate: ConvergenceCertificate
|