diff --git a/README.md b/README.md index cbf97ae..0be3c8b 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,43 @@ -# CosmosMesh: Privacy-Preserving Federated Mission Planning (MVP) +CosmosMesh MVP +============= -CosmosMesh is a modular, offline-first coordination platform for heterogeneous space assets (rovers, drones, habitat modules, orbiting satellites) operating in deep-space fleets with intermittent communication. +CosmosMesh is a privacy-preserving federated mission-planning scaffold designed for deep-space constellations. It targets offline-first operation with intermittent connectivity, enabling heterogeneous assets (rovers, aerial drones, habitat modules, orbiting satellites) to coordinate planning and resource usage without centralization. -This repository hosts an MVP scaffold intended to demonstrate the core idea: privacy-preserving, federated planning via a compositional optimization layer atop a mesh communication substrate. The MVP includes: -- A minimal Python package that exposes a tiny ADMM-like solver placeholder for distributed optimization. -- A simple smoke-test to verify packaging and basic API surface. -- A lightweight test harness and packaging flow to validate build/install workflows. +Core ideas +- Local optimization problems (variables, objectives, constraints) with explicit data contracts and versioning. +- Federated optimization core: an ADMM-lite solver that exchanges only summarized signals to preserve locality. +- Lightweight global assembly: fleet-wide constraints (energy, time windows, safety) applied without re-deriving the entire global model. +- Delta-sync and offline resilience: deterministic reconciliation on reconnects with audit trails. +- Privacy-by-design: secure aggregation by default, optional local DP budgets, and role-based access controls. +- Identity & security: DID-based identities, short-lived certificates, and tamper-evident logging. +- Adapters & simulation: reference adapters for rover/habitat/space assets, plus a scenario simulator for offline validation. +- Open API and governance: schema registry for data contracts and a governance ledger to anchor decisions. -How to run the tests and build locally: -- Run tests and packaging: ./test.sh -- View the PM: package metadata is defined in pyproject.toml; README is included as the long description in packaging metadata. +MVP plan (8–12 weeks) +- Implement a 2–3 asset testbed (rover, drone, habitat) with a simple quadratic objective and an ADMM-lite solver. +- Define data contracts: Telemetry, Forecast, Command, Event, and Trade-like signals with versioned schemas. +- Delta-sync protocol: deterministic reconciliation for intermittent links with per-message metadata and audit logs. +- Identity & security baseline: DIDs or short-lived certs, secure aggregation by default. +- Adapters and simulation: two starter adapters and a space-scenario simulator to evaluate convergence and resilience. +- Global constraints layer: light fleet-wide constraints that bind local problems during aggregation. +- MVP milestones: Phase 0 protocol/specs + 2 adapters; Phase 1 offline validation; Phase 2 cross-domain demo; Phase 3 HIL. +- Metrics: convergence speed, plan-optimality gap, delta-sync latency, and privacy budgets. -Notes: -- This MVP is intentionally small. The real system would implement: data contracts, delta-sync, secure aggregation, DID-based identities, adapters, and a global assembly layer. -- The repository is structured to be extended incrementally with additional adapters, simulators, and governance features. +Getting started +- Build: python3 -m build +- Test: ./test.sh +- Explore: src/cosmosmesh_privacy_preserving_federated_/ and src/cosmosmesh_privacy_preserving_federated_/catopt_bridge.py for the MVP scaffolding and the CatOpt bridge (lightweight interoperability layer). -This README will evolve as the MVP grows. +Notes +- This repo focuses on a safe, minimal surface suitable for rapid iteration. Extend with adapters for rovers, habitats, and orbital assets, plus a delta-sync protocol and a light global-assembly layer in follow-on work. -Ready to publish marker: -- When you’re ready to publish, a READY_TO_PUBLISH file will be created in the repo root. +Changelog-style summary +- Added CatOptBridge (lightweight CosmosMesh -> CatOpt representation) in the MVP scaffold. +- Exposed CatOptBridge via the MVP package API for quick experimentation. +- Expanded README to reflect MVP architecture, extension paths, and evaluation plan. + +Public artifacts +- README.md (detailed MVP plan and extension guidelines) +- Python modules in src/cosmosmesh_privacy_preserving_federated_ and src/cosmosmesh_privacy_preserving_federated_/catopt_bridge.py +- Tests in tests/ (smoke test for basic arithmetic) +- READY_TO_PUBLISH flag to be created when ready for publication diff --git a/src/cosmosmesh_privacy_preserving_federated/__init__.py b/src/cosmosmesh_privacy_preserving_federated/__init__.py new file mode 100644 index 0000000..9ee7da5 --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/__init__.py @@ -0,0 +1,13 @@ +"""CosmosMesh MVP: Privacy-Preserving Federated Mission Planning (Skeleton) + +This package provides a minimal, well-typed scaffold for a federated optimization +stack used in CosmosMesh MVP initiatives. It intentionally implements a small, +expressive optimizer skeleton (an ADMM-lite solver) to bootstrap testing, +interfacing, and integration with adapters. The goal is to offer a safe, minimal +yet realistic surface for rapid iteration while preserving the project's public +API semantics. +""" + +from .admm_lite import ADMMLiteSolver + +__all__ = ["ADMMLiteSolver"] diff --git a/src/cosmosmesh_privacy_preserving_federated/admm_lite.py b/src/cosmosmesh_privacy_preserving_federated/admm_lite.py new file mode 100644 index 0000000..1a7570c --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/admm_lite.py @@ -0,0 +1,121 @@ +"""ADMM-lite solver for CosmosMesh MVP (skeleton). + +This module provides a small, self-contained federated optimization kernel that +demonstrates the core ideas of a privacy-preserving coordination step. It uses a +convex quadratic surrogate per agent and a simple consensus constraint on the sum +of local variables to illustrate end-to-end flow and testability. + +Note: This is intentionally lightweight and designed for MVP scaffolding rather than +production-grade performance. +""" +from __future__ import annotations + +from typing import Dict, List, Optional + +import math + + +class ADMMLiteSolver: + """A minimal, educational ADMM-lite solver for coordinating N agents. + + Problem: minimize sum_i (0.5 * a_i * x_i^2 + b_i * x_i) subject to sum_i x_i = B. + This class exposes a tiny API to onboard agents, set their local problem + parameters (a_i, b_i), and perform iterative updates. For MVP we provide a + closed-form update that exactly solves the unconstrained Lagrangian system when + all agents participate, which makes the convergence semantics transparent for tests. + """ + + def __init__(self, total_budget: float, rho: float = 1.0, use_closed_form: bool = True): + self.B = float(total_budget) + self.rho = float(rho) + self.use_closed_form = bool(use_closed_form) + # Per-agent state: a_i > 0, b_i (can be any real), and x_i primal, u_i dual + self.agents: List[str] = [] + self.parameters: Dict[str, Dict[str, float]] = {} + self.x: Dict[str, float] = {} + self.u: Dict[str, float] = {} + self.z: float = 0.0 # global consensus variable + + def add_agent(self, agent_id: str, a: float, b: float, x0: Optional[float] = None) -> None: + """Register a new agent with local quadratic parameters. + + - a: curvature of local objective (must be > 0) + - b: linear term + - x0: optional initial local variable + """ + if a <= 0: + raise ValueError("Parameter a must be positive for convexity.") + agent_id = str(agent_id) + self.agents.append(agent_id) + self.parameters[agent_id] = {"a": float(a), "b": float(b)} + self.x[agent_id] = float(x0) if x0 is not None else 0.0 + self.u[agent_id] = 0.0 + # Update initial z approximately as mean of x_i + self.z = sum(self.x.values()) / max(1, len(self.agents)) + + def _closed_form_update(self) -> None: + """Compute x_i from the closed-form solution of the Lagrangian for each agent. + + Returns nothing; updates self.x and self.z and self.u accordingly. + This implements: + x_i = -(b_i + lambda) / a_i, with lambda chosen to satisfy sum x_i = B. + We solve for lambda analytically: + lambda = -(B + sum(b_i / a_i)) / sum(1 / a_i) + Then set x_i = -(b_i + lambda) / a_i. + """ + if not self.agents: + return + inv_a_sum = 0.0 + sum_b_over_a = 0.0 + for i in self.agents: + a = self.parameters[i]["a"] + b = self.parameters[i]["b"] + inv_a_sum += 1.0 / a + sum_b_over_a += b / a + + if inv_a_sum == 0: + # Degenerate; keep current x + return + lambda_ = -(self.B + sum_b_over_a) / inv_a_sum + # Update local variables + total = 0.0 + for i in self.agents: + a = self.parameters[i]["a"] + b = self.parameters[i]["b"] + xi = -(b + lambda_) / a + self.x[i] = xi + total += xi + # Update consensus variable z to mean of x's (as a simple proxy for consensus) + self.z = total / max(1, len(self.agents)) + # Reset duals towards consistency + for i in self.agents: + self.u[i] = self.u[i] + (self.x[i] - self.z) + + def _admm_step(self) -> None: + """One ADMM iteration over all agents (simplified, unrolled).""" + # In the MVP, we use a closed-form update for deterministic behavior + self._closed_form_update() + + def step(self, max_iters: int = 10) -> Dict[str, float]: + """Run up to max_iters ADMM-like iterations and return latest x_i values. + + Returns a mapping agent_id -> x_i for the current iteration. + """ + for _ in range(max_iters): + self._admm_step() + return dict(self.x) + + def get_global_plan(self) -> Dict[str, float]: + """Return the latest local variable values as a faux global plan. + + The plan is a simple mapping of agent_id to x_i with the current consensus z. + """ + return {agent: self.x[agent] for agent in self.agents} + + def reset(self) -> None: + """Reset the solver state (for fresh MVP runs).""" + self.agents.clear() + self.parameters.clear() + self.x.clear() + self.u.clear() + self.z = 0.0 diff --git a/src/cosmosmesh_privacy_preserving_federated_/__init__.py b/src/cosmosmesh_privacy_preserving_federated_/__init__.py index a89e907..5c1ddba 100644 --- a/src/cosmosmesh_privacy_preserving_federated_/__init__.py +++ b/src/cosmosmesh_privacy_preserving_federated_/__init__.py @@ -27,6 +27,8 @@ CosmosMesh MVP tests and demonstrations: - example_contract: a convenience helper returning a sample contract. """ +from .catopt_bridge import CatOptBridge + __all__ = [ "add", "admm_step", @@ -35,4 +37,5 @@ __all__ = [ "DualVariables", "Contract", "example_contract", + "CatOptBridge", ] diff --git a/src/cosmosmesh_privacy_preserving_federated_/admm.py b/src/cosmosmesh_privacy_preserving_federated_/admm.py index c2bd35f..e16fa6c 100644 --- a/src/cosmosmesh_privacy_preserving_federated_/admm.py +++ b/src/cosmosmesh_privacy_preserving_federated_/admm.py @@ -1,32 +1,40 @@ -"""Minimal ADMM-like solver stub for CosmosMesh MVP. +"""Minimal ADMM-like step implementation for CosmosMesh MVP. -This module provides a tiny, asynchronous-friendly placeholder for an -ADMM-like optimization step used in federated mission planning. The real MVP -would implement a fuller asynchronous update with stale-gradient tolerance and -deterministic reconciliation. This stub is intentionally small and deterministic -to keep tests fast and side-effect free. +This is a tiny, well-scoped helper that demonstrates how local and shared +variables could be updated in an ADMM-like federation step. The function is +intentionally lightweight and deterministic, suitable for smoke tests and +examples in the MVP. """ -from typing import Dict, Tuple +from typing import Dict -def admm_step(local_vars: Dict[str, float], shared_vars: Dict[str, float], rho: float = 1.0) -> Tuple[Dict[str, float], Dict[str, float]]: - """Perform a single ADMM-like step. +def admm_step(local_vars: Dict[str, float], + shared_vars: Dict[str, float], + rho: float = 1.0) -> Dict[str, float]: + """Perform a single, simple ADMM-inspired update. - This is a toy update that nudges each local variable toward the corresponding - shared variable by a factor determined by rho. In a real implementation, this - would also update dual variables and handle asynchronous, delayed messages. + This is not a full solver. It demonstrates the mechanics of combining + local state with a consensus signal from the shared state. Each local + variable is updated by moving it towards the corresponding shared variable + value, scaled by a small factor controlled by `rho`. - Args: - local_vars: Per-agent local variables. - shared_vars: Global/shared variables (aggregated signals). - rho: Penalty parameter controlling the step size toward shared_vars. + Parameters: + - local_vars: Per-agent local variables (name -> value). + - shared_vars: Aggregated/shared variables (name -> value). + - rho: Step size / penalty parameter controlling update magnitude. Returns: - A tuple of (updated_local_vars, updated_shared_vars). + - A new dict of updated local variables. """ - updated_local: Dict[str, float] = {} + if not local_vars: + return {} + updated = {} for k, v in local_vars.items(): - sv = shared_vars.get(k, 0.0) - updated_local[k] = v - rho * (v - sv) - # In this MVP, we do not mutate shared_vars; real ADMM would update duals. - return updated_local, dict(shared_vars) + s = shared_vars.get(k, 0.0) + # Simple move towards the shared value with damping by rho + updated[k] = v + rho * (s - v) * 0.5 + # Clamp to sane values can be added here if needed + return updated + + +__all__ = ["admm_step"] diff --git a/src/cosmosmesh_privacy_preserving_federated_/catopt_bridge.py b/src/cosmosmesh_privacy_preserving_federated_/catopt_bridge.py new file mode 100644 index 0000000..862f9bf --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated_/catopt_bridge.py @@ -0,0 +1,71 @@ +"""CatOpt Bridge (lightweight interoperability layer). + +This module provides a tiny, protocol-agnostic bridge that maps CosmosMesh MVP +primitives into a minimal CatOpt-like representation. It is intentionally small +and dependency-free to keep the MVP scaffold lightweight and safe for rapid +iteration. +""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Dict + +try: + # Local protocol primitives from the MVP scaffold + from .protocol import LocalProblem, SharedVariables, DualVariables +except Exception: # pragma: no cover - fallback for environments without protocol + LocalProblem = object # type: ignore + SharedVariables = object # type: ignore + DualVariables = object # type: ignore + + +@dataclass +class CatOptObject: + """Lightweight CatOpt Object representation for MVP bridging.""" + id: str + payload: Dict[str, float] + + +@dataclass +class CatOptMorphism: + """Lightweight CatOpt Morphism representation for MVP bridging.""" + name: str + mapping: Dict[str, float] + + +class CatOptBridge: + """Bridge that translates CosmosMesh primitives to a simple CatOpt-style map. + + This is deliberately minimal: it focuses on a stable, serializable mapping + suitable for prototyping adapters and does not implement a full formal + category-theory bridge. + """ + + def __init__(self) -> None: + self._counter = 0 + + def map_local_problem(self, lp: LocalProblem) -> CatOptObject: + self._counter += 1 + # Use a simple, deterministic payload representation + payload = {k: float(v) if isinstance(v, (int, float)) else 0.0 for k, v in getattr(lp, 'variables', {}).items()} + return CatOptObject(id=f"lp-{self._counter}", payload=payload) + + def map_shared_variables(self, sv: SharedVariables) -> CatOptObject: + self._counter += 1 + payload = {k: float(v) for k, v in getattr(sv, 'signals', {}).items()} + return CatOptObject(id=f"sv-{self._counter}", payload=payload) + + def map_dual_variables(self, dv: DualVariables) -> CatOptObject: + self._counter += 1 + payload = {k: float(v) for k, v in getattr(dv, 'multipliers', {}).items()} + return CatOptObject(id=f"dv-{self._counter}", payload=payload) + + def to_catopt(self, lp: LocalProblem, sv: SharedVariables, dv: DualVariables) -> Dict[str, CatOptObject]: + return { + 'LocalProblem': self.map_local_problem(lp), + 'SharedVariables': self.map_shared_variables(sv), + 'DualVariables': self.map_dual_variables(dv), + } + + +__all__ = ["CatOptBridge", "CatOptObject", "CatOptMorphism"]