Skip to content

Migrate QAOA to explicit objective-sense + canonical energy semantics - #58

Closed
fgfuchs with Copilot wants to merge 2 commits into
mainfrom
copilot/implement-objective-direction-migration
Closed

Migrate QAOA to explicit objective-sense + canonical energy semantics#58
fgfuchs with Copilot wants to merge 2 commits into
mainfrom
copilot/implement-objective-direction-migration

Conversation

Copilot AI commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

This change migrates the repository from implicit sign conventions to an explicit Problem-level objective direction model. QAOA internals now consistently minimize canonical energy, while preserving legacy APIs through deprecations and compatibility aliases.

  • Problem API: explicit objective direction (single source of truth)

    • Added ObjectiveSense (MINIMIZE / MAXIMIZE) in base_problem.py.
    • Standardized problem contracts around:
      • objective_sense
      • objective_value(bitstring) (natural mathematical objective)
      • energy(bitstring) (canonical minimized quantity)
    • Kept cost() as deprecated compatibility API with legacy score behavior (cost == -energy).
    • Added clear bound helpers: objective_bounds(), optimal_objective(), energy_bounds().
    • Kept computeMinMaxCosts() as deprecated wrapper.
  • Built-in problems: natural objectives + centralized sign conversion

    • Graph/MaxCut/Max-k-Cut families expose natural cut objective with MAXIMIZE.
    • QUBO exposes un-negated polynomial objective, defaults to MINIMIZE, supports explicit objective_sense, and applies sign only through energy mapping/circuit construction.
    • ExactCover and PortfolioOptimization now expose natural minimization objectives directly.
    • Brute-force selection paths updated to compare canonical energy (argmin semantics).
  • QAOA internals: energy-first optimization and result APIs

    • Loss/optimization path now minimizes lower-tail energy CVaR directly (removed manual negation patterns).
    • Best-solution selection, landscape warm-start, and statistics now use energy ordering.
    • Added explicit result accessors:
      • get_energy()
      • get_objective()
    • Kept get_Exp() as deprecated alias for compatibility.
    • Added energy-centric landscape fields (Energy_sampled_p1, MinEnergy_sampled_p1, MaxEnergy_sampled_p1) while retaining legacy aliases.
  • CVaR/statistics/utilities aligned to minimization semantics

    • Statistic.get_CVaR() now computes lower-tail CVaR.
    • flip.py, post.py, and plotting/post-processing paths were updated to evaluate/compare via energy or explicit objective APIs.
    • Approximation-ratio computation now supports both objective senses with explicit formulas.
  • Phase-separator validation and sign checks

    • Circuit validation now checks the canonical phase convention:
      • exp(-1j * t * energy(x))
    • QUBO validation separates:
      1. natural objective consistency checks
      2. circuit-to-energy sign validation
  • Serialization schema + metadata

    • Extended qaoaIO models with schema/versioned payload updates.
    • Persisted objective-sense/objective-vs-energy semantics in serialized result structures.
    • Round-trip loading preserves these semantics.
  • Docs + migration guidance

    • README updated with a dedicated “Objective direction and sign convention” section, equations, and API usage (objective_value vs energy).
    • Custom-problem guidance updated to require explicit objective sense and natural objective implementation.
from qaoa.problems.base_problem import ObjectiveSense

class MyProblem(Problem):
    def __init__(self):
        super().__init__(objective_sense=ObjectiveSense.MAXIMIZE)

    def objective_value(self, bitstring):
        return my_natural_objective(bitstring)

# Central invariant:
# energy(x) = objective_value(x)          if MINIMIZE
# energy(x) = -objective_value(x)         if MAXIMIZE
# legacy cost(x) == -energy(x)            (deprecated compatibility)

Copilot AI changed the title [WIP] Implement repository-wide objective direction migration for QAOA Migrate QAOA to explicit objective-sense + canonical energy semantics Jul 31, 2026
Copilot AI requested a review from fgfuchs July 31, 2026 11:38
@fgfuchs
fgfuchs requested a review from Copilot July 31, 2026 11:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates QAOA’s sign conventions to an explicit objective_sense model where QAOA always minimizes canonical energy(x), while exposing natural objective_value(x) and retaining legacy APIs (e.g., deprecated cost() / get_Exp() aliases).

Changes:

  • Introduces ObjectiveSense and standardizes objective_value/energy conversion at the Problem level.
  • Updates QAOA optimization/selection/statistics to be energy-first (including CVaR becoming lower-tail).
  • Extends serialization and docs to persist and explain objective-vs-energy semantics.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
