Two scientific software engineers reviewing a SciPy graph with a zero-cost edge and shortest-path results on dual monitors

Keep Zero-Cost Edges in Your SciPy Graph

Fri, Oct 16, 2026

The intended route from A to D is A, B, C, D, with cost 3. Convert its sparse adjacency matrix to an ordinary zero-filled dense array, pass that array to scipy.sparse.csgraph.dijkstra, and the supplied fixture instead reports A, C, D, with cost 10. The calculation completes. The missing ingredient is not another shortest-path algorithm: it is the authored zero-cost edge A→B.

This is a representation qualification problem. Does the object crossing the graph boundary still encode every authorized directed edge, including one whose weight is exactly zero? The four-node example makes that question inspectable without external data, random inputs or production assumptions. Its edge manifest is independent of the matrices being evaluated, and its expected route follows directly from four literal weights.

The evidence combines three views: stored edge coordinates, distances from the selected source, and a named path reconstructed from predecessors. Direct CSR construction and explicit non-edge-sentinel conversion preserve the baseline. Lossy conversions, a positive-weight control, an intentionally changed graph and rejected manifests explain what each successful assertion actually establishes.

The nine-case observations below are attributed to the supplied synthetic commissioning run. They are fixture evidence, not measurements from a deployed data pipeline. They support a bounded decision about this manifest and recorded environment. They do not establish production safety, performance, universal version coverage or how tied shortest paths should be selected.

Define the graph independently of its matrix

Start with the ordered node tuple A, B, C, D. It fixes both axes of every 4×4 adjacency representation: index 0 means A, index 1 means B, index 2 means C and index 3 means D. An index has no independent business meaning once separated from that mapping.

The baseline has exactly these four directed edges. An omitted coordinate means no authored connection, not an unknown weight waiting to be filled.

Source

Destination

Weight

A

B

0.0

A

C

9.0

B

C

2.0

C

D

1.0

Weights are stored as float64. The fixture admits only finite, nonnegative weights, known endpoints and unique directed coordinates. There are no concurrent writers. A→B=0 is an observed fact of this synthetic manifest, not a missing observation, rounding instruction or permission to invent a connection.

Derive the oracle before constructing a matrix. The route through B costs 0+2+1=3. The alternative through C directly costs 9+1=10. Those are the only A-to-D routes authorized here, so the shortest route is unique. No reverse edge or self-loop is in the manifest. Starting at a node still has zero distance to that same node; that distance does not require an authored self-loop.

Keep the tuple and manifest alongside the representation, rather than recovering them from a later numerical display. This is a different question from observed and introduced zeros in reports: here the zero already has valid edge identity. The task is to preserve it, not decide whether a new zero may be assigned.

The independently stated manifest also gives reviewers a stable reference when a control deliberately changes an edge. A changed graph needs its own authorization; it cannot inherit the baseline simply because the node names remain familiar.

Separate edge absence from a zero edge weight

The SciPy graph representation contract distinguishes three meanings of absence. Ordinary dense csgraph inputs use zero, infinity or NaN as non-edge values. Masked dense inputs represent absence through the mask. Sparse inputs represent absence through missing storage entries, allowing a stored zero to denote an edge. These are graph-input conventions, not universal interpretations of every NumPy or sparse operation.

The troublesome representation is therefore not “dense” in the abstract. It is an unqualified dense input that uses the same zero value for absent coordinates and for a legitimate zero-cost edge. A dense matrix with an explicit sentinel can carry the needed distinction into a graph-aware conversion, as S4 demonstrates. Masked representations are another documented option, but this fixture does not execute a masked candidate.

A numeric matrix is not a complete edge manifest

For this graph, the representation contract has three parts: node order, edge presence and edge weight. A numerical cell can express the third part while losing the second. A zero read from position A,B may be the weight of an explicit edge or the numerical value shown for an absent coordinate. Looking at that value alone cannot decide which history occurred.

This explains how two graph objects can produce equal-looking dense arrays yet authorize different traversals. One object stores A→B with weight zero; the other has no A→B entry. Their dense displays collapse that distinction. Numerical equality of the displays is not equality of the underlying edge populations.

