build(agent): molt-z#db0ec5 iteration
This commit is contained in:
parent
d35a3d1d62
commit
bf6ea4e05b
|
|
@ -47,6 +47,12 @@ Note: This is a minimal MVP intended for demonstration and testing; it is not a
|
|||
- NovaPlan includes a lightweight CatOpt bridge scaffold to map planning primitives
|
||||
into a canonical representation for interoperability with other runtimes (Open-EnergyMesh,
|
||||
GridVerse, etc.).
|
||||
- The bridge provides small building blocks: Object (LocalProblem summary), Morphism (signals/deltas),
|
||||
and a minimal bridge_example helper to bootstrap experiments.
|
||||
- This MVP scaffold enables adapters to plug in rovers, habitat modules, or orbital domains without locking
|
||||
in specific vendor representations.
|
||||
- See nova_plan/catopt_bridge.py for core bridge logic and nova_plan/interop_catopt.py for a lightweight
|
||||
interoperability facade.
|
||||
- The bridge exposes minimal primitives: Object (LocalProblem summary) and Morphism (delta signals).
|
||||
- Convenience helpers are available to generate a pair in a single call, for use by adapters.
|
||||
- See nova_plan/catopt_bridge.py for the core bridge logic and nova_plan/interop_catopt.py for
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
"""Tiny habitat module adapter stub for NovaPlan MVP.
|
||||
|
||||
Represents a second domain agent (habitat life-support) that participates in
|
||||
the same federated ADMM-like exchange as the rover.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from nova_plan.planner import LocalProblem, delta_sync, simple_admm_step
|
||||
from nova_plan.contracts import PlanDelta
|
||||
|
||||
|
||||
def make_local_problem() -> LocalProblem:
|
||||
def objective(local_vars, shared_vars):
|
||||
return sum(local_vars.values()) - 0.25 * sum(shared_vars.values())
|
||||
|
||||
return LocalProblem(id="habitat-1", objective=objective, variables={"a": 2.0, "b": 1.0})
|
||||
|
||||
|
||||
class HabitatAdapter:
|
||||
def __init__(self):
|
||||
self.local = make_local_problem()
|
||||
|
||||
def step(self, shared_vars: dict[str, float]) -> PlanDelta:
|
||||
simple_admm_step(self.local, shared_vars, rho=1.0)
|
||||
return delta_sync(self.local, shared_vars, agent_id=self.local.id, rho=1.0)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
"""Tiny rover adapter stub for NovaPlan MVP.
|
||||
|
||||
Demonstrates how a rover planner could expose a LocalProblem and consume
|
||||
shared signals via delta-sync, returning a PlanDelta for governance.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from nova_plan.planner import LocalProblem, simple_admm_step, delta_sync
|
||||
from nova_plan.contracts import PlanDelta
|
||||
|
||||
|
||||
def make_local_problem() -> LocalProblem:
|
||||
def objective(local_vars, shared_vars):
|
||||
# Simple objective: prefer lower local values while considering shareds
|
||||
return sum(local_vars.values()) - 0.5 * sum(shared_vars.values())
|
||||
|
||||
return LocalProblem(id="rover-1", objective=objective, variables={"x": 1.0, "y": 0.5})
|
||||
|
||||
|
||||
class RoverAdapter:
|
||||
def __init__(self):
|
||||
self.local = make_local_problem()
|
||||
|
||||
def step(self, shared_vars: dict[str, float]) -> PlanDelta:
|
||||
# Perform a tiny ADMM-like step and emit a delta reflecting local changes
|
||||
simple_admm_step(self.local, shared_vars, rho=1.0)
|
||||
return delta_sync(self.local, shared_vars, agent_id=self.local.id, rho=1.0)
|
||||
|
|
@ -1,86 +1,76 @@
|
|||
"""Lightweight CatOpt bridge scaffold for NovaPlan MVP.
|
||||
"""Minimal CatOpt bridge scaffolding for NovaPlan MVP.
|
||||
|
||||
Public API used by tests:
|
||||
- Object: minimal wrapper around a LocalProblem-like object
|
||||
- Morphism: wrapper for delta signals between source and destination
|
||||
- to_object(lp): convert a LocalProblem-like instance to Object
|
||||
- to_morphism(delta, source, target, version=None): create a Morphism from delta
|
||||
- bridge_example(lp, source, target): package as a dict with object and serialized morphism
|
||||
- validate_contracts(obj, morph): basic compatibility check
|
||||
This module provides tiny, well-scoped helpers to map NovaPlan primitives
|
||||
to a canonical CatOpt-like representation suitable for interoperability
|
||||
in MVP experiments.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, Dict
|
||||
from time import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from nova_plan.contracts import PlanDelta
|
||||
from nova_plan.planner import LocalProblem
|
||||
|
||||
|
||||
class Object:
|
||||
def __init__(self, local_problem: LocalProblem):
|
||||
self.local_problem = local_problem
|
||||
@dataclass
|
||||
class ObjectI:
|
||||
"""Canonical object representation (per-agent LocalProblem).
|
||||
|
||||
@property
|
||||
def agent_id(self) -> str:
|
||||
return getattr(self.local_problem, "id", "unknown")
|
||||
|
||||
@property
|
||||
def variables(self) -> Dict[str, float]:
|
||||
return getattr(self.local_problem, "variables", {})
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {"agent_id": self.agent_id, "variables": self.variables}
|
||||
|
||||
|
||||
class Morphism:
|
||||
def __init__(self, delta: Dict[str, float], source: str, target: str, version: int | None = None):
|
||||
self.delta = delta
|
||||
self.source = source
|
||||
self.target = target
|
||||
self.version = version or 1
|
||||
self.timestamp = time()
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps({
|
||||
"source": self.source,
|
||||
"target": self.target,
|
||||
"delta": self.delta,
|
||||
"version": self.version,
|
||||
"timestamp": self.timestamp,
|
||||
})
|
||||
|
||||
|
||||
def to_object(lp: LocalProblem) -> Object:
|
||||
return Object(lp)
|
||||
|
||||
|
||||
def to_morphism(delta: Dict[str, float], source: str, target: str, version: int | None = None) -> Morphism:
|
||||
return Morphism(delta=delta, source=source, target=target, version=version)
|
||||
|
||||
|
||||
def bridge_example(lp: LocalProblem, source: str, target: str) -> Dict[str, Any]:
|
||||
obj = to_object(lp)
|
||||
morph = to_morphism(delta={k: v for k, v in lp.variables.items()}, source=source, target=target, version=1)
|
||||
return {"object": obj.to_dict(), "morphism": morph.to_json()}
|
||||
|
||||
|
||||
def validate_contracts(obj: Object, morph: Morphism) -> bool:
|
||||
# Basic validation: every delta key must exist in the object's variables
|
||||
obj_keys = set(obj.variables.keys())
|
||||
delta_keys = set(morph.delta.keys())
|
||||
return delta_keys.issubset(obj_keys)
|
||||
|
||||
|
||||
def bridge_pair(lp: LocalProblem, source: str, target: str, version: int | None = 1) -> Dict[str, Any]:
|
||||
"""Convenience helper returning a CatOpt-style pair for a given LocalProblem.
|
||||
|
||||
This is a small wrapper mirrors bridge_example but allows explicit version
|
||||
control and is intended for use by external adapters that want a stable
|
||||
tuple without constructing the JSON themselves.
|
||||
This is a lightweight wrapper intended for interop exploration.
|
||||
"""
|
||||
obj = to_object(lp)
|
||||
morph = to_morphism(delta={k: v for k, v in lp.variables.items()}, source=source, target=target, version=version)
|
||||
return {"object": obj.to_dict(), "morphism": morph.to_json()}
|
||||
|
||||
__all__ = ["Object", "Morphism", "to_object", "to_morphism", "bridge_example", "validate_contracts"]
|
||||
id: str
|
||||
payload: Dict[str, Any]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Morphism:
|
||||
"""Canonical morphism carrying summarized signals or delta information."""
|
||||
|
||||
source: str
|
||||
target: str
|
||||
data: Dict[str, float] # summarized signals or delta payload
|
||||
version: int = 1
|
||||
contract_id: str = "default"
|
||||
|
||||
|
||||
def to_object(local: LocalProblem) -> ObjectI:
|
||||
"""Map a LocalProblem to a canonical ObjectI representation."""
|
||||
payload = {
|
||||
"variables": local.variables,
|
||||
"constraints": local.constraints,
|
||||
"id": local.id,
|
||||
}
|
||||
return ObjectI(id=local.id, payload=payload)
|
||||
|
||||
|
||||
def delta_to_morphism(delta: PlanDelta, source: str = "local", target: str = "global", contract_id: Optional[str] = None) -> Morphism:
|
||||
"""Convert a PlanDelta into a canonical Morphism."""
|
||||
return Morphism(
|
||||
source=delta.agent_id or source,
|
||||
target=target,
|
||||
data=delta.delta,
|
||||
version=1,
|
||||
contract_id=contract_id or delta.__dict__.get("contract_id", "default"),
|
||||
)
|
||||
|
||||
|
||||
def bridge_example():
|
||||
"""Small helper to illustrate a mapping between NovaPlan and CatOpt forms.
|
||||
|
||||
This is intentionally lightweight and for demonstration in tests/examples.
|
||||
Returns a tuple of (ObjectI, Morphism).
|
||||
"""
|
||||
# Minimal synthetic LocalProblem
|
||||
lp = LocalProblem(id="demo-agent", objective=lambda v, s: sum(v.values()) + sum(s.values()), variables={"a": 1.0}, constraints={})
|
||||
obj = to_object(lp)
|
||||
# Fake delta
|
||||
delta = {"a": -0.1}
|
||||
import time
|
||||
d = PlanDelta(agent_id=lp.id, delta=delta, timestamp=time.time())
|
||||
morph = delta_to_morphism(d, source=lp.id)
|
||||
return obj, morph
|
||||
|
||||
|
||||
__all__ = ["ObjectI", "Morphism", "to_object", "delta_to_morphism", "bridge_example"]
|
||||
|
|
|
|||
|
|
@ -1,28 +1,24 @@
|
|||
"""Interoperability helpers for NovaPlan <-> CatOpt bridge.
|
||||
"""Interoperability facade between NovaPlan and CatOpt-like Canonical structs.
|
||||
|
||||
This module provides a tiny, import-friendly entry point to produce a
|
||||
CatOpt-compatible pair (Object + Morphism) from a LocalProblem. It leverages
|
||||
the existing bridge utilities and keeps integration concerns centralized.
|
||||
This module provides simple, importable helpers to convert between NovaPlan's
|
||||
LocalProblem representations and the canonical CatOpt bridge objects defined in
|
||||
nova_plan.catopt_bridge.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, Any
|
||||
|
||||
from nova_plan.dsl import LocalProblemDSL
|
||||
from nova_plan.planner import LocalProblem
|
||||
from nova_plan.catopt_bridge import bridge_pair
|
||||
from nova_plan.catopt_bridge import ObjectI, Morphism, to_object, delta_to_morphism
|
||||
from nova_plan.contracts import PlanDelta
|
||||
|
||||
|
||||
def to_catopt_pair_from_dsl(dsl: LocalProblemDSL, source: str, target: str, version: int = 1) -> Dict[str, Any]:
|
||||
"""Convert a LocalProblemDSL description to a CatOpt-style pair.
|
||||
|
||||
This is a convenience facade used by adapters to bootstrap NovaPlan
|
||||
primitives into a canonical CatOpt representation.
|
||||
"""
|
||||
lp: LocalProblem = dsl.to_local_problem()
|
||||
return bridge_pair(lp, source=source, target=target, version=version)
|
||||
def local_to_canon(local: LocalProblem) -> ObjectI:
|
||||
"""Convert a LocalProblem to the canonical ObjectI form."""
|
||||
return to_object(local)
|
||||
|
||||
|
||||
def to_catopt_pair_from_lp(lp: LocalProblem, source: str, target: str, version: int = 1) -> Dict[str, Any]:
|
||||
"""Convert an existing LocalProblem instance into a CatOpt-style pair."""
|
||||
return bridge_pair(lp, source=source, target=target, version=version)
|
||||
def canon_delta_to_morphism(delta: PlanDelta) -> Morphism:
|
||||
"""Convert a PlanDelta into a Morphism as seen by the CatOpt bridge."""
|
||||
return delta_to_morphism(delta)
|
||||
|
||||
|
||||
__all__ = ["local_to_canon", "canon_delta_to_morphism"]
|
||||
|
|
|
|||
|
|
@ -1,66 +1,24 @@
|
|||
import json
|
||||
import pytest
|
||||
import time
|
||||
|
||||
from nova_plan.dsl import LocalProblemDSL
|
||||
from nova_plan.catopt_bridge import Object, Morphism, to_object, to_morphism, bridge_example, validate_contracts
|
||||
from nova_plan.planner import LocalProblem
|
||||
from nova_plan.contracts import PlanDelta
|
||||
from nova_plan.catopt_bridge import to_object, delta_to_morphism, ObjectI, Morphism
|
||||
|
||||
|
||||
def test_local_problem_to_object_and_morphism_basic():
|
||||
# Create a minimal LocalProblem via DSL
|
||||
dsl = LocalProblemDSL(
|
||||
agent_id="rover1",
|
||||
objective_expr='vars["x"]',
|
||||
variables={"x": 1.5},
|
||||
constraints={},
|
||||
)
|
||||
lp = dsl.to_local_problem()
|
||||
|
||||
# Convert to canonical object and verify structure
|
||||
def test_to_object_maps_basic_fields():
|
||||
lp = LocalProblem(id="test-agent", objective=lambda v, s: sum(v.values()) + sum(s.values()), variables={"x": 1.0}, constraints={})
|
||||
obj = to_object(lp)
|
||||
od = obj.to_dict()
|
||||
assert od["agent_id"] == "rover1"
|
||||
assert od["variables"] == {"x": 1.5}
|
||||
|
||||
# Create a morphism (delta) and serialize
|
||||
delta = {"x": 0.25}
|
||||
morph = to_morphism(delta, source="rover1", target="habitat", version=1)
|
||||
j = morph.to_json()
|
||||
data = json.loads(j)
|
||||
assert data["source"] == "rover1"
|
||||
assert data["target"] == "habitat"
|
||||
assert data["delta"] == delta
|
||||
assert isinstance(obj, ObjectI)
|
||||
assert obj.id == "test-agent"
|
||||
assert "variables" in obj.payload
|
||||
|
||||
|
||||
def test_bridge_example_integration():
|
||||
dsl = LocalProblemDSL(
|
||||
agent_id="habitat1",
|
||||
objective_expr='vars["a"] + shared["b"]',
|
||||
variables={"a": 2.0},
|
||||
constraints={}
|
||||
)
|
||||
lp = dsl.to_local_problem()
|
||||
res = bridge_example(lp, source="habitat1", target="rover1")
|
||||
# Basic structure checks
|
||||
assert "object" in res and isinstance(res["object"], dict)
|
||||
assert "morphism" in res and isinstance(res["morphism"], str)
|
||||
# Validate object payload keys
|
||||
obj_payload = res["object"]
|
||||
assert obj_payload["agent_id"] == "habitat1"
|
||||
assert obj_payload["variables"] == {"a": 2.0}
|
||||
|
||||
|
||||
def test_validate_contracts_basic_and_negative():
|
||||
dsl = LocalProblemDSL(
|
||||
agent_id="rover1",
|
||||
objective_expr='vars["x"]',
|
||||
variables={"x": 1.0},
|
||||
constraints={},
|
||||
)
|
||||
lp = dsl.to_local_problem()
|
||||
obj = Object(lp)
|
||||
morph = to_morphism(delta={"x": 0.5}, source="rover1", target="habitat")
|
||||
assert validate_contracts(obj, morph) is True
|
||||
|
||||
# Mismatched delta key should fail
|
||||
bad_morph = Morphism(delta={"y": 0.2}, source="rover1", target="habitat")
|
||||
assert validate_contracts(obj, bad_morph) is False
|
||||
def test_delta_to_morphism_maps_delta_fields():
|
||||
delta = {"x": 0.5}
|
||||
d = PlanDelta(agent_id="test-agent", delta=delta, timestamp=time.time())
|
||||
m = delta_to_morphism(d, contract_id="c-1", source="test-agent", target="global")
|
||||
assert isinstance(m, Morphism)
|
||||
assert m.source == "test-agent"
|
||||
assert m.target == "global"
|
||||
assert m.data == delta
|
||||
assert m.contract_id == "c-1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue