Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
8 changes: 8 additions & 0 deletions src/jacobian/catalog/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@
ADMISSIONS as FINITE_TOPOLOGY_ADMISSIONS,
)
from jacobian.math.finite_topology._tools import TOOLS as FINITE_TOPOLOGY_TOOLS
from jacobian.math.finite_topology_spaces._admission import (
ADMISSIONS as FINITE_TOPOLOGY_SPACES_ADMISSIONS,
)
from jacobian.math.finite_topology_spaces._tools import (
TOOLS as FINITE_TOPOLOGY_SPACES_TOOLS,
)
from jacobian.math.formal_power_series._admission import (
ADMISSIONS as FORMAL_POWER_SERIES_ADMISSIONS,
)
Expand Down Expand Up @@ -336,6 +342,7 @@
*WORDS_TOOLS,
*SYMBOLIC_DYNAMICS_TOOLS,
*FINITE_TOPOLOGY_TOOLS,
*FINITE_TOPOLOGY_SPACES_TOOLS,
*ELECTRICAL_NETWORKS_TOOLS,
*REGULAR_LANGUAGES_TOOLS,
*ALGEBRAIC_COMBINATORICS_TOOLS,
Expand Down Expand Up @@ -370,6 +377,7 @@
*FINITE_SETS_ADMISSIONS,
*FINITE_STATE_TRANSDUCERS_ADMISSIONS,
*FINITE_TOPOLOGY_ADMISSIONS,
*FINITE_TOPOLOGY_SPACES_ADMISSIONS,
*FORMAL_POWER_SERIES_ADMISSIONS,
*GEOMETRY_ADMISSIONS,
*GEOMETRY_EUCLIDEAN_ADMISSIONS,
Expand Down
29 changes: 29 additions & 0 deletions src/jacobian/math/finite_topology_spaces/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Supported native finite topological space API."""

from jacobian.math.finite_topology_spaces.operations import (
boundary,
closure,
continuous_check,
from_preorder,
interior,
kolmogorov_quotient,
minimal_neighbourhoods,
specialization_preorder,
)
from jacobian.math.finite_topology_spaces.values import (
FiniteTopologicalMap,
FiniteTopologicalSpace,
)

__all__ = [
"FiniteTopologicalMap",
"FiniteTopologicalSpace",
"boundary",
"closure",
"continuous_check",
"from_preorder",
"interior",
"kolmogorov_quotient",
"minimal_neighbourhoods",
"specialization_preorder",
]
Comment on lines +18 to +29

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 New domain is not registered in the root jacobian.math namespace

The package exposes a public __all__ and has an owner-local test_public_api.py, but finite_topology_spaces was not added to src/jacobian/math/__init__.py or to ROOT_MATH_DOMAINS in tests/math/public_api/test_namespace.py:18-43. from jacobian.math import finite_topology_spaces still works as a submodule import so the new test passes, but the cross-owner invariants (no private names, one canonical owner per public callable) never run against this domain. Confirm whether omission from the root namespace is deliberate.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

33 changes: 33 additions & 0 deletions src/jacobian/math/finite_topology_spaces/_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(
"topology.finite.interior.compute",
AdmissionDecision.KEEP,
"exact interior via minimal open neighbourhood containment in an Alexandrov space",
),
OperationAdmission(
"topology.finite.closure.compute",
AdmissionDecision.KEEP,
"exact closure via specialization preorder up-set",
),
OperationAdmission(
"topology.finite.boundary.compute",
AdmissionDecision.KEEP,
"exact boundary as closure minus interior",
),
OperationAdmission(
"topology.finite.kolmogorov_quotient.compute",
AdmissionDecision.KEEP,
"exact T0 quotient identifying points with the same minimal open neighbourhood",
),
OperationAdmission(
"topology.finite.continuity_check.compute",
AdmissionDecision.KEEP,
"exact continuity check via specialization preorder monotonicity",
),
)
61 changes: 61 additions & 0 deletions src/jacobian/math/finite_topology_spaces/_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Typed wire contracts for finite topological space operations."""

from __future__ import annotations

from pydantic import Field

from jacobian._models import StrictModel
from jacobian.math.finite_topology_spaces.values import (
FiniteTopologicalMap,
FiniteTopologicalSpace,
)


class SubsetRequest(StrictModel):
"""Operate on a subset of points."""

space: FiniteTopologicalSpace
subset: tuple[int, ...] = Field(default=())
Comment on lines +14 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Asking for the closure or boundary of a point number that does not exist crashes the request

A request naming a point outside the space is accepted by the input model and then rejected deep inside the computation (raise ValueError at src/jacobian/math/finite_topology_spaces/operations.py:59-60), so an accepted request turns into a host error instead of a typed answer or a validation message.
Impact: Callers get an unhandled server-side failure rather than a clear rejection of their input.