Preserve the presence convention with the payload at every conversion boundary. Record whether absence is represented by omitted coordinates, a mask or a declared sentinel, and identify the converter that consumes it. A field named “adjacency” and a 4×4 shape are not sufficient evidence. Review the convention before interpreting a shortest-path result, not only after a route looks suspicious.

Run the nine-case SciPy graph fixture

Save the complete source below as scipy_graph_fixture.py in a writable disposable directory. Use Python 3.13.5, NumPy 2.3.5 and SciPy 1.17.0 to reproduce the recorded dependency versions. Dependencies must already be available; the driver makes no network request and requires no unpublished helper. Run:

python scipy_graph_fixture.py

The supplied commissioning observation reports PASS_9_CASES on Linux-6.18.44-x86_64-with-glibc2.41. It began at 2026-10-16T06:19:12.656593+00:00 and ended at 2026-10-16T06:19:12.663150+00:00. These timestamps identify the run, not a benchmark. Its source SHA-256 is:

6dce4ee7545a480498ab9bcc40ed3002bec3dc940bd307ac9918b75af68da729

The dates and success label above describe the supplied commissioning record. The source below preserves its code. For the stated digest, save UTF-8 without a byte-order mark, use LF line endings and include one final newline. A rerun records its own versions, timestamps, source identity and outputs; copying an earlier success label is not a rerun. Any byte change requires a new digest, and claims about the changed source require a new execution.

"""Directed four-node graph; an explicit zero is an edge, not missing data."""
import hashlib
import json
import math
import platform
import tempfile
from datetime import datetime, timezone
from pathlib import Path

import numpy as np
import scipy
from scipy.sparse import csr_matrix, issparse
from scipy.sparse.csgraph import csgraph_from_dense, dijkstra

NODES = ("A", "B", "C", "D")
EDGES = [("A", "B", 0.0), ("A", "C", 9.0), ("B", "C", 2.0), ("C", "D", 1.0)]

def require(ok, message):
    if not ok:
        raise RuntimeError(message)

def build(edges):
    seen, rows, columns, values = set(), [], [], []
    for a, b, weight in edges:
        if (a not in NODES or b not in NODES or (a, b) in seen or
                not math.isfinite(weight) or weight < 0):
            raise ValueError("Unsupported edge manifest")
        seen.add((a, b))
        rows.append(NODES.index(a)); columns.append(NODES.index(b)); values.append(weight)
    return csr_matrix((np.array(values, dtype=np.float64), (rows, columns)), shape=(4, 4))

def stored(graph):
    # Do not use nonzero(): that would omit an explicit zero-valued edge.
    return [[NODES[i], NODES[int(graph.indices[k])], float(graph.data[k])]
            for i in range(4) for k in range(graph.indptr[i], graph.indptr[i + 1])]

def path_to(predecessors, source, target):
    if target == source:
        return [NODES[source]]
    route, current = [], target
    for in NODES:
        if current == -9999:
            return None
        route.append(NODES[current])
        if current == source:
            return list(reversed(route))
        current = int(predecessors[current])
    raise RuntimeError("Predecessor cycle")

