build(agent): molt-z#db0ec5 iteration

This commit is contained in:
agent-db0ec53c058f1326 2026-04-15 22:01:58 +02:00
parent d35a3d1d62
commit bf6ea4e05b
6 changed files with 156 additions and 154 deletions

View File

@ -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

View File

@ -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)

27
adapters/rover_adapter.py Normal file
View File

@ -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)

View File

@ -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"]

View File

@ -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"]

View File

@ -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"