Skip to content

Introduce solver backend interface#43

Open
bernalde wants to merge 5 commits into
feature/issue-36-qubo-ising-conversionfrom
feature/issue-37-backend-interface
Open

Introduce solver backend interface#43
bernalde wants to merge 5 commits into
feature/issue-36-qubo-ising-conversionfrom
feature/issue-37-backend-interface

Conversation

@bernalde

@bernalde bernalde commented May 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Introduce AbstractTenSolverBackend and DMRGBackend as the core backend boundary.
  • Route public minimize calls through backend-specific dispatch while keeping the current DMRG implementation as the default.
  • Route symbolic backends through _normalize_backend(::Val{...}) so extensions can register symbolic backend names without core edits.
  • Document _minimize as the backend extension hook for matrix and polynomial input forms.
  • Consolidate backend error handling while keeping :peps on the PEPS-extension-specific message.
  • Add tests for default DMRG selection, symbolic backend extension, generic unknown backend errors, and missing _minimize methods.
  • Merge the updated PR Add QUBO/Ising conversion utilities #42 base into this stacked branch.

This is stacked on PR #42 and targets feature/issue-36-qubo-ising-conversion.

Tests run

  • git diff --check - passed.
  • julia +1.12 --project=. -e 'using Pkg; Pkg.instantiate()' - passed.
  • julia +1.12 --project=. -e 'using Test, Random, LinearAlgebra, TenSolver; include("test/backend.jl")' - passed, 26 tests.
  • julia +1.12 --project=docs/ -e 'using Pkg; Pkg.instantiate()' - passed.
  • julia +1.12 --project=docs/ docs/make.jl - passed; deployment skipped outside CI.
  • julia +1.12 --project=. -e 'using Pkg; Pkg.test(; coverage=false)' - passed.

Notes

  • First focused test attempt failed before test execution because the environment was not instantiated; after Pkg.instantiate() the rerun passed.
  • No SpinGlassPEPS dependency is introduced.
  • QUBODrivers/JuMP backend attributes remain out of scope for this PR and are planned for issue [SpinGlassPEPS 5/6] Expose PEPS backend through QUBODrivers and JuMP attributes #39.
  • No Blocking comment was declined; the older symbolic-dispatch Blocking thread is addressed by the Val dispatch path.

Closes #37

@bernalde bernalde left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

GitHub rejected REQUEST_CHANGES because this authenticated account is the pull request author, so I am submitting this as COMMENT. Maintainer recommendation: do not merge as-is.

Review summary:

  1. Blocking issues: 1. The symbolic backend normalization is not extensible for the later optional backend = :peps path described by the docs.
  2. Nonblocking issues: none.
  3. Questions: none.
  4. Tests run and outcomes:
    • julia --project=. -e 'using Pkg; Pkg.instantiate()': passed.
    • Targeted test/backend.jl on Julia 1.11.5: passed, 13/13.
    • Custom backend object dispatch probe: passed.
    • Symbol-extension dispatch probe: confirmed value-specific symbol extension is not reachable.
    • git diff --check origin/feature/issue-36-qubo-ising-conversion...HEAD: passed.
    • Targeted test/backend.jl on Julia 1.10.11: passed, 13/13.
    • julia --project=. -e 'using Pkg; Pkg.test()': passed.
    • Documented docs setup command: passed.
    • julia --project=docs/ docs/make.jl local: passed.
    • GitHub CI checks inspected: all current checks are passing.
  5. Merge as-is: no.

I would not merge this until the blocking issue above is addressed.

Comment thread src/solver.jl Outdated

_normalize_backend(backend::DMRGBackend) = backend
_normalize_backend(backend::AbstractTenSolverBackend) = backend
function _normalize_backend(backend::Symbol)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Blocking: This catches every Symbol backend in one concrete method, so an optional package extension cannot later implement the documented backend = :peps path with normal Julia dispatch. minimize(...; backend = :peps) will always enter this method and throw before backend-specific _minimize methods can run, which makes the new backend interface much harder to extend without editing core TenSolver again or overwriting this method.