def main():
    directory = Path(tempfile.mkdtemp(prefix="scipy-graph-run-", dir=Path(_file__).parent))
    report = {"status": "FAIL", "fixture": "zero-edge-v1", "cases": [],
              "manifest": EDGES, "nodes": NODES,
              "started_utc": datetime.now(timezone.utc).isoformat(),
              "python": platform.python_version(), "numpy": np.__version__,
              "scipy": scipy.__version__, "platform": platform.platform(),
              "source_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}

    def case(identity, graph, expected, route, decision, source=0, target=3):
        row = {"id": identity, "status": "FAIL", "decision": decision,
               "source": NODES[source], "target": NODES[target]}
        report["cases"].append(row)
        before = stored(graph) if issparse(graph) else None
        distance, predecessors = dijkstra(graph, directed=True, indices=source,
                                          return_predecessors=True)
        row["distance"] = [None if np.isinf(x) else float(x) for x in distance]
        row["reachable"] = np.isfinite(distance).tolist()
        row["predecessors"] = predecessors.tolist()
        row["path"] = path_to(predecessors, source, target)
        row["stored_edges"] = before
        require(row["distance"] == expected and row["path"] == route, identity+": wrong graph result")
        if before is not None:
            require(stored(graph) == before, identity+": stored graph changed")
        row["status"] = "PASS"

    try:
        original = build(EDGES)
        require(stored(original) == [list(edge) for edge in EDGES], "source edges changed")
        dense_zero = original.toarray()
        dense_inf = np.full((4, 4), np.inf, dtype=np.float64)
        for a, b, w in EDGES:
            dense_inf[NODES.index(a), NODES.index(b)] = w
        good, lost = [0., 0., 2., 3.], [0., None, 9., 10.]
        case("S1", original, good, ["A","B","C","D"], "ACCEPT_BOUNDED")
        case("S2", dense_zero, lost, ["A","C","D"], "REPAIR_REPRESENTATION")
        case("S3", csr_matrix(dense_zero), lost, ["A","C","D"], "REPAIR_SOURCE_REQUIRED")
        case("S4", csgraph_from_dense(dense_inf, null_value=np.inf), good,
             ["A","B","C","D"], "ACCEPT_BOUNDED")
        case("S5", original.toarray(), lost, ["A","C","D"], "REJECT_LOSSY_ROUNDTRIP")
        positive = build([("A","B",1.)] + EDGES[1:])
        case("S6", positive.toarray(), [0.,1.,3.,4.], ["A","B","C","D"], "POSITIVE_WEIGHT_CONTROL")
        case("S7", build(EDGES[1:]), lost, ["A","C","D"], "INTENTIONAL_EDGE_REMOVAL_CONTROL")
        case("S8", original, [None,None,None,0.], None, "DIRECTED_NO_PATH_CONTROL", source=3,target=0)
        row = {"id":"S9", "status":"FAIL", "rejections":[]}
        report["cases"].append(row)
        for name, candidate in [
            ("duplicate", EDGES+[EDGES[0]]),
            ("negative", [("A","B",-1.)]),
            ("nonfinite", [("A","B",float("inf"))]),
            ("unknown_node", [("A","X",1.)]),
        ]:
            try:
                build(candidate)
            except ValueError:
                row["rejections"].append(name)
            else:
                raise RuntimeError("Invalid manifest admitted: "+name)
        require(stored(original)==[list(edge) for edge in EDGES],"baseline changed")
        row["status"]="PASS"; row["decision"]="REJECT_UNSUPPORTED_MANIFEST"
        require(len(report["cases"])==9,"incomplete inventory")
        report["status"]="PASS_9_CASES"
    except Exception as error:
        report["error"]={"type":type(error).__name__,"message":str(error)}
        raise
    finally:
        report["finished_utc"]=datetime.now(timezone.utc).isoformat()
        path=directory/"results.json"
        path.write_text(json.dumps(report,indent=2,allow_nan=False)+"\n")
        print(path)

if name=="__main__":
    main()

With dependencies available and the script directory writable, an invocation creates a fresh scipy-graph-run- directory beside the script and prints the path to results.json. The report starts in FAIL state, records the independent manifest and node order, and appends each case before its assertions complete. Inside the protected execution block, an exception is recorded and re-raised; the finalizer attempts to write the report. A report-writing failure also leaves the run unsuccessful. Missing dependencies or failures before that block exit unsuccessfully but may produce no report, so retain the process exit status and error output too.

For each graph case, the driver records all four source distances, reachability flags, predecessors, the requested target path and, for sparse inputs, a stored-edge inventory. It checks literal distances and the named target route, then verifies that the sparse inventory remains unchanged across the call. S9 records four admission rejections instead of invoking Dijkstra on rejected inputs.

The predecessor array is retained evidence, not a separately asserted literal array. Path reconstruction consumes it and stops on the documented no-predecessor sentinel; the Dijkstra API reference describes return_predecessors, selected source indices and -9999. The loop is bounded by the four node names and raises an error if the predecessor chain cannot be resolved within that bound. It does not follow predecessors indefinitely.

Infinite distance is serialized as JSON null, with a corresponding false reachable flag. A real zero distance remains numeric zero with true reachability. The report therefore does not ask a consumer to interpret infinity as JSON numeric syntax, and it does not confuse an unreachable node with a zero-cost connection.

Do not turn a documentation version into an executed version

Documentation and execution have separate identities. On October 16, 2026, the linked unversioned csgraph, Dijkstra and conversion references displayed SciPy 1.18.0. The supplied run instead used SciPy 1.17.0. The versioned SciPy 1.17 graph reference describes the same zero-edge distinction. A documentation page does not demonstrate execution on that release, and no SciPy 1.18 run is claimed. Keep the reference version and the run’s recorded version separate when reviewing compatibility.

The script records versions rather than enforcing them. A successful run under other installed versions is new local evidence for those versions, not a reproduction of the exact recorded environment. Likewise, PASS_9_CASES means all nine diagnostic expectations passed, including expectations of loss. It does not approve every representation. The case labels distinguish accepted candidates, repair decisions and controls; later sections map those labels to workflow decisions without rewriting the preserved driver.

Establish the sparse zero-edge baseline

S1 constructs CSR directly from the manifest’s row coordinates, column coordinates and float64 values. The SciPy 1.17 CSR constructor reference documents this coordinate form and the indptr, indices and data storage arrays. A supplied value of zero is retained as a stored value here; it is not inferred from a numerical scan of a dense matrix.

The baseline assertion compares stored(original) with the four literal edge triples. The inspection walks each row’s interval in indptr, reads destination indices from indices, and reads corresponding weights from data. It does not use nonzero() to inventory edges, because that selects nonzero numerical entries and would omit the very zero-valued edge under review. This helper assumes CSR layout and the fixed four-node mapping; recognizing an object as sparse does not validate either assumption.

For the supplied coordinate order, the expected CSR storage can be read as follows. These arrays explain the manifest-to-storage mapping; the driver records named triples rather than printing these three arrays separately.

indptr  = [0, 2, 3, 4, 4]
indices = [1, 2, 2, 3]
data    = [0.0, 9.0, 2.0, 1.0]

Row A occupies slots 0 and 1. Slot 0 names column B and stores 0.0; slot 1 names C and stores 9.0. Row B supplies the next slot, B→C=2.0. Row C supplies C→D=1.0. Row D’s empty interval contains no outgoing edges. This is positive evidence of A→B’s presence, not merely an observation that A,B evaluates numerically to zero.

From A, S1 expects distances [0,0,2,3] and the target path A,B,C,D. The path’s three edge weights sum to the independently derived cost 3. B is reachable at zero cost, while A’s own zero distance describes the starting point. Keeping those meanings separate is essential when reading the report.

The supplied run passed both the result assertions and the before/after storage comparison. That supports ACCEPT_BOUNDED for this directly constructed baseline, with this node order and recorded runtime. It does not qualify every sparse transformation that might later receive the object.

Observe the graph changed by ordinary dense interpretation

S2 takes original.toarray() and passes the result directly to Dijkstra. With rows and columns ordered A, B, C, D, the numerical display is:

[[0.0, 0.0, 9.0, 0.0],
 [0.0, 0.0, 2.0, 0.0],
 [0.0, 0.0, 0.0, 1.0],
 [0.0, 0.0, 0.0, 0.0]]

The displayed zero at A,B now shares the consumer’s ordinary zero-as-absence convention with all absent coordinates. Under that interpretation, A can reach C directly and then D, but cannot reach B. B→C still exists; it does not create a route from A into B, and directed traversal cannot reverse it.

The literal S2 distance expectation is [0,null,9,10] in JSON, with reachability [true,false,true,true]. The target route is A,C,D. Those observations match the graph after A→B has been removed from its edge population. The case passes by detecting that loss, while its workflow decision remains REPAIR_REPRESENTATION.

A plausible shortest distance can describe the wrong graph

Cost 10 is not an arithmetic mistake. It is 9+1 along the available A→C→D route. A test that merely requires D’s distance to be finite would approve this altered graph. Even checking that the target path uses existing edges would not recover the missing authorization, because both edges on A,C,D genuinely remain available.

The stronger comparison asks whether the graph supplied to the algorithm retains the independently approved population. That comparison fails at A→B before any disagreement about optimization arises. The different distance is useful evidence of the loss, not the definition of the loss.

Retain the failed candidate’s route and full distance vector. B’s change from reachable-at-zero to unreachable helps localize the representation boundary. A repair review should explain that transition rather than replacing only the reported total. Changing the expected cost from 3 to 10 would conceal the broken baseline contract.

Show why dense-to-CSR conversion cannot invent the edge

S3 wraps the same zero-filled matrix in csr_matrix(dense_zero) before traversal. The result is sparse again, but the authorized A→B edge is still absent. Its expected distances remain [0,null,9,10], and its route remains A,C,D. A type check that accepts every CSR object would miss this failure.

The distinction is between two constructor inputs. Direct coordinate construction receives an affirmative statement that A,B is present with value zero. Dense construction receives a grid of values in which that zero is numerically indistinguishable from absent coordinates. The CSR construction and storage reference lists both forms; choosing the sparse output type cannot supply edge provenance that the dense input no longer carries.

S3’s recorded inventory contains A→C=9, B→C=2 and C→D=1. It gives a structural explanation for the same route observed in S2. The conversion did not recover the fourth edge, even though shape, node count and the three positive weights remained consistent with the baseline.

The preserved driver calls S3’s diagnostic decision REPAIR_SOURCE_REQUIRED. Operationally, that is REPAIR_REPRESENTATION when the independent manifest is available, as it is in this fixture. Rebuild from that source. When provenance is unavailable, the appropriate workflow state is HOLD_MISSING_PROVENANCE instead. Neither state can be resolved merely by converting the damaged matrix into another container.

Do not manually insert A→B because a desired total happens to be 3. In this example, insertion is justified by the retained literal manifest, not by reverse-engineering a preferred answer. The expected route is an oracle against which to test reconstruction, not a replacement source of edges.

A recovery procedure must therefore name its input authority. “Converted to CSR” records a mechanical action; “rebuilt from the approved A,B,C,D manifest” records why the resulting edge population is legitimate. Without the latter, a superficially repaired object remains unqualified.

Convert an explicit non-edge sentinel correctly

S4 demonstrates a valid dense staging representation. It begins with a fresh float64 matrix filled with infinity and writes only the four manifest edges into their authorized coordinates. This construction is independent of the ambiguous zero-filled matrix. It does not search old zero cells and guess which ones should survive.

The csgraph_from_dense API defines null_value as the non-edge value. Calling csgraph_from_dense(dense_inf, null_value=np.inf) converts the declared infinity placeholders into absence while retaining A→B=0 as a real stored edge. The resulting CSR-format graph is then passed to the same directed Dijkstra call as the baseline.

Zero is a stored edge; infinity is the declared non-edge sentinel

Infinity is a staging marker, not an admitted edge weight. Every manifest edge remains finite and nonnegative. Even diagonal cells begin as infinity because the manifest authors no self-loops. The shortest-path result still assigns distance zero to the selected source, without requiring a stored diagonal zero.

For S4, the expected named inventory is the original four edges, the distance vector is [0,0,2,3], and the path is A,B,C,D. The report retains the inventory, while the driver asserts the path and distances and checks inventory stability around traversal. A reviewer should also reconcile the recorded S4 triples with the independent manifest rather than assuming every recorded field is separately asserted.

Do not replace the graph-aware conversion with csr_matrix(dense_inf). The ordinary constructor is not instructed that infinity denotes a non-edge. SciPy’s explicit-zero graph example expressly distinguishes those construction semantics. This article’s accepted candidate is the explicit-sentinel conversion actually present in S4, not an untested substitution.

This repair shows why banning all dense formats would be the wrong rule. A dense payload can retain edge meaning when its presence convention is explicit and the receiving converter honors that convention. Acceptance attaches to the complete construction-and-consumption path, not to the word “dense” or “sparse” alone.

Reject a lossy round trip and keep a positive control

S5 repeats the direct dense consumption as an explicit round-trip check: take the accepted sparse baseline, call toarray(), then pass that result to Dijkstra. The expected cost becomes 10 and the route becomes A,C,D. The baseline object itself is not rewritten into this new graph; the failure lies in interpreting the exported numerical representation without its original edge-presence distinction.

S2 and S5 deliberately expose the same loss at different review boundaries. S2 evaluates an ordinary dense candidate. S5 asks whether export from an accepted object followed by ordinary graph consumption preserves the graph. Its answer is REJECT_LOSSY_ROUNDTRIP. This is useful redundancy in the case inventory, not evidence for a second failure mechanism.

A numerical equality check between the exported array and another zero-filled display would not rescue the round trip. The representation can preserve every displayed number while losing which zero was explicitly stored. Round-trip qualification must preserve meaning, not just the values visible after both objects have been flattened into the same ambiguous convention.

S6 supplies a positive-weight control under a deliberately different manifest. It changes only A→B from 0 to 1, converts that graph to dense and expects distances [0,1,3,4]. The target path is still A,B,C,D, now costing 1+2+1=4. The alternate route through A→C→D still costs 10.

That positive control isolates the significant difference: the zero-valued connection is the one colliding with absence in the baseline dense interpretation. A dense candidate that handles a positive A→B correctly has not thereby demonstrated support for a zero A→B.

The driver records POSITIVE_WEIGHT_CONTROL, not approval of the original zero-edge workflow. Keep the modified manifest explicit in review notes. Replacing the real zero with 1 to make a dense pipeline pass would change the problem, not repair its representation. Restore the original manifest for baseline acceptance.

Distinguish an accidentally changed graph from an approved one

S7 removes A→B intentionally by constructing a sparse graph from EDGES[1:]. This is an authorized control graph with three edges, not a repaired version of the four-edge baseline. It yields [0,null,9,10] from A and the route A,C,D, exactly the result expected from the lossy S2 and S3 candidates.

That equality is decisive evidence about the limits of result-only review. The same cost 10 can mean “the authorized graph excludes A→B” or “a conversion silently dropped an authorized A→B.” The algorithm’s distance vector does not encode which of those instructions the producer intended.

Identical results do not prove identical source graphs

S2 and S7 share output but differ in authorization. S7’s source explicitly excludes the edge. S2 begins with a source that includes it and crosses a boundary that cannot preserve its presence. The responsible decision therefore depends on the manifest and transformation history, not on recognizing a particular numeric pattern as inherently acceptable or unacceptable.

This is related to the limits of a plausible numerical scalar, but the graph problem needs its own evidence. No model, residual pairing or loss repair is involved. Here the source identities are directed edges, and the operational oracle includes a named route together with the authorized population that permits it.

The driver calls S7 INTENTIONAL_EDGE_REMOVAL_CONTROL. Its successful assertion confirms the deliberately different graph’s expected behavior. It does not authorize deletion from the baseline or grant a general exception for any candidate returning 10. Keep the control’s source expression attached to its case identity.

There is another useful limitation: even matching a route and distance does not by itself prove an entire graph’s edge population. Unused edges can remain outside the requested route. This fixture addresses that gap through a retained manifest and inspectable sparse storage, rather than pretending one shortest-path query is an exhaustive inventory.

Reviewers should ask both questions explicitly: did this candidate compute the expected route for its declared graph, and is that declared graph the one the workflow was authorized to use? S7 separates those questions without changing the algorithm.

Verify direction and admitted edge inputs

S8 starts at D and asks for a path to A with directed=True. Because D has no outgoing authored edges, its distance vector is [null,null,null,0], its reachability flags are [false,false,false,true], and the target path is null. The zero at D is the source distance, not evidence of a reverse route.

The directed traversal and predecessor contract makes direction an explicit part of the call. In this fixture, the existence of C→D does not authorize D→C. S8 checks that this query remains unreachable under the declared direction; it is not a general guarantee against every directedness mistake. The driver’s DIRECTED_NO_PATH_CONTROL is a diagnostic label for this expected absence.

S9 evaluates four invalid manifest candidates separately. The duplicate control appends A→B again. The negative control supplies A→B=-1. The nonfinite control supplies A→B with positive infinity. The unknown-node control supplies A→X=1. Each must raise ValueError before a graph is admitted for traversal.

Those controls exercise the declared builder contract: unique coordinates, finite nonnegative weights and endpoints from the fixed tuple. They do not assert that every other SciPy workflow rejects the same input classes. The builder contains a finite-weight guard, but the executed nonfinite subcontrol is specifically positive infinity; this inventory is not an exhaustive exercise of every possible invalid value.

After all four rejections, the driver checks that the original stored-edge inventory still equals the literal baseline. That distinguishes successful refusal of unsupported input from accidentally damaging the source used by the other cases. S9 receives REJECT_UNSUPPORTED_MANIFEST and contributes one main case to the nine-case inventory, with four named subcontrols.

Keep rejection evidence separate from no-path evidence. S8 is an admitted graph with an unreachable target. S9 candidates are not admitted graphs for which a shortest-path answer should be interpreted.

Reconcile paths, distances and stored edges

The table reconciles the supplied commissioning expectations and reported successful case checks. Zero-edge retention refers specifically to A→B=0; “not applicable” in S6 means that the control deliberately changes its weight. Costs refer to A→D except S8, which asks for D→A.

Case

Representation

A→B=0 retained?

Target path

Cost

Workflow decision

S1

CSR from manifest

Yes

A,B,C,D

3

ACCEPT_BOUNDED

S2

Zero-filled dense

No

A,C,D

10

REPAIR_REPRESENTATION

S3

CSR from zero-filled dense

No

A,C,D

10

REPAIR_REPRESENTATION; source required

S4

Infinity sentinel → CSR

Yes

A,B,C,D

3

ACCEPT_BOUNDED

S5

CSR → dense → traversal

No

A,C,D

10

REJECT_LOSSY_ROUNDTRIP

S6

Positive-weight dense control

Not applicable

A,B,C,D

4

Control only; changed weight

S7

CSR without A→B

Intentionally absent

A,C,D

10

Control only; approved removal

S8

Baseline CSR, source D

Yes

No path to A

Unreachable

Control only; direction check

S9

Four invalid manifests

Not admitted

Not computed

Not computed

REJECT_UNSUPPORTED_MANIFEST

S9’s recorded rejection names are duplicate, negative, nonfinite and unknown_node. Its final check preserves the baseline’s four stored edges. S3 retains the raw report label REPAIR_SOURCE_REQUIRED; the table maps that label to the representation-repair workflow when source provenance survives. S6–S8 retain their diagnostic control labels in the report and are not additional approvals of the original conversion.

A passing assertion and an acceptance decision answer different questions. S5 must reproduce the lossy result for its diagnostic check to pass, yet the route through that conversion remains rejected. S9 must refuse each unsupported candidate. Reading only the report’s top-level PASS would invert the meaning of these controls.

For sparse cases, reconcile the named inventory with the case-specific manifest, then read all distances and the target route. Dense cases deliberately record no CSR inventory, so their null stored_edges field is not evidence that the graph has no edges. Likewise, JSON null in distance evidence means unreachable only in conjunction with the recorded conversion and reachability flags.

This is complete diagnostic evidence applied to one graph boundary: source identity, representation, expected behavior, observations and a decision. Preserve the entire report and source together. A screenshot of cost 3, or the word PASS detached from its case inventory, cannot show which graph was admitted or which loss controls were exercised.

Recover only from surviving edge provenance

Suppose the zero-filled matrix is all that remains. It does not tell you whether A,B was a zero-cost edge, whether other zero cells were edges, or whether every zero genuinely meant absence. The representation has collapsed several possible source graphs into the same values. No inspection of those values alone can select the historically authorized graph.

Assign HOLD_MISSING_PROVENANCE to that recovery attempt. This is not a claim that no reconstruction is possible. It means the available object is insufficient evidence for deciding which edges to restore. Seek a trustworthy edge manifest, an original sparse object with its node mapping, or another retained source that independently establishes presence.

Do not manufacture edges from blank evidence

Storing every zero cell as an edge is not conservative restoration. For this fixture it would authorize connections never present in the four-edge source, including directions the manifest excludes. It would answer a new graph problem while presenting the change as a representation repair.

Nor can the expected scalar total identify a unique repair. A desired cost is a check on an authorized reconstruction, not permission to create whatever connections produce that cost. Preserve the distinction between evidence of a lost edge and a hypothesis about which edge might improve a result.

When the independent manifest survives, rebuild a new candidate from it using direct CSR construction or the explicit-sentinel conversion. Keep the damaged representation and its prior output as evidence; do not overwrite them with the repaired object and erase the explanation of the failure. Record the node mapping and conversion policy with the new artifact.

The relevant connection to data cleaning is preserving source meaning during recovery, not filling missing cells or deduplicating rows. This manifest is already clean. Recovery restores its declared edge population and reruns the bounded route, distance and storage checks.

Close HOLD only when the recovered source is authoritative for the intended graph. Then evaluate the new candidate on its own evidence. Historical success from the baseline does not substitute for checking that the reconstruction actually consumed the recovered source correctly.

Assign representation ownership and acceptance criteria

Assign ownership before the matrix leaves its producer. The data producer owns node identities, node order, directed edge identities, weights and the declared absence convention. Delivering only values transfers an ambiguity, not a complete graph contract. The graph maintainer owns the converter and the explicit directedness setting. The reviewer owns the comparison between authorized inputs, recorded storage and requested route evidence.

A useful acceptance record names the graph version or retained source, the A,B,C,D order, the four baseline edges, the float64 policy and the conversion path. It also names the expected distance vector and target route, plus the recorded environment and source digest. Each field answers a different question; a dependency version cannot stand in for source identity, and a source digest cannot certify semantic correctness.

ACCEPT_BOUNDED applies to S1 and S4 under those stated conditions. REPAIR_REPRESENTATION applies when an authorized edge is lost but trustworthy provenance permits reconstruction. REJECT_LOSSY_ROUNDTRIP rejects the tested export-and-consumption path. REJECT_UNSUPPORTED_MANIFEST keeps rejected inputs outside this fixture’s admitted domain. HOLD_MISSING_PROVENANCE prevents an ambiguous payload from becoming a guessed source of truth.

Reopen acceptance when node order, serialization, container type or the conversion convention changes. Reordering only an index mapping can attach the same numbers to different node names. Reusing an old report after such a change would compare identities from one graph with values from another. Treat a new boundary as a new candidate requiring evidence, even when its printed array looks unchanged.

Separate what is asserted from what is available for review. The driver asserts distances and the selected path, inventory stability for sparse graph calls, initial and final baseline equality, the four admission failures and nine main case entries. It records additional evidence, including predecessors and candidate inventories. Do not describe every recorded field as an independently enforced acceptance condition.

The fixture does not settle tied shortest-path selection. Its shortest A-to-D route is unique, and the Dijkstra notes on multiple solutions caution that outputs can vary when multiple valid solutions exist. Broader path-selection requirements would need their own declared contract, not an extrapolation from these literals.

For reviewers developing scientific Python foundations, the transferable skill is tracing meaning across representations. This exercise qualifies a narrow graph boundary; it makes no claim about performance, memory efficiency or every downstream consumer. Acceptance should remain as specific as the evidence that supports it.

Build scientific Python review foundations

The bounded result is clear: retain the authored A→B=0 edge through direct CSR construction or explicit infinity-sentinel conversion, then verify A,B,C,D at cost 3 against the manifest. Reject the tested lossy round trip. Hold recovery when only ambiguous zeros survive. A graph algorithm can return a valid answer while its caller has supplied the wrong graph.

The review habit is to keep authorization, representation and numerical evidence separate until they agree. Preserve the complete source and report, including controls whose successful assertions demonstrate a failure mode. Carry those habits into later changes without treating this small fixture as universal coverage.

For structured study of the foundations behind this work, explore Refonte Learning’s Data Science & AI programme. The programme page lists Python data science, statistical modelling and applied projects. Those are relevant foundations; this is not a claim that the programme teaches SciPy csgraph or this exact experiment.