Unvalidated subset indices in SubsetRequest

SubsetRequest (src/jacobian/math/finite_topology_spaces/_models.py:14-18) accepts any tuple[int, ...] for subset with no cross-field check against the space's point count. compute_closure / compute_boundary (src/jacobian/math/finite_topology_spaces/_operations.py:37-44) then call closure, which raises ValueError("subset index out of range"). AGENTS.md states: "A request the model accepts must return a typed domain result; mathematical inapplicability belongs in the request validator or the result, not in a raised backend exception."

The same unvalidated input behaves inconsistently for interior, which silently ignores out-of-range members (src/jacobian/math/finite_topology_spaces/operations.py:41-50), so topology.finite.interior.compute returns an answer while topology.finite.closure.compute fails on the identical request. The fix belongs in the request model: validate that every subset index lies in range(len(space.points)) (and reject duplicates), which also makes the in-kernel raise unreachable.

Prompt for agents
SubsetRequest in src/jacobian/math/finite_topology_spaces/_models.py accepts arbitrary integers in `subset` with no check against the accompanying space. compute_closure and compute_boundary then reach `closure()` in operations.py, which raises ValueError("subset index out of range"), turning an accepted request into a host exception; compute_interior instead silently ignores the same out-of-range indices, so the two operations disagree on identical input. Add a model_validator(mode="after") on SubsetRequest that requires every subset index to be within range(len(space.points)) (and consider rejecting duplicates so the subset is canonical), so the mathematical domain is enforced at the request boundary and the kernel raise becomes unreachable.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.



class InteriorResult(StrictModel):
interior: tuple[int, ...]


class ClosureResult(StrictModel):
closure: tuple[int, ...]


class BoundaryResult(StrictModel):
boundary: tuple[int, ...]


class ContinuousCheckRequest(StrictModel):
"""Check continuity of a point map."""

point_map: FiniteTopologicalMap


class ContinuousCheckResult(StrictModel):
is_continuous: bool


class KolmogorovQuotientRequest(StrictModel):
space: FiniteTopologicalSpace


class KolmogorovQuotientResult(StrictModel):
quotient_points: tuple[str, ...]
quotient_preorder: tuple[tuple[int, ...], ...]


