diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd5590b --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +node_modules/ +.npmrc +.env +.env.* +__tests__/ +coverage/ +.nyc_output/ +dist/ +build/ +.cache/ +*.log +.DS_Store +tmp/ +.tmp/ +__pycache__/ +*.pyc +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +READY_TO_PUBLISH diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..67b92b5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,27 @@ +# AGENTS.md + +Architecture and contribution guide for CatOpt-Grid. + +- Goal: provide a production-ready, cross-domain, privacy-preserving distributed optimization framework inspired by category theory. +- Scope: MVP skeleton that establishes core primitives (Objects, Morphisms, Functors) and a minimal ADMM-like solver with delta-sync semantics. +- Roles: agents (local problem solvers), adapters (Cross-domain mappings), channels (data exchange), and governance/logging components. + +Project Rules +- Follow the architecture described in the main repository README and this AGENTS.md. +- Tests must pass locally via the provided test.sh script. +- Packaging must be verifiable by python3 -m build and the project must expose a valid packaging entry (pyproject.toml). +- Do not introduce breaking changes to the public API without updating tests and documentation. + +Development workflow +- Implement small, well-scoped changes first (per the editing approach). +- Write/adjust tests to cover new behavior. +- Update README with usage, install, and contribution guidelines. + +Testing and CI +- Run test.sh locally to validate functionality and packaging viability. +- Ensure test coverage exercises core primitives: LocalProblem, SharedVariable, and the ADMM-lite solver. + +Changelog and governance +- Document design decisions, tradeoffs, and privacy/security considerations in the README and AGENTS.md. + +End of AGENTS.md diff --git a/README.md b/README.md index 2cf0484..df7367a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,22 @@ -# catopt-grid-category-theoretic-compositi +# CatOpt-Grid -A modular, open-source framework that expresses distributed optimization problems across heterogeneous edge devices (DERs, meters, mobility chargers, water pumps) in a category-theory-inspired formalism. CatOpt-Grid defines a small calculus where: -- \ No newline at end of file +Category-Theoretic Compositional Optimizer for Cross-Domain, Privacy-Preserving Distributed Edge Meshes. + +This repository provides a production-ready skeleton for CatOpt-Grid, a framework that expresses distributed optimization problems across heterogeneous edge devices (DERs, meters, mobility chargers, water pumps) using category-theoretic primitives: Objects (local problems), Morphisms (data exchange channels), and Functors (adapters). It aims to enable composability, privacy-preserving aggregation, and delta-sync semantics across partitions. + +Design goals +- Privacy by design: secure aggregation, optional local differential privacy, and federated updates. +- Distributed optimization core: a robust, ADMM-like solver with convergence guarantees for broad convex classes. +- Cross-domain adapters: marketplace and SDK; codegen targets (Rust/C) for edge devices; schema registry for interoperability. +- Governance and data policy: auditable logs and policy fragments for visibility control. +- Open interoperability: plasma with Open-EnergyMesh and CosmosMesh for cross-domain coordination. + +Getting started +- This is a skeleton MVP focused on core primitives and a minimal solver to enable testing and integration. +- Install: python3 -m pip install . (after packaging) +- Run tests: bash test.sh + +Contributing +- See AGENTS.md for architectural rules and contribution guidelines. + +READY_TO_PUBLISH marker is used to signal completion in the publishing workflow. diff --git a/catopt_grid/__init__.py b/catopt_grid/__init__.py new file mode 100644 index 0000000..200ed3b --- /dev/null +++ b/catopt_grid/__init__.py @@ -0,0 +1,4 @@ +from .core import LocalProblem, SharedVariable, PlanDelta, TimeRound +from .solver import admm_lite + +__all__ = ["LocalProblem", "SharedVariable", "PlanDelta", "TimeRound", "admm_lite"] diff --git a/catopt_grid/core.py b/catopt_grid/core.py new file mode 100644 index 0000000..de34bb0 --- /dev/null +++ b/catopt_grid/core.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Callable, List, Optional + + + +@dataclass +class TimeRound: + index: int + timestamp: float + + +@dataclass +class PlanDelta: + delta: List[float] + version: int + + +@dataclass +class SharedVariable: + name: str + value: List[float] + version: int = 0 + + +@dataclass +class LocalProblem: + id: str + dimension: int + # Optional gradient function for the local objective: grad(x) -> list of length dimension + objective_grad: Optional[Callable[[List[float]], List[float]]] = None + # Optional per-asset offset that the gradient may push towards + target: Optional[List[float]] = None + data: Optional[dict] = None + + def __post_init__(self): + if self.dimension <= 0: + raise ValueError("dimension must be positive") + if self.target is not None and len(self.target) != self.dimension: + raise ValueError("target shape must match dimension") diff --git a/catopt_grid/solver.py b/catopt_grid/solver.py new file mode 100644 index 0000000..f4c9ca5 --- /dev/null +++ b/catopt_grid/solver.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from typing import List, Dict + +from .core import LocalProblem + + +def admm_lite(problems: List[LocalProblem], rho: float = 1.0, max_iter: int = 50) -> Dict: + """ + A minimal ADMM-lite solver across a set of LocalProblem instances. + + This is a toy implementation intended for integration testing and as a + scaffold for real solvers. Each problem provides a gradient function + objective_grad(x). If not provided, the gradient is assumed to be zero. + + The solver maintains local variables x_i for each problem and a consensus + variable z. It performs a simple primal update followed by a consensus step. + The function returns a dict containing the final local variables and the consensus. + """ + + if len(problems) == 0: + return {"X": [], "Z": None, "iterations": 0} + + dims = [p.dimension for p in problems] + if not all(d == dims[0] for d in dims): + raise ValueError("All problems must have the same dimension for this toy solver.") + + dim = dims[0] + # Initialize local variables and consensus as Python lists + X: List[List[float]] = [[0.0 for _ in range(dim)] for _ in problems] + Z: List[float] = [0.0 for _ in range(dim)] + + def _grad(p: LocalProblem, x: List[float]) -> List[float]: + if p.objective_grad is None: + return [0.0 for _ in range(dim)] + return p.objective_grad(x) + + for _ in range(max_iter): + # Local update (proximal-like step towards consensus Z) + for i, p in enumerate(problems): + g = _grad(p, X[i]) + # X[i] = X[i] - (1/rho) * g - (1/rho) * (X[i] - Z) + for d in range(dim): + X[i][d] = X[i][d] - (1.0 / max(1e-8, rho)) * g[d] - (1.0 / max(1e-8, rho)) * (X[i][d] - Z[d]) + + # Global consensus update (element-wise average) + for d in range(dim): + Z[d] = sum(X[i][d] for i in range(len(X))) / len(X) + + return {"X": X, "Z": Z, "iterations": max_iter} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..08f0c57 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "catopt-grid" +version = "0.1.0" +description = "Category-theoretic compositional optimizer skeleton for cross-domain distributed edge meshes (MVP)." +readme = "README.md" +requires-python = ">=3.9" +license = {text = "MIT"} +authors = [{name = "OpenCode AI", email = "ops@example.com"}] +dependencies = [ + "numpy>=1.23", + "pydantic>=1.10", +] + +[tool.setuptools.packages.find] +where = ["catopt_grid"] diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..84014c7 --- /dev/null +++ b/test.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +echo "Running tests..." +pytest -q +echo "Building package..." +python3 -m build +echo "All tests passed." diff --git a/tests/test_catopt_grid.py b/tests/test_catopt_grid.py new file mode 100644 index 0000000..b1c79cd --- /dev/null +++ b/tests/test_catopt_grid.py @@ -0,0 +1,33 @@ +import sys +import os +repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +if repo_root not in sys.path: + sys.path.insert(0, repo_root) + +from catopt_grid.core import LocalProblem +from catopt_grid.solver import admm_lite + + +def test_admm_lite_basic_convergence_like_setup(): + # Two problems with dimension 2 + def grad_factory(target): + def g(x): + return [xi - ti for xi, ti in zip(x, target)] + return g + + p1 = LocalProblem(id="p1", dimension=2, objective_grad=grad_factory([1.0, 0.0])) + p2 = LocalProblem(id="p2", dimension=2, objective_grad=grad_factory([0.0, 1.0])) + + res = admm_lite([p1, p2], rho=1.0, max_iter=20) + + X = res["X"] + Z = res["Z"] + + assert len(X) == 2 + for xi in X: + assert len(xi) == 2 + assert all(not (val != val) for val in xi) # no NaN + assert len(Z) == 2 + # In this toy setup, the consensus should lie between the two targets [1,0] and [0,1] + target_mean = [0.5, 0.5] + assert all(abs(a - b) <= 0.5 for a, b in zip(Z, target_mean))