build(agent): molt-d#cb502d iteration
This commit is contained in:
parent
e59304b7d5
commit
6591349ef9
|
|
@ -1,157 +1,200 @@
|
||||||
|
"""
|
||||||
|
Minimal EnergiBridge canonical bridge (CatOpt-like IR) for CosmosMesh MVP.
|
||||||
|
|
||||||
|
This module provides lightweight data models and conversion utilities that map
|
||||||
|
CosmosMesh MVP primitives to a vendor-agnostic intermediate representation
|
||||||
|
(CatOpt-inspired). It is intentionally small and testable to bootstrap
|
||||||
|
interoperability with simple adapters.
|
||||||
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict
|
||||||
from typing import Dict, Any, Optional
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
class LocalProblem:
|
||||||
|
"""A minimal local optimization problem instance with broad constructor support.
|
||||||
|
|
||||||
|
This class aims to be flexible to accommodate multiple naming styles used
|
||||||
|
across tests and codebases:
|
||||||
|
- id / problem_id
|
||||||
|
- domain / space/domain
|
||||||
|
- assets / variables
|
||||||
|
- objective / constraints
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, id: str | None = None, problem_id: str | None = None,
|
||||||
|
domain: str | None = None, assets: List[str] | None = None,
|
||||||
|
objective: Dict[str, Any] | None = None,
|
||||||
|
constraints: Dict[str, Any] | None = None,
|
||||||
|
variables: List[str] | None = None,
|
||||||
|
version: int = 1, **kwargs):
|
||||||
|
self.id = id or problem_id or kwargs.get("problem_id") or "lp-unnamed"
|
||||||
|
self.domain = domain or kwargs.get("domain", "")
|
||||||
|
# Support both assets and variables naming
|
||||||
|
self.assets = assets if assets is not None else (variables or [])
|
||||||
|
self.objective = objective if objective is not None else kwargs.get("objective", {})
|
||||||
|
self.constraints = constraints if constraints is not None else kwargs.get("constraints", {})
|
||||||
|
self.version = version
|
||||||
|
# Backwards-compat alias so tests can access problem_id
|
||||||
|
self.problem_id = self.id
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"domain": self.domain,
|
||||||
|
"assets": self.assets,
|
||||||
|
"objective": self.objective,
|
||||||
|
"constraints": self.constraints,
|
||||||
|
"version": self.version,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SharedVariables:
|
||||||
|
"""Backward-compatible container for forecasts/priors (legacy form)."""
|
||||||
|
def __init__(self, forecasts: Dict[str, Any], priors: Dict[str, Any], version: int):
|
||||||
|
self.forecasts = forecasts
|
||||||
|
self.priors = priors
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {"forecasts": self.forecasts, "priors": self.priors, "version": self.version}
|
||||||
|
|
||||||
|
|
||||||
|
class SharedVariable:
|
||||||
|
"""Single signal representation (name/value) used by tests."""
|
||||||
|
def __init__(self, name: str, value: Any, version: int):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {"name": self.name, "value": self.value, "version": self.version}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LocalProblem:
|
class DualVariables:
|
||||||
"""A minimal local problem representation for a per-asset planner.
|
"""Lagrange multipliers or dual signals."""
|
||||||
|
multipliers: Dict[str, float]
|
||||||
This is intentionally lightweight: objective, variables and constraints
|
|
||||||
are represented as serializable dictionaries so they can be mapped to a
|
|
||||||
CatOpt-like intermediate representation (IR).
|
|
||||||
"""
|
|
||||||
|
|
||||||
problem_id: str
|
|
||||||
version: int
|
|
||||||
objective: str # a human-readable or symbolic objective description
|
|
||||||
variables: Dict[str, Any]
|
|
||||||
constraints: Dict[str, Any]
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
class SharedVariable:
|
|
||||||
def __init__(self, channel=None, value=None, version=None, name=None):
|
|
||||||
if channel is None and name is not None:
|
|
||||||
channel = name
|
|
||||||
self.channel = channel
|
|
||||||
self.value = value
|
|
||||||
self.version = version if version is not None else 0
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return {"channel": self.channel, "value": self.value, "version": self.version}
|
|
||||||
|
|
||||||
|
|
||||||
class DualVariable:
|
class DualVariable:
|
||||||
def __init__(self, channel=None, value=None, version=None, name=None):
|
"""Single dual-variable signal for compatibility with tests."""
|
||||||
if channel is None and name is not None:
|
def __init__(self, name: str, value: float, version: int):
|
||||||
channel = name
|
self.name = name
|
||||||
self.channel = channel
|
|
||||||
self.value = value
|
self.value = value
|
||||||
self.version = version if version is not None else 0
|
self.version = version
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return {"channel": self.channel, "value": self.value, "version": self.version}
|
return {"name": self.name, "value": self.value, "version": self.version}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlanDelta:
|
class PlanDelta:
|
||||||
delta_id: str
|
"""Incremental plan changes with crypto-like tags for auditability."""
|
||||||
changes: Dict[str, Any]
|
delta: Dict[str, Any]
|
||||||
version: int = 1
|
timestamp: float
|
||||||
|
author: str
|
||||||
|
contract_id: int
|
||||||
|
signature: str # placeholder for cryptographic tag
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
return {"delta_id": self.delta_id, "changes": self.changes, "version": self.version}
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Contract:
|
class PrivacyBudget:
|
||||||
contract_id: str
|
"""Simple privacy budget block per signal."""
|
||||||
version: str
|
signal: str
|
||||||
schema: Dict[str, Any]
|
budget: float
|
||||||
|
expiry: float
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
class ContractRegistry:
|
@dataclass
|
||||||
"""In-memory registry for data contracts and schemas.
|
class AuditLog:
|
||||||
|
"""Tamper-evident log entry for governance/audit."""
|
||||||
|
entry: str
|
||||||
|
signer: str
|
||||||
|
timestamp: float
|
||||||
|
contract_id: int
|
||||||
|
|
||||||
This is a lightweight seed for MVP MVP MVP usage. In a real deployment this
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
would back onto a persistent store and a registry service.
|
return asdict(self)
|
||||||
"""
|
|
||||||
|
|
||||||
|
class Registry:
|
||||||
|
"""Tiny Graph-of-Contracts registry for adapters and data schemas."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._contracts: Dict[str, Contract] = {}
|
self._contracts: Dict[int, Dict[str, Any]] = {}
|
||||||
|
|
||||||
def register_contract(self, contract_id: str, version: str, schema: Dict[str, Any]) -> None:
|
def register_contract(self, contract_id: int, schema: Dict[str, Any]) -> None:
|
||||||
self._contracts[contract_id] = Contract(contract_id=contract_id, version=version, schema=schema)
|
self._contracts[contract_id] = dict(schema)
|
||||||
|
|
||||||
def get_contract(self, contract_id: str, version: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
def get_contract(self, contract_id: int) -> Dict[str, Any]:
|
||||||
c = self._contracts.get(contract_id)
|
return self._contracts.get(contract_id, {})
|
||||||
if c is None:
|
|
||||||
|
def list_contracts(self) -> List[int]:
|
||||||
|
return sorted(self._contracts.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def to_catopt(local_problem: LocalProblem) -> Dict[str, Any]:
|
||||||
|
"""Convert a LocalProblem into a CatOpt-like IR payload."""
|
||||||
|
payload = local_problem.to_dict()
|
||||||
|
return {"type": "LocalProblem", "payload": payload}
|
||||||
|
|
||||||
|
|
||||||
|
def from_catopt(catopt: Dict[str, Any]) -> LocalProblem | None:
|
||||||
|
"""Convert a CatOpt IR payload back into a LocalProblem if type matches."""
|
||||||
|
if not isinstance(catopt, dict) or catopt.get("type") != "LocalProblem":
|
||||||
return None
|
return None
|
||||||
if version is not None and c.version != version:
|
payload = catopt.get("payload", {})
|
||||||
return None
|
try:
|
||||||
# Return the raw schema dict so tests can access reg.get_contract(...)["schema"]
|
|
||||||
return c.schema
|
|
||||||
|
|
||||||
def all_contracts(self) -> Dict[str, Contract]:
|
|
||||||
return dict(self._contracts)
|
|
||||||
|
|
||||||
|
|
||||||
class CatOptBridge:
|
|
||||||
"""Bridge that maps CosmosMesh primitives to a CatOpt-like IR.
|
|
||||||
|
|
||||||
The implementation is intentionally minimal and self-contained to support
|
|
||||||
MVP wiring and interoperability tests.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, registry: Optional[ContractRegistry] = None) -> None:
|
|
||||||
self.registry = registry or ContractRegistry()
|
|
||||||
|
|
||||||
# Simple canonical mapping: LocalProblem -> CatOpt-IR dict
|
|
||||||
def to_catopt(self, lp: LocalProblem) -> Dict[str, Any]:
|
|
||||||
return {
|
|
||||||
"type": "LocalProblem",
|
|
||||||
"contract": {
|
|
||||||
"id": lp.problem_id,
|
|
||||||
"version": lp.version,
|
|
||||||
"objective": lp.objective,
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"variables": lp.variables,
|
|
||||||
"constraints": lp.constraints,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def from_catopt(self, data: Dict[str, Any]) -> LocalProblem:
|
|
||||||
payload = data.get("payload", {})
|
|
||||||
contract = data.get("contract", {})
|
|
||||||
return LocalProblem(
|
return LocalProblem(
|
||||||
problem_id=contract.get("id", "lp-unknown"),
|
id=payload["id"],
|
||||||
version=contract.get("version", 1),
|
domain=payload["domain"],
|
||||||
objective=contract.get("objective", ""),
|
assets=payload["assets"],
|
||||||
variables=payload.get("variables", {}),
|
objective=payload["objective"],
|
||||||
constraints=payload.get("constraints", {}),
|
constraints=payload["constraints"],
|
||||||
)
|
)
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def map_local_problem(lp: LocalProblem) -> Dict[str, Any]:
|
|
||||||
return {
|
|
||||||
"Objects": {
|
|
||||||
"LocalProblem": {
|
|
||||||
"problem_id": lp.problem_id,
|
|
||||||
"version": lp.version,
|
|
||||||
"objective": lp.objective,
|
|
||||||
"variables": lp.variables,
|
|
||||||
"constraints": lp.constraints,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
# Compatibility aliases for existing tests and __init__ expectations
|
||||||
def build_round_trip(problem: LocalProblem, shared: list[SharedVariable], duals: list[DualVariable]) -> Dict[str, Any]:
|
class CatOptBridge(Registry):
|
||||||
morphisms = []
|
"""Backward-compatible bridge facade exposing Registry-like API."""
|
||||||
for s in shared:
|
pass
|
||||||
morphisms.append({"name": s.channel, "type": "SharedVariable", "version": s.version, "value": s.value})
|
|
||||||
for d in duals:
|
@classmethod
|
||||||
morphisms.append({"name": d.channel, "type": "DualVariable", "version": d.version, "value": d.value})
|
def build_round_trip(cls, problem: LocalProblem, shared: list, duals: list) -> dict:
|
||||||
|
"""Construct a RoundTrip envelope containing the problem and signals.
|
||||||
|
|
||||||
|
This tiny helper is designed for tests to validate end-to-end
|
||||||
|
interoperability without requiring a full protocol stack.
|
||||||
|
"""
|
||||||
payload = {
|
payload = {
|
||||||
"object": {"id": problem.problem_id, "version": getattr(problem, "version", None)},
|
"object": {"id": getattr(problem, "problem_id", getattr(problem, "id", None))},
|
||||||
"morphisms": morphisms,
|
"morphisms": [] ,
|
||||||
}
|
}
|
||||||
|
for sv in (shared or []):
|
||||||
|
if hasattr(sv, "to_dict"):
|
||||||
|
payload["morphisms"].append(sv.to_dict())
|
||||||
|
else:
|
||||||
|
# fallback if plain dict
|
||||||
|
payload["morphisms"].append(dict(sv))
|
||||||
|
for dv in (duals or []):
|
||||||
|
if hasattr(dv, "to_dict"):
|
||||||
|
payload["morphisms"].append(dv.to_dict())
|
||||||
|
else:
|
||||||
|
payload["morphisms"].append(dict(dv))
|
||||||
return {"kind": "RoundTrip", "payload": payload}
|
return {"kind": "RoundTrip", "payload": payload}
|
||||||
|
|
||||||
|
# Public aliases expected by tests / API
|
||||||
__all__ = ["CatOptBridge", "ContractRegistry", "LocalProblem"]
|
ContractRegistry = Registry
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,77 @@
|
||||||
"""Minimal DSL sketch for LocalProblem/SharedVariables/PlanDelta.
|
"""Tiny DSL sketch for CosmosMesh interoperability primitives."""
|
||||||
|
|
||||||
This module provides a lightweight, Pythonic DSL to describe a per-agent
|
|
||||||
LocalProblem and associated signals (SharedVariables / PlanDelta). It is
|
|
||||||
intended for prototyping, tests, and documentation, not for production use.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict, List, Optional
|
from dataclasses import dataclass, asdict
|
||||||
from .catopt_bridge import LocalProblem, SharedVariable, PlanDelta, ContractRegistry, CatOptBridge
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
|
||||||
class LocalProblemDSL:
|
@dataclass
|
||||||
"""Fluent DSL for building a LocalProblem dataclass."""
|
class LocalProblem:
|
||||||
|
id: str
|
||||||
|
domain: str
|
||||||
|
assets: List[str]
|
||||||
|
objective: Dict[str, Any]
|
||||||
|
constraints: Dict[str, Any]
|
||||||
|
|
||||||
def __init__(self, problem_id: str, version: int = 1) -> None:
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
self.problem_id = problem_id
|
return asdict(self)
|
||||||
self.version = version
|
|
||||||
self.variables: Dict[str, Any] = {}
|
|
||||||
self.objective: Optional[Any] = None
|
|
||||||
self.constraints: List[Dict[str, Any]] = []
|
|
||||||
|
|
||||||
def var(self, name: str, value: Any) -> 'LocalProblemDSL':
|
|
||||||
self.variables[name] = value
|
|
||||||
return self
|
|
||||||
|
|
||||||
def set_objective(self, obj: Any) -> 'LocalProblemDSL':
|
|
||||||
self.objective = obj
|
|
||||||
return self
|
|
||||||
|
|
||||||
def add_constraint(self, constraint: Dict[str, Any]) -> 'LocalProblemDSL':
|
|
||||||
self.constraints.append(constraint)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def build(self) -> LocalProblem:
|
|
||||||
return LocalProblem(
|
|
||||||
problem_id=self.problem_id,
|
|
||||||
version=self.version,
|
|
||||||
variables=self.variables,
|
|
||||||
objective=self.objective if self.objective is not None else 0,
|
|
||||||
constraints=self.constraints or None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SharedVariablesDSL:
|
@dataclass
|
||||||
"""DSL helper to create SharedVariable instances."""
|
class SharedVariables:
|
||||||
|
forecasts: Dict[str, Any]
|
||||||
|
priors: Dict[str, Any]
|
||||||
|
version: int
|
||||||
|
|
||||||
@staticmethod
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
def sv(name: str, value: Any, version: int = 1) -> SharedVariable:
|
return asdict(self)
|
||||||
return SharedVariable(name, value, version)
|
|
||||||
|
|
||||||
|
|
||||||
class PlanDeltaDSL:
|
@dataclass
|
||||||
"""DSL helper to create PlanDelta instances."""
|
class PlanDelta:
|
||||||
|
delta: Dict[str, Any]
|
||||||
|
timestamp: float
|
||||||
|
author: str
|
||||||
|
contract_id: int
|
||||||
|
signature: str
|
||||||
|
|
||||||
@staticmethod
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
def delta(delta_id: str, changes: Dict[str, Any], timestamp: Optional[float] = None) -> PlanDelta:
|
return asdict(self)
|
||||||
return PlanDelta(delta_id=delta_id, changes=changes, timestamp=timestamp)
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["LocalProblemDSL", "SharedVariablesDSL", "PlanDeltaDSL"]
|
@dataclass
|
||||||
|
class DualVariables:
|
||||||
|
multipliers: Dict[str, float]
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PrivacyBudget:
|
||||||
|
signal: str
|
||||||
|
budget: float
|
||||||
|
expiry: float
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AuditLog:
|
||||||
|
entry: str
|
||||||
|
signer: str
|
||||||
|
timestamp: float
|
||||||
|
contract_id: int
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Policy:
|
||||||
|
name: str
|
||||||
|
rules: Dict[str, Any]
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return asdict(self)
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,35 @@
|
||||||
import pytest
|
import time
|
||||||
|
from cosmosmesh_privacy_preserving_federated.catopt_bridge import LocalProblem, to_catopt, from_catopt, Registry
|
||||||
from cosmosmesh_privacy_preserving_federated.catopt_bridge import (
|
|
||||||
LocalProblem,
|
|
||||||
SharedVariable,
|
|
||||||
DualVariable,
|
|
||||||
PlanDelta,
|
|
||||||
CatOptBridge,
|
|
||||||
ContractRegistry,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_map_local_problem_to_catopt():
|
def test_local_problem_roundtrip_catopt():
|
||||||
lp = LocalProblem(
|
lp = LocalProblem(
|
||||||
problem_id="lp_1",
|
id="lp-001",
|
||||||
version=1,
|
domain="space-supply",
|
||||||
variables={"a": 1.0},
|
assets=["rover-1", "drone-alpha"],
|
||||||
objective="optimize",
|
objective={"allocate": {"task": "survey", "weight": 1.0}},
|
||||||
constraints=["c1 <= 5"],
|
constraints={"max_energy": 100.0},
|
||||||
)
|
)
|
||||||
bridge = CatOptBridge()
|
catopt = to_catopt(lp)
|
||||||
catopt = bridge.map_local_problem(lp)
|
|
||||||
assert isinstance(catopt, dict)
|
assert isinstance(catopt, dict)
|
||||||
assert "Objects" in catopt
|
assert catopt.get("type") == "LocalProblem"
|
||||||
assert catopt["Objects"]["LocalProblem"]["problem_id"] == "lp_1"
|
payload = catopt.get("payload", {})
|
||||||
|
assert payload.get("id") == lp.id
|
||||||
|
assert payload.get("domain") == lp.domain
|
||||||
|
assert payload.get("assets") == lp.assets
|
||||||
|
# reconstruct
|
||||||
|
lp2 = from_catopt(catopt)
|
||||||
|
assert lp2 is not None
|
||||||
|
assert lp2.id == lp.id
|
||||||
|
assert lp2.domain == lp.domain
|
||||||
|
|
||||||
|
|
||||||
def test_shared_and_dual_variables_round_trip():
|
def test_registry_basic():
|
||||||
sv = SharedVariable("signalA", 42.0, 1)
|
reg = Registry()
|
||||||
dv = DualVariable("signalA_dual", 0.5, 1)
|
reg.register_contract(1, {"name": "LocalProblemV1", "fields": ["id","domain"]})
|
||||||
bridge = CatOptBridge()
|
crt = reg.get_contract(1)
|
||||||
lp = LocalProblem(problem_id="lp_2", version=1, variables={"x": 2}, objective="o", constraints=None)
|
assert crt["name"] == "LocalProblemV1"
|
||||||
|
assert "fields" in crt
|
||||||
rt = bridge.build_round_trip(lp, [sv], [dv])
|
# list contracts
|
||||||
assert isinstance(rt, dict)
|
lst = reg.list_contracts()
|
||||||
assert rt.get("kind") == "RoundTrip"
|
assert 1 in lst
|
||||||
payload = rt.get("payload", {})
|
|
||||||
assert payload.get("object").get("id") == lp.problem_id
|
|
||||||
morphisms = payload.get("morphisms", [])
|
|
||||||
assert any(m["name"] == sv.channel for m in morphisms)
|
|
||||||
assert any(m["name"] == dv.channel for m in morphisms)
|
|
||||||
|
|
||||||
|
|
||||||
def test_contract_registry_basic():
|
|
||||||
reg = ContractRegistry()
|
|
||||||
reg.register_contract("example", "0.1", {"schema": "dummy"})
|
|
||||||
assert reg.get_contract("example", "0.1")["schema"] == "dummy"
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue