Skip to content

fix(writePajekNet): directed Pajek output, tolerance filter, robust filtering & checks#2651

Merged
farid-zare merged 2 commits into
opencobra:developfrom
marouenbg:fix/writePajekNet
May 8, 2026
Merged

fix(writePajekNet): directed Pajek output, tolerance filter, robust filtering & checks#2651
farid-zare merged 2 commits into
opencobra:developfrom
marouenbg:fix/writePajekNet

Conversation

@marouenbg
Copy link
Copy Markdown
Contributor

Summary

Audit of writePajekNet surfaced five issues. This PR fixes all of them and rewrites the regression test to assert semantics rather than byte-equality with a frozen fixture.

Fixes

  1. Directed output (*Arcs instead of *Edges) — the function already encodes direction (forward/reverse based on flux sign), but the previous header marked the file as undirected, so downstream tools could discard the directionality.
  2. Tolerance-based active-reaction filtering — replace FBA.full(i) == 0 with abs(v(i)) < fluxTol. fluxTol defaults to the LP solver's feasTol (via getCobraSolverParams) and can be overridden via the new optional argument. Prevents tiny solver residuals (e.g. 1e-12) from being serialised as real arcs.
  3. Solver status check — validate FBA.stat == 1 before consuming FBA.full. On failure, raise writePajekNet:lpFailed instead of silently emitting a graph from an invalid solution or crashing on indexing.
  4. Robust biomass / objective exclusion — make the substring filter case-insensitive (Biomass_* no longer leaks through) and additionally exclude any reaction whose linear-objective coefficient (model.c) is non-zero.
  5. Test rewrite — the previous test only compared the produced .net byte-for-byte against a frozen fixture, so any of the bugs above could pass. The new test:
    • parses the file and asserts the *Vertices block matches model.mets,
    • requires a *Arcs header (directed) with a count matching the number of arc lines,
    • validates each arc has in-range vertex ids and a positive weight,
    • confirms biomass / objective fluxes do not appear as arc weights,
    • exercises tolerance filtering (large fluxTol ⇒ zero arcs),
    • exercises the LP-failure path (infeasible bounds ⇒ writePajekNet:lpFailed).
      The obsolete COBRAmodeltest.net fixture is removed.

Backward compatibility

  • Calling writePajekNet(model) still works; new arguments are optional.
  • The output file remains a valid Pajek .net; consumers that ignored the previous *Edges vs *Arcs distinction are unaffected, while consumers that respect it now get the correct semantics.
  • The default fluxTol matches the configured solver tolerance, so any reaction the solver itself considers active is still emitted.

Files

  • src/base/io/utilities/writePajekNet.m — fixes 1–4, plus optional fluxTol and fileName arguments and onCleanup-guarded fclose.
  • test/verifiedTests/base/testwritePajekNet/testwritePajekNet.m — semantic test (fix 5).
  • test/verifiedTests/base/testwritePajekNet/COBRAmodeltest.net — removed (obsolete fixture).

…iltering & checks

- Emit *Arcs (directed) instead of *Edges, matching the actual semantics of
  the encoded graph (forward/reverse based on flux sign).
- Replace exact-zero flux check with a configurable fluxTol (defaults to
  the LP solver feasTol via getCobraSolverParams) so solver residuals do
  not produce spurious arcs.
- Validate the FBA solution status before consuming FBA.full; raise
  writePajekNet:lpFailed on infeasible / unbounded / failed solves.
- Make biomass / objective filtering case-insensitive and also exclude
  any reaction with a non-zero linear objective (model.c).
- Use fprintf format specifiers, guarantee fclose via onCleanup, and
  expose an optional fileName argument.
- Rewrite testwritePajekNet to assert directed format, tolerance
  behaviour, biomass exclusion, and the LP-failure error path; remove
  the obsolete byte-equal fixture (COBRAmodeltest.net).
@github-actions
Copy link
Copy Markdown
Contributor

Summary:

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️
253 218 0 35

Failed Tests:

No failed tests ✨

Github Test Reporter by CTRF 💚

@farid-zare
Copy link
Copy Markdown
Collaborator

@codex

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5935dba7c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +80 to +82
for w = excludedFluxes(:)'
assert(~any(abs(arcWeights - w) < 1e-12), ...
'Excluded reaction with flux %g leaked into the graph', w);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate excluded reactions by identity, not flux value

This check assumes each excluded reaction has a unique flux magnitude, but in ecoli_core_model many non-excluded reactions share the same absolute flux as the biomass/objective reaction (for example, repeated 0.87392 values), so the test can fail even when writePajekNet correctly excludes those reactions. Because arc weights are not reaction identifiers, this assertion is fundamentally unstable across models/solvers and will produce false negatives; the test should compare expected reaction-derived arcs (or reaction membership) instead of raw weight equality.

Useful? React with 👍 / 👎.

Codex P1 on opencobra#2651: the previous exclusion check
asserted that no arc weight matched any excluded reaction's flux
magnitude. In ecoli_core_model many non-excluded reactions share the
biomass reaction's |v| (e.g. 0.87392), so the assertion is unstable
across models and solvers and can produce false negatives.

Replace it with a multiset comparison: replicate writePajekNet's
filtering (biomass / objective name match, model.c != 0, |v| < fluxTol,
demand/sink) to build the expected (src, dst, weight) arc list, parse
the file's actual arcs, sort both, and assert equality. This validates
exclusion by reaction identity and additionally catches missing arcs,
swapped src/dst, and wrong weights. Expected weights are round-tripped
through writePajekNet's '%g' format so the comparison is exact.

Also closes a small gap in the previous test, which only matched
'biomass' in reaction names; now matches 'objective' as well, aligning
with writePajekNet's own logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

Summary:

Tests 📝 Passed ✅ Failed ❌ Skipped ⏭️
257 222 0 35

Failed Tests:

No failed tests ✨

Github Test Reporter by CTRF 💚

@farid-zare farid-zare merged commit 2e2edd4 into opencobra:develop May 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants