A sparse-matrix review can return the correct numerical answer and still fail its evidence contract. In the commissioning fixture for this article, the represented matrix has two mathematically nonzero coordinates. Calling count_nonzero() on the live CSR object returns 2, yet the object no longer contains the six stored slots that entered the review. Duplicate coordinates have been summed, the live storage now has five slots, and the original duplicate decomposition is gone from that object.
That outcome is not described here as numerical corruption. The dense 3-by-3 oracle remains unchanged. The narrower problem is observational: a procedure intended to inspect source storage changed the source storage it was supposed to preserve. For scientific-Python maintenance and numerical QA, that distinction determines whether a result is acceptable, repairable, or held for further qualification.
This playbook is explicitly scoped to the supplied CPython 3.13.5, SciPy 1.17.0 and NumPy 2.3.5 Linux run. It uses nine fresh, owned csr_matrix cases with finite float64 values, duplicate coordinates, explicit zeros and exact cancellation. The goal is not optimization or large-scale densification. It is to separate three different counts, capture before-and-after buffers, qualify counting on an independent copy, and keep optional normalization under a separate authorization decision. The acceptance target is evidence-preserving inspection, not a prettier sparse object.
Declare whether the review concerns values or stored slots
Start by naming the quantity being reviewed. CSR exposes several numbers that may coincide for a simple canonical matrix but diverge for raw storage. The versioned nnz reference defines nnz as the number of stored values, including explicit zeros. It answers a stored-representation question, not a byte-allocation measurement or a count of mathematically nonzero coordinates.
A second measurement, np.count_nonzero(a.data), examines the values currently present in the raw data buffer. NumPy's counting reference describes counting nonzero array values. Here it excludes stored zeros but counts both nonzero duplicate slots, even when they address one coordinate and cancel after summation. A third measurement, a.count_nonzero(), counts represented nonzero coordinates after duplicates are summed. The three measurements therefore need different labels.
On a matrix with no duplicates and no explicit zeros, all three measurements agree within this finite-value domain. That simpler input does not establish what happens on raw duplicate storage. CSR nnz versus count_nonzero is a comparison of different quantities, not competing estimates of one quantity. The fixture keeps those questions visibly separate.
Review question | Expression | Fixture answer |
How many slots are stored? | a.nnz | 6 |
How many stored data values are nonzero? | np.count_nonzero(a.data) | 4 |
How many represented coordinates are nonzero? | a.count_nonzero() | 2 |
The audit contract must also say what preservation means. Here it protects the ordered contents of the source data, indices and indptr arrays, as captured by storage(). The fixture compares Python-list values and order; it does not test buffer addresses, array-object identity, dtype identity or bitwise equality. Nor does it promise that a lazily computed metadata cache remains untouched. Reading has_canonical_format may populate such a cache without changing the protected slot contents. This is the same discipline as defining the meaning of a data transformation before applying it: representation policy must be explicit rather than inferred from a method name.
Read the CSR contracts without assuming a read-only method
The SciPy 1.17.0 CSR reference documents direct construction from (data, indices, indptr). In that representation, each row uses an indptr slice to select corresponding column indices and data values. The same page defines canonical CSR as having sorted column indices within each row and no duplicate entries. It does not say that canonical form excludes explicit zeros.
For counting semantics, the rolling count_nonzero reference says that duplicate entries are summed before nonzeros are counted and contrasts that result with stored-entry counts. The rolling sum_duplicates reference describes duplicate summation as an in-place operation. Together, those contracts should stop a reviewer from assuming that a method whose name sounds observational is necessarily storage-preserving.
Read-only behavior must be established, not guessed. Documentation may define the value returned without promising that the receiver's representation is untouched. For SciPy sparse storage validation, source inspection identifies the implementation path, while before-and-after snapshots establish what that path did to the particular object's recorded buffers.
Separate documented semantics from the observed implementation
Version identity matters. The rolling pages checked on October 15, 2026 identify SciPy 1.18.0, while the supplied execution is SciPy 1.17.0. Those pages provide semantic context, not a version-pinned test result. The SciPy 1.17.0 implementation and the installed method captured by the driver both call self.sum_duplicates() before np.count_nonzero(self.data) for axis=None. The live-storage mutation claim is tied to that implementation and the fixture evidence, not inferred from the rolling documentation alone.
This is not presented as a newly introduced feature, a regression date, or a promise about every earlier or later release. It is also not a universal assertion that all sparse counting methods mutate every input. It is one verified implementation path for the stated version and input. If the installed method, sparse type, axis behavior or storage contract differs, the correct decision is HOLD until the new behavior is measured. API names are useful hints, but a storage audit must rely on documented semantics, inspected implementation and before-and-after evidence.
Construct a literal cancellation and explicit-zero fixture
The fixture starts from raw compressed storage rather than a coordinate constructor that would combine duplicates during construction. Its arrays are deliberately small enough to inspect without abstraction:
data = [3., -3., 0., 4., 0., 5.]
indices = [0, 0, 1, 1, 0, 2]
indptr = [0, 3, 4, 6]
shape = (3, 3)Row 0 uses slots 0 through 2. The first two slots both target coordinate (0, 0), carrying 3 and -3, so their represented value is zero after duplicate summation. The third slot stores an explicit zero at (0, 1). Row 1 contains a single 4 at (1, 1). Row 2 contains an explicit zero at (2, 0) and a 5 at (2, 2).
Those six slots contain four individually nonzero data values: 3, -3, 4 and 5. After coordinate reconciliation, only 4 and 5 remain mathematically nonzero. The independent literal oracle is [[0., 0., 0.], [0., 4., 0.], [0., 0., 5.]]. Therefore the raw nnz is 6, the raw data-buffer nonzero count is 4, and the represented mathematical nonzero count is 2.
The three arrays also make row ownership auditable. The indptr values split the six slots into row lengths 3, 1 and 2; indices assigns columns inside those slices; data supplies the values. Reading the arrays together avoids treating a data value as meaningful without its row and column address. Because every call creates fresh NumPy arrays inline, the fixture evaluates the matrix's own live buffers rather than an unrelated external alias.
Use a tiny dense oracle without recommending densification at scale
The driver calls toarray() only because each ordinary fixture has nine coordinates and the literal oracle is independent of the sparse methods being qualified. That bounded comparison is not guidance to densify production-sized sparse data and makes no claim about memory or speed. The chosen values and their sums are exactly representable in float64, so this experiment does not introduce tolerance selection, nonfinite handling or inexact duplicate-summation analysis. The oracle verifies the same nine values in each case source a; storage evidence is examined separately. S9 adds a distinct three-coordinate zero control, whose checks are explained below.
Run the complete nine-case storage experiment
Save the complete source below as scipy_storage_fixture.py in a writable, isolated directory. Use CPython 3.13.5 with SciPy 1.17.0 and NumPy 2.3.5 to reproduce the recorded environment. Each case calls fixture() to obtain a fresh raw matrix, captures storage before its action, verifies the dense oracle afterward and records its own status. No case inherits a matrix canonicalized by an earlier case.
Copy the editable source into a plain-text file, preserving indentation and straight quotes. The supplied digest corresponds to UTF-8 text without a byte-order mark, LF line endings and one final newline. Calculate the digest from the exact saved bytes rather than assuming that a paste preserved them. Visual line wrapping is not a source-code newline. An editor that changes whitespace or line endings creates a different source artifact requiring its own digest, even when behavior is unchanged.
The report records the actual Python, SciPy and NumPy versions, platform string, UTC start and finish times, source SHA-256, installed count_nonzero source and all nine case rows. The supplied commissioning record reports PASS_9_CASES, beginning at 2026-10-15T06:47:35.921599+00:00 and finishing at 2026-10-15T06:47:35.926803+00:00. Its embedded driver digest is 514792f2d967bccbf7dde7b36f5b9477b2198248894a7602e1c7d98f6050e160. Those facts identify one local run, not a benchmark or cross-platform guarantee.
A rerun is successful only when the printed results.json exists, the top-level status is PASS_9_CASES, all nine named cases are present and marked PASS, the versions match the environment actually used, and the digest matches that rerun's source. An exception, absent file, shortened inventory or unexplained version difference is HOLD. The dated commissioning result is reported evidence; a reader's new execution must retain its own report. Source inspection or a syntax check alone is not a behavioral test.
The case inventory is intentionally diagnostic rather than uniformly acceptable. S1 records the untouched raw baseline. S2 exposes the live-count side effect. S3 moves that side effect to an owned copy. S4 and S5 isolate duplicate summation and zero elimination. S6 proves the wrong-order residue, S7 produces the reviewed two-slot form, S8 checks repetition, and S9 separates an all-zero value result from empty storage. This sequence makes each causal step observable.
Run: python scipy_storage_fixture.py
"""Tiny finite float64 CSR matrix; distinguish stored slots from matrix values."""
import hashlib
import inspect
import json
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
DENSE = [[0., 0., 0.], [0., 4., 0.], [0., 0., 5.]]
def require(ok, message):
if not ok:
raise RuntimeError(message)
def fixture():
return csr_matrix((np.array([3., -3., 0., 4., 0., 5.]),
np.array([0, 0, 1, 1, 0, 2]),
np.array([0, 3, 4, 6])), shape=(3, 3))
def storage(a):
return {'data': a.data.tolist(), 'indices': a.indices.tolist(),
'indptr': a.indptr.tolist(), 'nnz': int(a.nnz),
'canonical': bool(a.has_canonical_format)}
def same_values(a):
# Densification is bounded to nine fixture coordinates, not a production recipe.
require(np.array_equal(a.toarray(), np.array(DENSE)), 'Mathematical values changed')
def main():
root = Path(tempfile.mkdtemp(prefix='scipy-storage-', dir=Path(__file__).parent))
report = {'status': 'FAIL', 'python': platform.python_version(), 'scipy': scipy.__version__,
'numpy': np.__version__, 'platform': platform.platform(), 'cases': [],
'started_utc': datetime.now(timezone.utc).isoformat(),
'source_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
'installed_count_nonzero_source': inspect.getsource(csr_matrix.count_nonzero)}
def case(identity, action):
a = fixture()
before = storage(a)
row = {'id': identity, 'status': 'FAIL', 'before': before}
report['cases'].append(row)
action(a, row)
row['after'] = storage(a)
same_values(a)
row['status'] = 'PASS'
try:
def baseline(a, row):
row['stored_nonzero_values'] = int(np.count_nonzero(a.data))
require(a.nnz == 6 and row['stored_nonzero_values'] == 4, 'Wrong raw fixture')
same_values(a)
require(storage(a) == row['before'], 'Baseline observation changed storage')
case('S1', baseline)
def live_count(a, row):
row['count'] = int(a.count_nonzero())
require(row['count'] == 2 and a.nnz == 5, 'Pinned count/mutation baseline changed')
require(a.data.tolist() == [0., 0., 4., 0., 5.], 'Wrong canonicalized storage')
case('S2', live_count)
def copy_count(a, row):
owned = a.copy()
row['shared_buffers'] = [bool(np.shares_memory(getattr(a, key), getattr(owned, key)))
for key in ('data', 'indices', 'indptr')]
require(not any(row['shared_buffers']), 'Copy shares a buffer')
row['count'] = int(owned.count_nonzero())
row['counted_copy'] = storage(owned)
require(row['count'] == 2 and storage(a) == row['before'], 'Audit source changed')
case('S3', copy_count)
def sum_only(a, row):
a.sum_duplicates()
require(a.nnz == 5 and a.has_canonical_format, 'Duplicate normalization failed')
require(np.count_nonzero(a.data) == 2, 'Wrong value count after summation')
case('S4', sum_only)
def remove_only(a, row):
a.eliminate_zeros()
require(a.nnz == 4 and not a.has_canonical_format, 'Duplicates unexpectedly resolved')
require(a.data.tolist() == [3., -3., 4., 5.], 'Wrong explicit-zero removal')
case('S5', remove_only)
def wrong_order(a, row):
a.eliminate_zeros()
a.sum_duplicates()
require(a.nnz == 3 and a.data.tolist() == [0., 4., 5.], 'Cancellation control changed')
case('S6', wrong_order)
def normalize(a, row):
a.sum_duplicates()
a.eliminate_zeros()
require(a.nnz == 2 and a.data.tolist() == [4., 5.], 'Normalization failed')
require(a.has_canonical_format, 'Not canonical')
case('S7', normalize)
def repeated(a, row):
normalize(a, row)
once = storage(a)
normalize(a, row)
row['count'] = int(a.count_nonzero())
require(row['count'] == 2 and storage(a) == once, 'Idempotence failed')
case('S8', repeated)
def zeros(a, row):
z = csr_matrix((np.array([0., 0.]), [0, 2], [0, 2]), shape=(1, 3))
row['zero_before'] = storage(z)
row['count'] = int(z.count_nonzero())
require(row['count'] == 0 and z.nnz == 2, 'Zeros confused with empty storage')
z.eliminate_zeros()
row['zero_after'] = storage(z)
require(z.nnz == 0, 'Zero storage not removed')
require(storage(a) == row['before'], 'Unrelated source changed')
case('S9', zeros)
require(len(report['cases']) == 9, 'Incomplete cases')
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()
(root / 'results.json').write_text(json.dumps(report, indent=2) + '\n')
print(root / 'results.json')
if name == '__main__':
main()
After a successful run, compare every case's before and after objects, verify that case identifiers are unique and ordered S1 through S9, and retain the captured method with the version strings. A failing action may leave its after snapshot absent and later cases unexecuted. Preserve those gaps rather than filling them with expected values. A hand-edited status, a report from another environment, or a successful process exit without the complete ledger does not establish qualification.
The driver serializes source-buffer contents, nnz and canonical status, then checks the literal oracle after each action. Its finally block attempts to write results.json when the guarded case phase fails, and the exception is re-raised. This is not universal failure capture: imports, directory creation and initial source inspection occur before that try block, while an output failure can prevent the report from being written. Inspect both process failure and report completeness. A JSON file alone is not success, and no report is not a pass.
The source capture also prevents a documentation shortcut. Reviewers can compare the method actually imported by the recorded SciPy package with the behavior observed in the same report. If a future implementation no longer calls duplicate summation, that is not permission to reuse this article's mutation conclusion unchanged. It is a reason to rerun the nine cases, retain the new source digest and evaluate the results against the current source-preservation contract.
Observe a correct live count changing the source storage
Case S1 establishes the raw baseline without calling the matrix count. Before and after its observations, data is [3., -3., 0., 4., 0., 5.], indices is [0, 0, 1, 1, 0, 2], indptr is [0, 3, 4, 6], nnz is 6 and canonical format is false. It also records four nonzero values in the raw data buffer and confirms the dense oracle.
S2 starts from a new matrix with the same baseline and calls a.count_nonzero(). The returned count is 2, which is numerically correct. The expected after-state for the recorded implementation has data [0., 0., 4., 0., 5.], indices [0, 1, 1, 0, 2], indptr [0, 2, 3, 5], nnz 5 and canonical format true. The two slots at (0, 0) have become one zero-valued slot. Review all three recorded arrays; the PASS status alone does not list that transformation.
For a source-preserving inspection, S2 is REPAIR_OBSERVER. The count itself is not rejected. The procedure is rejected because it rewrites storage evidence that the review required to remain available in the live source object.
A robust comparison checks the arrays as an ordered tuple, not merely their lengths. The nnz change reveals that one slot disappeared, but it does not identify which coordinate changed or whether row boundaries moved. The updated indices and indptr show the exact compression result. Preserving all three before-and-after buffers also prevents a reviewer from accepting a count because the dense oracle matches while overlooking lost provenance in the sparse layout.
A changed representation is not necessarily a changed matrix value
Every dense coordinate still agrees with the literal oracle. No nonzero was invented, and the mathematical matrix was not corrupted. The failure is narrower and operational: observation consumed evidence about duplicate multiplicity and original slot boundaries. This differs from a different case where a valid-looking score is insufficient, where the concern is score interpretation. Here the numerical answer is valid, but the observer violates an independent preservation requirement. Keeping those verdicts separate prevents both overstatement and accidental acceptance.
Count an independent copy and prove buffer separation
S3 repairs the observer by assigning the potentially mutating operation to an owned temporary matrix: owned = a.copy(). A different Python object identity does not establish independence. The driver instead applies NumPy's memory-sharing check through np.shares_memory to the source and copy for data, indices and indptr. All three results must be false before counting begins.
The copy then receives owned.count_nonzero(). In the recorded SciPy 1.17.0 case, its six slots become five, as in S2. That change is acceptable because the temporary object is not the audit source. The count must be 2, and the original storage() result must equal its baseline element-for-element and in the same order. This is list-level content equality, not a bytewise buffer comparison. The copy may change while the recorded source contents remain intact.
This is the bounded acceptance path: ACCEPT_COUNT_ON_COPY. It proves the numerical answer and protects the three source buffers named in the contract. It does not assert that every sparse subclass, view-like object or future copy implementation has identical ownership behavior. Those cases are outside the laboratory. For the stated ordinary owned CPU csr_matrix, the explicit buffer checks are stronger evidence than id(a) != id(owned), because two objects can still expose arrays that alias the same underlying memory.
Create the copy before any operation that may canonicalize the source. A copy taken after S2 would own the already changed five-slot state and would not repair the lost evidence. Baseline capture, ownership checks and counting must form a controlled sequence, with no earlier normalization of the live matrix. This sequencing does not synchronize other writers or make the source immutable.
The copy is an ownership boundary, not an immutability claim. Code elsewhere could still mutate the source after this check. Reviewers should therefore capture the source buffers and perform the count within one controlled step, then retain the report that ties the unchanged source state to the accepted answer.
Keep duplicate summation and zero removal separate
Cases S4 and S5 isolate two operations that are often collapsed into the vague word normalization. S4 calls sum_duplicates() only. The duplicate pair at (0, 0) is replaced by one stored zero, so nnz falls from 6 to 5. The data becomes [0., 0., 4., 0., 5.], only two stored values are nonzero, and has_canonical_format is true because indices are sorted and duplicates are gone.
S5 starts again from raw storage and calls eliminate_zeros() only. The versioned eliminate_zeros reference identifies this as an in-place removal of zero entries. It removes the two zeros that already exist in the raw buffer, leaving [3., -3., 4., 5.] and four slots. The duplicate column-0 entries in row 0 remain, so canonical format is false. The represented value at that coordinate is still zero because 3 and -3 cancel when interpreted together.
Neither case produces the two-slot storage [4., 5.]. Each is evidence about one deliberate representation change. Whether either operation is allowed on a source artifact remains a policy question, so the operational label is NORMALIZE_ONLY_IF_AUTHORIZED, not automatic acceptance.
This separation also clarifies test assertions. S4 should assert duplicate removal and canonical status without expecting zero elimination. S5 should assert explicit-zero removal without expecting duplicate resolution. Combining those expectations into one generic 'clean' flag would make a failing operation hard to diagnose and could authorize a representation change whose exact mechanism was never reviewed.
Canonical does not mean free of explicit zeros
Canonical CSR, as used by the cited SciPy reference, requires sorted indices and no duplicate entries. It does not require every stored value to be nonzero. S4 is therefore canonical while retaining three explicit zeros. Conversely, S5 has no explicit zeros in data but is not canonical because a duplicate coordinate remains. A single flag, nnz value or data-buffer length cannot serve as a universal quality verdict. Reviewers must retain the actual arrays and state which structural property each operation was meant to establish.
Show why reversing normalization steps leaves a new zero
S6 and S7 use the same two in-place operations in opposite orders. In S6, eliminate_zeros() first removes the two zeros already present in raw storage, reducing the matrix to four slots: 3, -3, 4 and 5. sum_duplicates() then combines the first two values. Their cancellation creates a new stored zero, leaving [0., 4., 5.] in three slots.
S7 sums duplicates first. That step produces five slots containing [0., 0., 4., 0., 5.]. The subsequent zero elimination can now see every explicit zero, including the one created by cancellation, so the final storage is [4., 5.] with indices [1, 2], indptr [0, 0, 1, 2], nnz 2 and canonical format true.
Both paths preserve all nine dense values. The difference is representational and follows directly from the fact that duplicate summation can create a zero that did not exist as a single stored value before summation. This is why operation order must be part of the reviewed policy rather than an implementation detail left unstated.
At row level, the distinction is concrete. Raw row 0 contains two duplicate column-0 values and one zero at column 1. Removing zeros first leaves the duplicate pair; summation then emits one column-0 zero. Summing first emits zeros at columns 0 and 1, both removed by the later elimination. Consequently, indptr[1] is 1 in S6 and 0 in S7. The represented row remains all zeros. "Wrong order" here means wrong for the declared two-slot policy, not a mathematically invalid operation.
The article does not promote S7 as a general sparse-optimization recipe. It establishes only that, for this finite fixture, the order sum then eliminate yields the authored two-slot representation, while eliminate then sum leaves a zero slot. Replacing an input artifact with either result remains NORMALIZE_ONLY_IF_AUTHORIZED. No throughput, memory or production-scale claim follows from the case.
Check idempotence and an all-explicit-zero control
S8 applies the sum-then-eliminate pair to a fresh raw matrix, saves the resulting two-slot representation, then applies the pair again. It finally calls count_nonzero() and requires count 2 with storage equal to that first normalized snapshot. The assertion checks the combined second normalization and count against the saved state. It confirms stable data, indices, indptr, nnz and canonical status for this fixture, not idempotence of arbitrary cleaning pipelines.
S9 supplies a separate negative control: a 1-by-3 CSR matrix with two stored entries, both explicit zeros, at columns 0 and 2. Before elimination, its nnz is 2. count_nonzero() returns 0 because no represented coordinate is nonzero, yet the matrix still stores two slots. Only the explicit eliminate_zeros() call reduces nnz to 0.
That control blocks a common inference. A mathematical count of zero does not imply empty sparse storage. It also prevents reviewers from treating nnz == 0 as an interchangeable restatement of an all-zero matrix. One statement concerns representation; the other concerns values.
The controls also test scope discipline. S8 begins from raw storage rather than reusing S7, so its stable second pass cannot be explained by an earlier case's mutation. S9 creates z inside its action and then checks the unrelated source a. Together, those choices make accidental shared state visible. They also show that a count can answer a value question correctly while leaving a representation that still needs an explicit, separately authorized storage operation.
S9 also receives the ordinary three-row case source a, but performs its zero-only experiment on z. Its final assertion requires a to match its baseline, and the case wrapper checks the nine-coordinate oracle on a. The driver does not call same_values() on z or compare z against a separate dense oracle. Its zero-control evidence is the explicit input, count, nnz assertions and stored snapshots. Distinguish those checks rather than crediting S9 with a dense comparison it does not execute.
Reconcile numerical and representation evidence independently
An acceptance ledger should carry both axes of evidence. Numerical agreement alone cannot prove that every required representation property survived, just as identity checks beyond numerical agreement protect a different data contract. The following matrix summarizes the authored expectations for the recorded implementation. Its method-count column reports a count_nonzero() call only where the driver makes one; "not called" does not mean the mathematical count is unknown.
Case | Operation | Stored slots | Method count | Literal oracle | Decision |
S1 | Baseline observations | a: 6 → 6 | Not called | a: match | HOLD until observer selected |
S2 | Count live matrix | a: 6 → 5 | 2 | a: match | REPAIR_OBSERVER |
S3 | Count copy; no shared buffers | a: 6 → 6; copy: 6 → 5 | 2 (copy) | a: match; copy not dense-checked | ACCEPT_COUNT_ON_COPY |
S4 | Sum duplicates only | a: 6 → 5 | Not called | a: match | NORMALIZE_ONLY_IF_AUTHORIZED |
S5 | Remove existing zeros only | a: 6 → 4 | Not called | a: match | NORMALIZE_ONLY_IF_AUTHORIZED |
S6 | Eliminate, then sum | a: 6 → 3 | Not called | a: match | HOLD for two-slot policy |
S7 | Sum, then eliminate | a: 6 → 2 | Not called | a: match | NORMALIZE_ONLY_IF_AUTHORIZED |
S8 | Repeat pair, then count | a: 6 → 2; stable | 2 | a: match | NORMALIZE_ONLY_IF_AUTHORIZED |
S9 | All-explicit-zero control | a: 6 → 6; z: 2 → 2 after count, then 0 | 0 (z) | a: match; z not dense-checked | HOLD without zero-removal policy |
The literal-oracle column refers to the case source a. The S3 copy and S9 z have their own count and storage evidence, but neither receives an additional dense-oracle comparison in this driver. No single column substitutes for the others.
The S3 row also depends on three false shares_memory results, one for each source buffer. That ownership evidence is not visible in nnz or the dense oracle, so it belongs in the report even though the table summarizes it compactly.
Preserve the complete case inventory and its negative controls
PASS_9_CASES means the observed outcomes matched the driver's authored expectations. It does not mean every action is suitable for replacing the source. S2 intentionally passes its diagnostic assertions while receiving REPAIR_OBSERVER under the audit contract. S6 passes because it demonstrates the wrong-order consequence. S9 passes because it preserves the difference between zero mathematical count and zero stored slots. Removing these cases, pre-normalizing the fixture or reporting only the accepted path would erase the evidence that makes the decision bounded. Missing cases, changed states or an incomplete report are HOLD.
Authorize normalization before replacing an input artifact
Counting on a copy is an observation repair. Publishing the normalized copy as a replacement for the input is a different decision. The first action assigns a mutating implementation to temporary owned storage and discards that temporary after obtaining the count. The second action changes the representation delivered to another component, file, cache or reviewer.
Dense equality is necessary in this fixture but not sufficient to authorize replacement. Duplicate multiplicity, original slot order and explicit-zero positions can preserve evidence about an input or its downstream contract. The reviewed normalization removes duplicate multiplicity and explicit-zero positions from the retained representation; it does not establish that no consumer needed them. The distinction resembles encoded equality versus original meaning in another domain: equal visible values do not make all source representations interchangeable.
A reviewed normalization should therefore name the owner who approves the target representation, the exact sequence of operations, the input domain, the artifact retained for comparison and the downstream checks required after replacement. For this case, the sequence is sum_duplicates() followed by eliminate_zeros(). Its result is the two-slot matrix holding 4 and 5. The decision is NORMALIZE_ONLY_IF_AUTHORIZED, even though the dense oracle matches and the operation is idempotent in S8.
Keep either the original buffers or an independently retained source artifact. A normalized object cannot serve as its own proof of what duplicate slots and explicit zeros existed before normalization. Audit evidence should make the transformation reversible at the evidence level, even when the mathematical mapping itself is many-to-one and cannot be inverted from the result.
When replacement is authorized, give the normalized artifact a distinct identity and link it to the retained source and transformation report. That practice prevents a later consumer from mistaking the two-slot output for the originally received storage and allows reviewers to repeat the transformation instead of relying on an unexplained artifact.
Choose a bounded acceptance or repair decision
The decision follows from two independent requirements: obtain the represented nonzero count and preserve the source storage chosen for audit. A numerical pass cannot silently waive the storage requirement, and a storage change cannot by itself establish a numerical error.
Bounded acceptance ends at the tested object, versions, finite data domain and named buffer contents; it is not a library-wide certification. Apply the labels narrowly:
REPAIR_OBSERVER when counting the live matrix returns the required number but changes any protected source buffer, as in S2.
ACCEPT_COUNT_ON_COPY when the copy owns separate data, indices and indptr buffers, the copy returns the correct count, and the original serialization remains unchanged, as in S3.
NORMALIZE_ONLY_IF_AUTHORIZED when duplicate summation or zero removal is proposed as a retained representation change rather than a temporary observation detail.
HOLD when versions, source ownership, finite-value scope, structural policy, case inventory, installed implementation or recorded outcomes differ from the qualified evidence.
These labels do not generalize the result to sparse arrays, arbitrary sparse subclasses, GPU containers, nonfinite values, axis-specific counting, production-sized densification or other sparse formats. They also do not decide whether duplicate accumulation is numerically appropriate for a different application. Such changes alter the problem and require their own fixture and evidence.
A stored zero policy belongs to the data owner
The numerical maintainer can prove that an explicit-zero slot exists, that removing it changes storage, and that dense values remain equal in this exact case. The maintainer cannot infer that every stored zero is useless. Authorization should come from the owner of the data or interface contract, with a statement of whether explicit zeros, duplicate slots or ordering carry meaning. The review record should identify the retained original, the approved output representation and any downstream validation still required. Without that policy, the safe decision is HOLD, not silent cleanup.
Preserve provenance when inspection has already changed storage
Once S2 has canonicalized the live matrix, copying that matrix is too late. The copy can preserve only the five-slot state that now exists. It cannot reconstruct whether the zero at (0, 0) came from 3 plus -3, 8 plus -8, several duplicate terms, or a single explicit zero. Many raw representations map to the same canonical value, so reverse engineering the original buffers would be guesswork.
Recover source evidence from an independent original: a retained file, serialized buffers, immutable upstream record or pre-operation copy whose ownership was established before counting. Keep the Python, SciPy and NumPy versions, platform, case identity, UTC timestamps and source digest. Also record shape and the actual dtypes of data, indices and indptr in the surrounding review record: the embedded JSON does not include explicit shape or dtype fields. The commissioning driver digest is 514792f2d967bccbf7dde7b36f5b9477b2198248894a7602e1c7d98f6050e160. A changed driver requires its own digest and report.
If no independent original survives, document that limitation directly. The current mathematical matrix may still be usable, but the prior duplicate decomposition and slot order are no longer verifiable. Do not manufacture a plausible raw form and label it recovered evidence. The appropriate provenance decision is HOLD for claims about the lost representation.
Rerun qualification when the installed observer implementation changes or when the representation contract changes, even if the expected mathematical count remains 2. Reviewers building broader skills around evidence, arrays and reproducible analysis can use numerical Python foundations as context, but the decisive practice here is concrete: preserve the subject before invoking an observer known to canonicalize it in the tested version.
Strengthen numerical review through applied data-science practice
This playbook combines Python inspection, numerical reasoning and reproducible evidence rather than treating one return value as the whole result. Refonte Learning's Data Science & AI programme page lists Python data science, statistical modelling, exploratory analysis, predictive modelling and application to industry projects. It states a three-month format with 12–14 hours per week and describes a Training Certificate and a Certificate of Internship on successful completion, plus a potential internship.
A useful applied-project habit is to attach a minimal fixture, an independent oracle, explicit acceptance labels and a machine-readable result ledger. That pattern makes numerical review reproducible without pretending that one successful output answers every structural question.
Those foundations can support disciplined project work in which numerical meaning, representation and provenance are reviewed together. The programme link is a learning pathway, not a claim that this exact SciPy storage laboratory or package version is part of its curriculum, and it does not imply a guaranteed internship or employment outcome.