__all__ = [
"BoundaryResult",
"ClosureResult",
"ContinuousCheckRequest",
"ContinuousCheckResult",
"InteriorResult",
"KolmogorovQuotientRequest",
"KolmogorovQuotientResult",
"SubsetRequest",
]
61 changes: 61 additions & 0 deletions src/jacobian/math/finite_topology_spaces/_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Domain adapter for finite topological space operations."""

from __future__ import annotations

from jacobian.math.finite_topology_spaces._models import (
BoundaryResult,
ClosureResult,
ContinuousCheckRequest,
ContinuousCheckResult,
InteriorResult,
KolmogorovQuotientRequest,
KolmogorovQuotientResult,
SubsetRequest,
)
from jacobian.math.finite_topology_spaces.operations import (
boundary,
closure,
continuous_check,
interior,
kolmogorov_quotient,
)

__all__ = [
"compute_boundary",
"compute_closure",
"compute_continuous_check",
"compute_interior",
"compute_kolmogorov_quotient",
]


def compute_interior(request: SubsetRequest) -> InteriorResult:
result = interior(request.space, frozenset(request.subset))
return InteriorResult(interior=tuple(sorted(result)))


def compute_closure(request: SubsetRequest) -> ClosureResult:
result = closure(request.space, frozenset(request.subset))
return ClosureResult(closure=tuple(sorted(result)))


def compute_boundary(request: SubsetRequest) -> BoundaryResult:
result = boundary(request.space, frozenset(request.subset))
return BoundaryResult(boundary=tuple(sorted(result)))


def compute_continuous_check(
request: ContinuousCheckRequest,
) -> ContinuousCheckResult:
result = continuous_check(request.point_map)
return ContinuousCheckResult(is_continuous=result)


def compute_kolmogorov_quotient(
request: KolmogorovQuotientRequest,
) -> KolmogorovQuotientResult:
result = kolmogorov_quotient(request.space)
return KolmogorovQuotientResult(
quotient_points=result["quotient_points"], # type: ignore[arg-type]
quotient_preorder=result["quotient_preorder"], # type: ignore[arg-type]
)
170 changes: 170 additions & 0 deletions src/jacobian/math/finite_topology_spaces/_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
"""Finite topological space operation declarations."""

from collections.abc import Callable
from typing import Any

from jacobian._models import StrictModel
from jacobian.catalog._examples import example
from jacobian.catalog.models import MathTool, OperationExample
from jacobian.math.finite_topology_spaces._models import (
BoundaryResult,
ClosureResult,
ContinuousCheckRequest,
ContinuousCheckResult,
InteriorResult,
KolmogorovQuotientRequest,
KolmogorovQuotientResult,
SubsetRequest,
)
from jacobian.math.finite_topology_spaces._operations import (
compute_boundary,
compute_closure,
compute_continuous_check,
compute_interior,
compute_kolmogorov_quotient,
)


def _op[
RequestT: StrictModel,
ResultT: StrictModel,
](
operation_id: str,
title: str,
description: str,
request_model: type[RequestT],
result_model: type[ResultT],
operation: Callable[[RequestT], ResultT],
*tags: str,
examples: tuple[OperationExample, ...] = (),
version: str = "1",
) -> MathTool[RequestT, ResultT]:
return MathTool(
operation_id=operation_id,
version=version,
title=title,
description=description,
request_type=request_model,
result_type=result_model,
run=operation,
tags=tags,
examples=examples,
)


# A Sierpinski space: points {a, b}, preorder rows: a -> {a}, b -> {a, b}
# (a <= b in specialization order, so open sets are {}, {a}, {a,b}).
_SPACE = {
"points": ["a", "b"],
"preorder": [[0], [0, 1]],
}


TOPOLOGY_SPACE_OPERATIONS: tuple[MathTool[Any, Any], ...] = (
_op(
"topology.finite.interior.compute",
"Compute the interior of a subset",
"Return the largest open set contained in the subset. In an "
"Alexandrov space, the interior consists of all points whose minimal "
"open neighbourhood is contained in the subset.",
SubsetRequest,
InteriorResult,
compute_interior,
"finite-topology",
"interior",
"exact",
examples=(
example(
"sierpinski_interior",
"Interior of {b} in the Sierpinski space.",
{"space": _SPACE, "subset": [1]},
),
),
),
_op(
"topology.finite.closure.compute",
"Compute the closure of a subset",
"Return the smallest closed set containing the subset. The closure "
"of x is the up-set of x in the specialization preorder.",
SubsetRequest,
ClosureResult,
compute_closure,
"finite-topology",
"closure",
"exact",
examples=(
example(
"sierpinski_closure",
"Closure of {a} in the Sierpinski space.",
{"space": _SPACE, "subset": [0]},
),
),
),
_op(
"topology.finite.boundary.compute",
"Compute the boundary of a subset",
"Return the boundary of a subset: closure minus interior.",
SubsetRequest,
BoundaryResult,
compute_boundary,
"finite-topology",
"boundary",
"exact",
examples=(
example(
"sierpinski_boundary",
"Boundary of {a} in the Sierpinski space.",
{"space": _SPACE, "subset": [0]},
),
),
),
_op(
"topology.finite.kolmogorov_quotient.compute",
"Compute the T0 (Kolmogorov) quotient",
"Return the T0 quotient that identifies points with the same minimal "
"open neighbourhood, plus the class map.",
KolmogorovQuotientRequest,
KolmogorovQuotientResult,
compute_kolmogorov_quotient,
"finite-topology",
"kolmogorov-quotient",
"exact",
examples=(
example(
"sierpinski_kolmogorov",
"T0 quotient of the Sierpinski space.",
{"space": _SPACE},
),
),
),
_op(
"topology.finite.continuity_check.compute",
"Check whether a point map is continuous",
"Return whether a point map between finite topological spaces is "
"continuous. A map f: X -> Y is continuous iff x' <= x implies "
"f(x') <= f(x) in the specialization preorders.",
ContinuousCheckRequest,
ContinuousCheckResult,
compute_continuous_check,
"finite-topology",
"continuity",
"exact",
examples=(
example(
"identity_continuous",
"The identity map is continuous.",
{
"point_map": {
"source": _SPACE,
"target": _SPACE,
"point_map": [0, 1],
},
},
),
),
),
Comment on lines +140 to +165

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 New domain overlaps heavily with the existing finite_topology domain

This domain duplicates concepts already owned by src/jacobian/math/finite_topology: topology.finite.continuity_check.compute covers the same mathematics as the existing topology.is_continuous.compute, and specialization_preorder/minimal_neighbourhoods restate topology.specialization_preorder.compute (src/jacobian/math/finite_topology/_tools.py:31,71). The two domains also use incompatible representations for the same object (open-set families with full topology-axiom validation in src/jacobian/math/finite_topology/values.py:15-44 vs. a preorder here), so agents can get two non-composable notions of a finite space. Worth confirming with the issue whether a second domain is intended rather than extending the existing one with interior/closure/boundary/quotient over FiniteTopology.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

)

TOOLS = TOPOLOGY_SPACE_OPERATIONS

__all__ = ["TOOLS"]
Loading
Loading