unittests/test_statistic_utility.py Updates CVaR expectation to lower-tail semantics.
unittests/test_objective_sense_migration.py Adds comprehensive tests for objective/energy invariants, validation, and serialization schema versioning.
README.md Documents explicit objective direction and canonical energy sign convention; updates example outputs.
qaoa/utils/validation.py Updates exact phase-separator validation to check exp(-i * t * energy(x)).
qaoa/utils/statistic.py Changes CVaR implementation to lower-tail aggregation and guards small-sample rounding.
qaoa/utils/qaoaIO.py Adds objective_sense fields, best_energy/objective to results, and schema_version handling.
qaoa/utils/post.py Switches post-processing to record/aggregate energy CVaR directly.
qaoa/utils/plotroutines.py Moves approximation ratio computation to objective values and adds objective-sense-aware normalization.
qaoa/utils/flip.py Updates bit-flip “boosting” to reduce energy (minimization) rather than increase cost.
qaoa/qaoa.py Refactors optimization history/results and landscape sampling to be energy-centric; adds get_energy()/get_objective() with get_Exp() deprecated.
qaoa/problems/qubo_problem.py Makes QUBO expose natural objective; applies sign only through energy mapping/circuit construction; updates validation reporting.
qaoa/problems/portfolio_problem.py Switches portfolio problem to natural minimization objective and energy-based brute-force selection.
qaoa/problems/maxkcut_one_hot_problem.py Declares MAXIMIZE objective sense and renames cost to natural objective.
qaoa/problems/graph_problem.py Declares MAXIMIZE objective sense and renames cost to natural objective.
qaoa/problems/exactcover_problem.py Switches exact cover to natural minimization objective and energy-based brute-force selection.
qaoa/problems/base_problem.py Adds ObjectiveSense, objective_value/energy/bounds helpers, and deprecates legacy cost APIs.
qaoa/problems/init.py Re-exports ObjectiveSense from the problems package.
Suppressed comments (2)

qaoa/qaoa.py:391

  • get_objective() has the same depth handling issue as get_energy() (falsy depth and allowing depth == current_depth + 1), which can lead to unexpected list-return behavior or KeyError. Use depth is None and validate 1 <= depth <= current_depth.
        if not depth:
            ret = []
            for i in range(1, self.current_depth + 1):
                ret.append(self.optimization_results[i].get_best_objective())
            return ret

qaoa/qaoa.py:785

  • Same issue as in sample_cost_landscape(): MaxCost_sampled_p1/MinCost_sampled_p1 should be -MinEnergy_sampled_p1/-MaxEnergy_sampled_p1 to preserve the deprecated cost(x) == -energy(x) alias semantics.
            self.MaxCost_sampled_p1 = self.MinEnergy_sampled_p1
            self.MinCost_sampled_p1 = self.MaxEnergy_sampled_p1

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread qaoa/qaoa.py
Comment on lines +377 to +384
if not depth:
ret = []
for i in range(1, self.current_depth + 1):
ret.append(self.optimization_results[i].get_best_energy())
return ret
if depth > self.current_depth + 1:
raise ValueError
return self.optimization_results[depth].get_best_energy()
Comment thread qaoa/qaoa.py
Comment on lines +674 to +675
self.MaxCost_sampled_p1 = self.MinEnergy_sampled_p1
self.MinCost_sampled_p1 = self.MaxEnergy_sampled_p1
Comment on lines 201 to 205
for string in hist:
if qaoa_instance.problem.isFeasible(string):
cost = qaoa_instance.problem.cost(string)
cost = qaoa_instance.problem.objective_value(string)
counts += hist[string]
stat.add_sample(cost, hist[string], string)
Comment thread qaoa/utils/statistic.py
Comment on lines 137 to 140
if self.cvar < 1:
cvarK = int(np.round(self.cvar * len(self.all_values)))
cvar = np.sum(self.all_values[-cvarK:]) / cvarK
cvarK = max(1, int(np.round(self.cvar * len(self.all_values))))
cvar = np.sum(self.all_values[:cvarK]) / cvarK
return cvar
Comment on lines 206 to 210
Exact check that the problem's circuit represents the problem's cost function.
This tests checks that the unitary operator represented by the quantum circuit is
equal to the expected matrix with diagonal elements
exp(-j*t*cost(e)),
equal to the expected matrix with diagonal elements
exp(-j*t*energy(e)),
where e is the corresponding binary state, up to a global phase.
Comment thread qaoa/utils/qaoaIO.py
Comment on lines 91 to 95
class PortfolioOptimizationProblemData(ProblemData):
risk: float = 0.0
exp_returns: np.ndarray = None
exp_return: np.ndarray = None
cov_matrix: np.ndarray = None
budget: int = 0
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.

3 participants