Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/jacobian/catalog/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@
from jacobian.math.graphical_models._tools import (
TOOLS as GRAPHICAL_MODEL_TOOLS,
)
from jacobian.math.greedoids._admission import ADMISSIONS as GREEDOIDS_ADMISSIONS
from jacobian.math.greedoids._tools import TOOLS as GREEDOIDS_TOOLS
from jacobian.math.graphs._admission import ADMISSIONS as GRAPHS_ADMISSIONS
from jacobian.math.graphs._tools import TOOLS as GRAPHS_TOOLS
from jacobian.math.graphs.coloring._admission import (
Expand Down Expand Up @@ -292,6 +294,7 @@
*DIOPHANTINE_APPROXIMATION_TOOLS,
*COMBINATORICS_TOOLS,
*FINITE_SETS_TOOLS,
*GREEDOIDS_TOOLS,
*FINITE_FIELDS_TOOLS,
*LOGIC_TOOLS,
*SEQUENCES_TOOLS,
Expand Down Expand Up @@ -368,6 +371,7 @@
*FINITE_GAME_THEORY_ADMISSIONS,
*FINITE_METRIC_SPACES_ADMISSIONS,
*FINITE_SETS_ADMISSIONS,
*GREEDOIDS_ADMISSIONS,
*FINITE_STATE_TRANSDUCERS_ADMISSIONS,
*FINITE_TOPOLOGY_ADMISSIONS,
*FORMAL_POWER_SERIES_ADMISSIONS,
Expand Down
2 changes: 2 additions & 0 deletions src/jacobian/math/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
finite_state_transducers,
finite_topology,
formal_power_series,
greedoids,
graphical_models,
graphs,
impartial_games,
Expand Down Expand Up @@ -40,6 +41,7 @@
"finite_state_transducers",
"finite_topology",
"formal_power_series",
"greedoids",
"graphical_models",
"graphs",
"impartial_games",
Expand Down
25 changes: 25 additions & 0 deletions src/jacobian/math/greedoids/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Supported native greedoid/antimatroid API."""

from jacobian.math.greedoids.operations import (
antimatroid_to_convex_geometry,
bases,
basic_word_profile,
convex_geometry_to_antimatroid,
feasible_continuations,
rank,
recognize,
union_closed,
)
from jacobian.math.greedoids.values import FiniteFeasibleSetSystem

__all__ = [
"FiniteFeasibleSetSystem",
"antimatroid_to_convex_geometry",
"bases",
"basic_word_profile",
"convex_geometry_to_antimatroid",
"feasible_continuations",
"rank",
"recognize",
"union_closed",
]
33 changes: 33 additions & 0 deletions src/jacobian/math/greedoids/_admission.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Owner-local admission decisions for built-in math operations."""

from __future__ import annotations

from jacobian.catalog.admission import AdmissionDecision, OperationAdmission

ADMISSIONS: tuple[OperationAdmission, ...] = (
OperationAdmission(
"greedoid.recognize.compute",
AdmissionDecision.KEEP,
"exact exhaustive accessibility and exchange recognition with deterministic first obstruction",
),
OperationAdmission(
"greedoid.rank.compute",
AdmissionDecision.KEEP,
"exact greedoid rank over the complete feasible family",
),
OperationAdmission(
"greedoid.bases.compute",
AdmissionDecision.KEEP,
"exact maximal feasible-set family with the common rank",
),
OperationAdmission(
"greedoid.basic_word.profile.compute",
AdmissionDecision.KEEP,
"exact prefix-feasibility profile with first obstruction",
),
OperationAdmission(
"greedoid.convex_geometry.compute",
AdmissionDecision.KEEP,
"exact complementary closed-set family and feasible->closed complement map",
),
)
121 changes: 121 additions & 0 deletions src/jacobian/math/greedoids/_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"""Typed wire contracts for greedoid operations."""

from __future__ import annotations

from typing import Self

from pydantic import Field, model_validator

from jacobian._models import StrictModel
from jacobian.math.greedoids.values import FiniteFeasibleSetSystem


class RecognizeRequest(StrictModel):
"""Recognize a feasible-set family as a greedoid."""

system: FiniteFeasibleSetSystem


class RecognizeResult(StrictModel):
"""``GREEDOID`` with rank/bases, or ``NOT_A_GREEDOID`` with the first obstruction."""

status: str
obstruction: str | None = None
larger_set: tuple[int, ...] | None = None
smaller_set: tuple[int, ...] | None = None
feasible_set: tuple[int, ...] | None = None
rank: int | None = None
bases: tuple[tuple[int, ...], ...] = ()
ground_size: int | None = None

@model_validator(mode="after")
def bind_status(self) -> Self:
if self.status not in ("GREEDOID", "NOT_A_GREEDOID"):
raise ValueError("status must be GREEDOID or NOT_A_GREEDOID")
if self.status == "GREEDOID":
if self.obstruction is not None:
raise ValueError("a GREEDOID result has no obstruction")
else:
if self.obstruction is None:
raise ValueError("a NOT_A_GREEDOID result must name an obstruction")
return self


class RankRequest(StrictModel):
"""Compute greedoid rank for an optional ground subset."""

system: FiniteFeasibleSetSystem
subset: tuple[int, ...] | None = Field(default=None)


class RankResult(StrictModel):
"""The greedoid rank of the supplied subset."""

rank: int = Field(ge=0)
subset: tuple[int, ...] | None = Field(default=None)


class BasesRequest(StrictModel):
"""Compute the maximal feasible subsets (bases)."""

system: FiniteFeasibleSetSystem
subset: tuple[int, ...] | None = Field(default=None)


class BasesResult(StrictModel):
"""The basis family and common rank."""

rank: int = Field(ge=0)
bases: tuple[tuple[int, ...], ...]


class BasicWordProfileRequest(StrictModel):
"""Profile a candidate basic word."""

system: FiniteFeasibleSetSystem
word: tuple[int, ...] = Field(default=())


class BasicWordProfileResult(StrictModel):
"""Whether the word is a basic word, with first obstruction if not."""

status: str
obstruction: str | None = None
prefix_index: int | None = None
prefix_set: tuple[int, ...] | None = None
prefix_length: int | None = None
is_full: bool | None = None
rank: int | None = None

@model_validator(mode="after")
def bind_status(self) -> Self:
if self.status not in ("BASIC_WORD", "NOT_A_BASIC_WORD"):
raise ValueError("status must be BASIC_WORD or NOT_A_BASIC_WORD")
return self


class ConvexGeometryRequest(StrictModel):
"""Compute the complementary closed-set family of a full-support antimatroid."""

system: FiniteFeasibleSetSystem


class ConvexGeometryResult(StrictModel):
"""The closed-set family and the feasible->closed complement map."""

closed_family: tuple[tuple[int, ...], ...]
complement_map: dict[tuple[int, ...], tuple[int, ...]]


__all__ = [
"BasesRequest",
"BasesResult",
"BasicWordProfileRequest",
"BasicWordProfileResult",
"ConvexGeometryRequest",
"ConvexGeometryResult",
"RankRequest",
"RankResult",
"RecognizeRequest",
"RecognizeResult",
]
101 changes: 101 additions & 0 deletions src/jacobian/math/greedoids/_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Domain adapter for greedoid operations."""

from __future__ import annotations

from typing import Any

from jacobian.math.greedoids._models import (
BasesRequest,
BasesResult,
BasicWordProfileRequest,
BasicWordProfileResult,
ConvexGeometryRequest,
ConvexGeometryResult,
RankRequest,
RankResult,
RecognizeRequest,
RecognizeResult,
)
from jacobian.math.greedoids.operations import (
antimatroid_to_convex_geometry,
bases,
basic_word_profile,
rank,
recognize,
)

__all__ = [
"compute_bases",
"compute_basic_word_profile",
"compute_convex_geometry",
"compute_rank",
"compute_recognize",
]


def compute_recognize(request: RecognizeRequest) -> RecognizeResult:
result: dict[str, Any] = recognize(request.system)
if result["status"] == "GREEDOID":
return RecognizeResult(
status="GREEDOID",
rank=result["rank"],
bases=tuple(result["bases"]),
ground_size=result["ground_size"],
)
return RecognizeResult(
status="NOT_A_GREEDOID",
obstruction=result["obstruction"],
larger_set=result.get("larger_set"),
smaller_set=result.get("smaller_set"),
feasible_set=result.get("feasible_set"),
)


def compute_rank(request: RankRequest) -> RankResult:
if request.subset is None:
r = rank(request.system)
else:
r = rank(request.system, frozenset(request.subset))
return RankResult(rank=r, subset=request.subset)


def compute_bases(request: BasesRequest) -> BasesResult:
if request.subset is None:
r, basis_list = bases(request.system)
else:
r, basis_list = bases(request.system, frozenset(request.subset))
return BasesResult(
rank=r,
bases=tuple(tuple(sorted(b)) for b in basis_list),
)


def compute_basic_word_profile(
request: BasicWordProfileRequest,
) -> BasicWordProfileResult:
result: dict[str, Any] = basic_word_profile(request.system, request.word)
if result["status"] == "BASIC_WORD":
return BasicWordProfileResult(
status="BASIC_WORD",
prefix_length=result["prefix_length"],
is_full=result["is_full"],
rank=result["rank"],
)
return BasicWordProfileResult(
status="NOT_A_BASIC_WORD",
obstruction=result["obstruction"],
prefix_index=result.get("prefix_index"),
prefix_set=result.get("prefix_set"),
)


def compute_convex_geometry(
request: ConvexGeometryRequest,
) -> ConvexGeometryResult:
closed_family, complement_map = antimatroid_to_convex_geometry(
request.system
)
return ConvexGeometryResult(
closed_family=tuple(closed_family),
complement_map=complement_map,
)
Loading
Loading