Use a value-dispatch hook instead, for example _normalize_backend(backend::Symbol) = _normalize_backend(Val(backend)), _normalize_backend(::Val{:dmrg}) = default_backend, and a fallback _normalize_backend(::Val{backend}) where backend = throw(_backend_error(backend)). Then a PEPS extension can add _normalize_backend(::Val{:peps}) = PEPSBackend(...). Please also add a regression test with a fake symbolic backend so this extension path stays covered.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in f4a3769. Symbol normalization now dispatches through Val(backend), :dmrg stays mapped to the default backend, and test/backend.jl covers a fake symbol-specific backend reaching its _minimize method.

bernalde added a commit that referenced this pull request May 16, 2026
@bernalde

Copy link
Copy Markdown
Member Author

Pushed commit f4a37690dc4aa951bb013c978d0d983e8321118d.

Main changes:

  • Changed symbolic backend normalization to use Val dispatch: _normalize_backend(backend::Symbol) = _normalize_backend(Val(backend)).
  • Kept :dmrg mapped to the default backend and kept unloaded symbols like :peps on the existing clear error path.
  • Added a regression test with a fake symbolic backend to prove an extension can register a symbol-specific normalization method and reach its backend-specific _minimize method.

Tests run:

  • julia +1.12 --project=. -e 'using Test, Random, LinearAlgebra, TenSolver; include("test/backend.jl")' - passed, 19 tests.
  • julia +1.12 --project=. -e 'using Pkg; Pkg.test()' - passed.
  • julia --project=docs/ docs/make.jl local - passed.
  • git diff --check - passed.

Comments intentionally not addressed: none.

Remaining risks/follow-up:

  • The extension hook is still an internal TenSolver method, but it now supports the planned package-extension path without another core edit.

@bernalde

Copy link
Copy Markdown
Member Author

Blocking: The PR introduces the backend boundary, but the committed symbol normalization currently hard-codes :dmrg and throws for every other symbol. That means a future optional extension cannot make backend = :peps work by adding a more specific method; core TenSolver would need another edit, which undercuts the extension path requested by issue #37 and described in the docs.

A concrete fix is to route symbols through Val, define the DMRG case in core, and leave the fallback on Val{backend} so extensions can add their own symbol mapping:

_normalize_backend(backend::Symbol) = _normalize_backend(Val(backend))
_normalize_backend(::Val{:dmrg}) = default_backend
function _normalize_backend(::Val{backend}) where {backend}
  throw(_backend_error(backend))
end

Then a later PEPS bridge can implement TenSolver._normalize_backend(::Val{:peps}) without modifying core. Please add a small regression test with a dummy symbol backend so this extension point stays protected.

@bernalde bernalde left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

GitHub rejected APPROVE because this authenticated account is the pull request author, so I am submitting this as COMMENT. Maintainer recommendation: merge-ready as-is.

Review summary:

  1. Blocking issues: none.
  2. Nonblocking issues: none.
  3. Questions: none.
  4. Tests run and outcomes:
    • julia --project=. -e 'using Pkg; Pkg.instantiate()': passed.
    • Targeted test/backend.jl on Julia 1.11.5: passed, 19/19.
    • Targeted test/backend.jl on Julia 1.10.11: passed, 19/19.
    • Module-scoped fake backend extension probe: passed.
    • git diff --check origin/feature/issue-36-qubo-ising-conversion...HEAD: passed.
    • julia --project=. -e 'using Pkg; Pkg.test()': passed.
    • Documented docs setup command: passed.
    • julia --project=docs/ docs/make.jl local: passed.
    • GitHub CI checks inspected: all current checks are passing.
  5. Merge as-is: yes.

The latest head uses Val dispatch for symbolic backend normalization and covers the extension path with a fake symbolic backend test. I found no actionable issues requiring inline comments.

@bernalde bernalde force-pushed the feature/issue-37-backend-interface branch from f4a3769 to 009bcd1 Compare May 17, 2026 01:26

@bernalde bernalde left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Senior maintainer review. Note: I am the PR author, so GitHub only permits a COMMENT event from me; a formal approval or change-request must come from another maintainer. No blocking issues found.

Diff verification: confirmed against the stacked base feature/issue-36-qubo-ising-conversion (not main). merge-base 03eb8d8; git diff <merge-base>..HEAD --stat = 6 files, +155 / -33, matching the PR's reported totals.

Issue intent (#37): introduce a minimal backend abstraction (AbstractTenSolverBackend, DMRGBackend, a default) and refactor minimize to delegate to a backend-specific _minimize while keeping DMRG default and not changing behavior. The PR delivers exactly this: the abstract type, DMRGBackend, const default_backend = DMRGBackend(), public minimize delegating via _minimize(_normalize_backend(backend), ...), plus Symbol (:dmrg) and object support with clear errors.

Verified, no behavior change: delegation forwards cutoff, kwargs... intact; _minimize(::DMRGBackend, ...) rebuilds obj/H exactly as before and calls the renamed _minimize_mpo (body byte-identical apart from the rename); maximize and the 2-arg minimize convenience forward backend transparently; the QUBODrivers sample path uses the default backend unchanged. No method ambiguity (Aqua passed); the typed _minimize(::DMRGBackend, ...) is strictly more specific than the args... fallback. default_backend is intentionally not exported, matching #37's guidance to avoid committing public names early. Behavioral equivalence confirmed: default vs backend=DMRGBackend() vs backend=:dmrg give identical energy and sample on a 4-var QUBO.

Nonblocking

  1. _backend_error (src/solver.jl:54) always says "Install/load the PEPS extension or use backend = :dmrg." A user passing an arbitrary symbol (e.g. :foo) or a custom AbstractTenSolverBackend subtype without a _minimize method gets PEPS-specific wording. Reserve the PEPS message for the :peps case and give a generic "no _minimize method for backend ..." message in the object/symbol fallbacks.

Question
2. The PR body says test/backend.jl has "13 tests"; my isolated run reports 19 Pass / 19 Total. Not a defect, but the body's count is inaccurate.

Tests run (Julia 1.10.11, clean worktree on the PR head):

  • julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.test()' — passed (Backend interface, ising_conversion, QUBODrivers.jl 130, Aqua.jl 11, VRP, HDF5, Doctests 1).
  • test/backend.jl in isolation: Backend interface 19/19.
  • Equivalence check on a 4-var QUBO: default / DMRGBackend() / :dmrg returned identical energy and sample.

Merge readiness: Ready to merge (pending normal human review). Faithful, minimal implementation of #37; delegation preserves all signatures, defaults, kwargs, dispatch, and QUBODrivers/maximize behavior; suite and doctests pass; no ambiguities. Only nonblocking polish: the reused PEPS-specific error wording, and the body's "13 tests" should read 19. Stacked on #42 (feature/issue-36) and must merge after it. The formal verdict must come from another maintainer.

Comment thread src/solver.jl Outdated

const default_backend = DMRGBackend()

_backend_error(backend) = ArgumentError("backend $(repr(backend)) is not available. Install/load the PEPS extension or use backend = :dmrg.")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nonblocking: this error always mentions the PEPS extension, but it also fires for an arbitrary unknown symbol (e.g. :foo) or a custom AbstractTenSolverBackend without a _minimize method (via the fallback at lines 61/216). Reserve the PEPS wording for the :peps case and give a generic "no _minimize method for backend ..." message otherwise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 0345560. src/solver.jl now reserves the PEPS-extension wording for :peps; unknown symbols and custom backends without _minimize get a generic no-_minimize message. Covered in test/backend.jl.

@bernalde bernalde left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Senior maintainer review of PR #43 ("Introduce solver backend interface"), stacked on feature/issue-36-qubo-ising-conversion (PR #42). I reviewed the diff against the PR's actual base (gh-computed), which reproduces the reported totals (+155/-33, 6 files). Findings are limited to code introduced by #43 itself; the QUBO/Ising lowering and _single_variable_minimize paths are inherited from the #41/#42 base chain and are out of scope here.

Process note: I am the PR author, so GitHub will not let me APPROVE/REQUEST_CHANGES my own PR. This is posted as a COMMENT; a formal verdict must come from another maintainer.

Assessment

The abstraction is coherent and minimal and matches issue #37's recommended shape: AbstractTenSolverBackend / DMRGBackend markers, a default_backend const, _normalize_backend to accept both symbols and backend objects, and a _minimize(backend, ...) dispatch layer with the unchanged DMRG body renamed to _minimize_mpo. The public API is preserved: minimize(Q[, l[, c]]), the minimize(Q, c) 2-arg overload, the polynomial overload, and maximize(qs...; kwargs...) all forward backend correctly (maximize forwards it through kwargs). The DMRG body is moved verbatim, so existing keywords (iterations, maxdim, mindim, noise, vtol, device, callbacks) keep working. Tests genuinely exercise the new boundary, including extension-style registration via _normalize_backend(::Val{...}) + _minimize(::CustomBackend, ...), default-equivalence, maximize forwarding, and both error paths. CI is green on all 9 Julia jobs + build + docs at the exact head SHA 009bcd1.

No blocking correctness issues found in #43's own changes. The blocking item below is a merge-readiness/process gate, not a code defect.

Blocking

  1. Cannot merge as-is: stacked-base dependency + staleness vs current main. This PR targets feature/issue-36-qubo-ising-conversion (PR #42), and that base chain (#41/#42) is not yet merged, so #43 cannot merge until its base merges (or is retargeted). Separately, the branch is cut from old main and is now stale: head Project.toml is version = "0.1.0" with QUBODrivers = "0.3", whereas current origin/main is 0.2.0 with QUBODrivers = "0.6.1" (#49/#53/#54 merged). git compare main...009bcd1 reports diverged: 6 ahead, 5 behind. The CI that passed ran against the stale base, so it does not validate post-rebase behavior. Concretely: rebase/merge the #41..#46 stack onto current main, resolve the version/QUBODrivers bump, and confirm this stack does not duplicate the already-merged single-variable QUBO fix (#48) that the inherited _single_variable_minimize path appears to overlap. Why it matters: merging would regress Project.toml version and the QUBODrivers compat bound and may re-introduce or conflict with already-merged fixes.

Nonblocking

  1. Closes #37 is accurate for scope. Issue #37 explicitly lists QUBODrivers backend attributes and a PEPS backend as out of scope, and the PR delivers the abstraction, default preservation, clear unknown-backend error, and extension room that #37's acceptance criteria require. No change requested; flagging only that #37 is part of a 6-PR epic and remains open until the rest of the stack lands.

  2. Redundant error surfaces in the backend-normalization layer. _normalize_backend(::Val{backend}) throws _backend_error(backend) and the generic _minimize(backend::AbstractTenSolverBackend, args...) fallback also throws _backend_error(backend), while the catch-all _normalize_backend(backend) throws a differently-worded "Unsupported backend ...". The two messages diverge for conceptually similar failures (unknown symbol vs unknown type). Consider consolidating on one message/builder so users see consistent guidance. Minor maintainability only.

Questions

  1. The generic _minimize(backend::AbstractTenSolverBackend, args...; kwargs...) fallback errors for any registered backend subtype lacking a concrete _minimize method. Is that the intended contract for a future PEPS extension that registers a backend type but only implements the matrix path (not the polynomial path)? If so, a brief note in the AbstractTenSolverBackend docstring stating that backends must implement _minimize for each input form they support (matrix and/or polynomial) would help extension authors; the current docstring says "backend-specific methods for the normalized optimization inputs they support" but does not name _minimize as the extension hook.

Summary

  1. Blocking: stacked-base dependency on unmerged #42 plus staleness vs current main (head 0.1.0/QUBODrivers 0.3 vs main 0.2.0/0.6.1; diverged 6 ahead/5 behind; possible overlap with merged #48). No blocking defects in #43's own code.
  2. Nonblocking: redundant/divergent error messages across _normalize_backend/_minimize fallbacks; Closes #37 is fine but #37 is one of a 6-PR epic.
  3. Questions: document _minimize as the per-input-form extension hook in the AbstractTenSolverBackend docstring.
  4. Tests run: none locally; the sandbox denied git fetch and julia (consistent with sibling reviews). Fell back to static review of the head sources (gh api .../contents/src/solver.jl?ref=009bcd1) plus CI verification against the exact head SHA 009bcd1 (all 9 Julia matrix jobs, build, and Documenter deploy: success). Note CI ran against the stale base and does not validate post-rebase behavior.
  5. Merge-readiness: Not mergeable yet. The code change for the backend interface itself is sound and well-tested, but the PR is blocked on (a) its unmerged base #42 and the rest of the #41..#46 stack, and (b) rebasing onto current main to resolve the version/QUBODrivers regression and confirm no duplication with merged fixes.

I would not merge this until the blocking issues above are addressed.

Comment thread src/solver.jl
throw(_backend_error(backend))
end
_normalize_backend(backend) = throw(ArgumentError("Unsupported backend $(repr(backend)). Use backend = :dmrg or backend = DMRGBackend()."))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nonblocking (maintainability): this catch-all throws "Unsupported backend ...", while _normalize_backend(::Val{backend}) above and the _minimize(::AbstractTenSolverBackend, args...) fallback both throw _backend_error ("backend X is not available..."). Two divergent messages for conceptually similar "unknown backend" failures. Consider consolidating on a single message builder.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 0345560. The unsupported-value path, unknown symbolic backend path, and missing-_minimize backend fallback now share _backend_error. Covered in test/backend.jl.

Comment thread src/solver.jl

"""
DMRGBackend()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Question (docs): consider naming _minimize here as the extension hook backends must implement, and noting it is per input form (matrix and/or polynomial). The generic _minimize(::AbstractTenSolverBackend, args...) fallback errors for any registered backend that omits a concrete method, so extension authors need to know which method(s) to define.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 0345560. The backend docstring now names _minimize as the extension hook for matrix and polynomial inputs, and notes the Val normalization hook for symbolic backends. Docs build passed.

@bernalde

Copy link
Copy Markdown
Member Author

Pushed commits:

Main changes:

  • Kept :peps on the PEPS-extension-specific error path.
  • Moved unknown symbolic backends, unsupported backend values, and custom backends without _minimize methods to a generic no-_minimize backend error.
  • Consolidated backend error construction through _backend_error.
  • Expanded the backend API docs to name _minimize as the extension hook for matrix and polynomial inputs.
  • Added backend tests for the PEPS-specific message, generic unknown backend messages, unsupported values, and a custom backend missing _minimize.

Tests run:

  • git diff --check - passed.
  • julia +1.12 --project=. -e 'using Pkg; Pkg.instantiate()' - passed.
  • julia +1.12 --project=. -e 'using Test, Random, LinearAlgebra, TenSolver; include("test/backend.jl")' - passed, 26 tests.
  • julia +1.12 --project=docs/ -e 'using Pkg; Pkg.instantiate()' - passed.
  • julia +1.12 --project=docs/ docs/make.jl - passed; deployment skipped outside CI.
  • julia +1.12 --project=. -e 'using Pkg; Pkg.test(; coverage=false)' - passed.

Comments intentionally not addressed:

  • None. No Blocking comment was declined; the older symbolic-dispatch Blocking feedback remains addressed by the existing Val dispatch path and fake symbolic backend regression test.

Remaining risks or follow-up:

  • GitHub CI is still pending for the pushed branch.

@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.05%. Comparing base (54e7cd0) to head (0345560).

Additional details and impacted files
@@                            Coverage Diff                             @@
##           feature/issue-36-qubo-ising-conversion      #43      +/-   ##
==========================================================================
- Coverage                                   97.89%   97.05%   -0.84%     
==========================================================================
  Files                                           5        5              
  Lines                                         237      272      +35     
==========================================================================
+ Hits                                          232      264      +32     
- Misses                                          5        8       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

1 participant