diff --git a/src/bssunfold/core/__init__.py b/src/bssunfold/core/__init__.py index dbe6fa9..8e795ea 100755 --- a/src/bssunfold/core/__init__.py +++ b/src/bssunfold/core/__init__.py @@ -131,7 +131,6 @@ from .unfold_maxed import solve_maxed, unfold_maxed from .unfold_mcmc import solve_bayesian_mcmc, unfold_mcmc from .unfold_mlem import solve_mlem, unfold_mlem -from .unfold_nnqp import solve_nnqp, unfold_nnqp from .unfold_mlem_bs import ( AUTO_BETA_RELATIVE_GRID, build_bspline_basis, @@ -157,6 +156,7 @@ solve_tikhonov_nnls, unfold_nnksvd, ) +from .unfold_nnqp import solve_nnqp, unfold_nnqp from .unfold_nsduaz import ( builtin_catalogue, select_catalogue_initial, diff --git a/src/bssunfold/core/_gnowee.py b/src/bssunfold/core/_gnowee.py index dc18646..1910d2b 100644 --- a/src/bssunfold/core/_gnowee.py +++ b/src/bssunfold/core/_gnowee.py @@ -36,9 +36,8 @@ from __future__ import annotations -import copy as cp from collections.abc import Callable -from dataclasses import dataclass, field +from dataclasses import dataclass from math import gamma, sqrt import numpy as np @@ -316,7 +315,8 @@ def initialize(self, num_samples: int, method: str | None = None) -> np.ndarray: elif method in ("lhc", "lhs"): try: from scipy.stats.qmc import LatinHypercube - sampler = LatinHypercube(d=n_dim, seed=int(self.rng.integers(0, 2**31 - 1))) + seed_int = int(self.rng.integers(0, 2**31 - 1)) + sampler = LatinHypercube(d=n_dim, seed=seed_int) unit = sampler.random(n=num_samples) except Exception: # Fallback: crude Latin-hypercube via permutation per dimension @@ -442,7 +442,8 @@ def mutate(self, pop: list[Parent]) -> list[np.ndarray]: perm1 = self.rng.permutation(n) perm2 = self.rng.permutation(n) r = float(self.rng.random()) - k = self.rng.random((n, dim)) > (self.s.frac_mutation * float(self.rng.random())) + k = self.rng.random((n, dim)) > ( + self.s.frac_mutation * float(self.rng.random())) diff = pop_arr[perm1] - pop_arr[perm2] step = r * diff children = pop_arr + step * k @@ -456,7 +457,8 @@ def population_update(self, parents: list[Parent], children: list[np.ndarray], timeline: list[Event] | None = None, adopted_parents: list[int] | None = None, mh_frac: float = 0.0, - random_parents: bool = False) -> tuple[list[Parent], int, list[Event] | None]: + random_parents: bool = False, + ) -> tuple[list[Parent], int, list[Event] | None]: """Evaluate children and replace worse parents (port of ``population_update``). Keeps the parents sorted ascending by fitness. Implements: @@ -474,7 +476,6 @@ def population_update(self, parents: list[Parent], children: list[np.ndarray], replace = 0 feval = 0 - worst = max((p.fitness for p in parents), default=self.s.penalty) for i, child_vars in enumerate(children): fnew = float(self.objective(np.asarray(child_vars, dtype=float))) if fnew > self.s.penalty: @@ -551,7 +552,8 @@ def run_gnowee(lb: np.ndarray, ub: np.ndarray, settings: GnoweeSettings | None = None, rng: np.random.Generator | None = None, seed_solution: np.ndarray | None = None, - extra_starting: np.ndarray | None = None) -> tuple[np.ndarray, float, list[Event]]: + extra_starting: np.ndarray | None = None, + ) -> tuple[np.ndarray, float, list[Event]]: """Run the Gnowee optimizer on a continuous problem. Parameters @@ -606,8 +608,6 @@ def run_gnowee(lb: np.ndarray, ub: np.ndarray, timeline.append(Event(0, len(pop), pop[0].fitness, np.array(pop[0].variables, dtype=float))) - fe = gh.s.frac_elite - fl = gh.s.frac_levy converge = False while not converge: # Gnowee re-samples the elite/levy fractions each generation for MI @@ -669,7 +669,8 @@ def run_gnowee(lb: np.ndarray, ub: np.ndarray, converge = True if settings.verbose: print("Gnowee: fitness convergence (absolute).") - elif abs((pop[0].fitness - settings.optimum) / settings.optimum) <= settings.opt_conv_tol: + elif (abs((pop[0].fitness - settings.optimum) / settings.optimum) + <= settings.opt_conv_tol): converge = True if settings.verbose: print("Gnowee: fitness convergence (relative).") diff --git a/src/bssunfold/core/detector.py b/src/bssunfold/core/detector.py index c37772a..6276015 100755 --- a/src/bssunfold/core/detector.py +++ b/src/bssunfold/core/detector.py @@ -102,6 +102,7 @@ from .unfold_mystic import unfold_mystic as unfold_mystic_impl from .unfold_mystic import unfold_mystic_hybrid as unfold_mystic_hybrid_impl from .unfold_nnksvd import unfold_nnksvd as unfold_nnksvd_impl +from .unfold_nnqp import unfold_nnqp as unfold_nnqp_impl from .unfold_nsduaz import unfold_nsduaz as unfold_nsduaz_impl from .unfold_nspline import unfold_nspline as unfold_nspline_impl from .unfold_odl_advanced import ( @@ -116,7 +117,6 @@ from .unfold_pspline_reml import ( unfold_pspline_reml as unfold_pspline_reml_impl, ) -from .unfold_nnqp import unfold_nnqp as unfold_nnqp_impl from .unfold_qpmad import unfold_qpmad as unfold_qpmad_impl from .unfold_qpsolvers import unfold_qpsolvers as unfold_qpsolvers_impl from .unfold_qubo import unfold_qubo as unfold_qubo_impl diff --git a/src/bssunfold/core/unfold_gnowee.py b/src/bssunfold/core/unfold_gnowee.py index bbf811c..ed09019 100644 --- a/src/bssunfold/core/unfold_gnowee.py +++ b/src/bssunfold/core/unfold_gnowee.py @@ -69,7 +69,8 @@ def _build_seed(A: np.ndarray, b: np.ndarray, x0: np.ndarray | None) -> np.ndarr return seed -def _build_log_bounds(seed: np.ndarray, half_range: float) -> tuple[np.ndarray, np.ndarray]: +def _build_log_bounds(seed: np.ndarray, half_range: float, + ) -> tuple[np.ndarray, np.ndarray]: """Return ``(lb, ub)`` in log space centred on the seed.""" y0 = np.log(np.maximum(np.asarray(seed, dtype=float), 1e-300)) span = half_range * np.log(10.0) diff --git a/src/bssunfold/core/unfold_qpmad.py b/src/bssunfold/core/unfold_qpmad.py index bc2c90a..fb847e2 100644 --- a/src/bssunfold/core/unfold_qpmad.py +++ b/src/bssunfold/core/unfold_qpmad.py @@ -48,7 +48,7 @@ from typing import Any import numpy as np -from scipy.linalg import cho_factor, cho_solve, solve_triangular +from scipy.linalg import solve_triangular from ..logging_config import get_logger from ._base_unfolder import make_solve_wrapper, run_unfolding @@ -154,11 +154,15 @@ def solve_H(rhs: np.ndarray) -> np.ndarray: ub_arr = np.asarray(ub, dtype=float) for i in range(n): if np.isfinite(lb_arr[i]): - r = np.zeros(n); r[i] = 1.0 - rows_C.append(r); vals_d.append(float(lb_arr[i])) + r = np.zeros(n) + r[i] = 1.0 + rows_C.append(r) + vals_d.append(float(lb_arr[i])) if np.isfinite(ub_arr[i]): - r = np.zeros(n); r[i] = -1.0 - rows_C.append(r); vals_d.append(-float(ub_arr[i])) + r = np.zeros(n) + r[i] = -1.0 + rows_C.append(r) + vals_d.append(-float(ub_arr[i])) if A is not None and lb_A is not None and ub_A is not None: A_arr = np.asarray(A, dtype=float) @@ -396,7 +400,8 @@ def _solve_qp_qpmad_cpp( ) return np.asarray(x, dtype=float), "OK" if status == 0 else "INFEASIBLE" except Exception as e: - logger.warning(f"qpmad.solve call failed: {e}; falling back to python backend") + logger.warning( + f"qpmad.solve call failed: {e}; falling back to python backend") if hasattr(qpmad_module, "Solver"): # OOP form: solver = qpmad.Solver(); solver.solve(...) @@ -416,7 +421,9 @@ def _solve_qp_qpmad_cpp( return np.zeros(n), "INFEASIBLE" return np.asarray(x, dtype=float), "OK" if status == 0 else "INFEASIBLE" except Exception as e: - logger.warning(f"qpmad.Solver.solve call failed: {e}; falling back to python backend") + logger.warning( + f"qpmad.Solver.solve call failed: {e};" + " falling back to python backend") raise ImportError( "qpmad Python bindings found but no compatible solve API. " diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 3e0634e..07cd02a 100755 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -2546,7 +2546,8 @@ def _make_detector(self): @pytest.mark.parametrize( "method", - ["cvxpy", "qpsolvers", "mlem", "landweber", "genetic", "gnowee", "nnqp", "qpmad"], + ["cvxpy", "qpsolvers", "mlem", "landweber", "genetic", + "gnowee", "nnqp", "qpmad"], ) def test_spectrum_zero_above_emax(self, method): det = self._make_detector() diff --git a/tests/test_gnowee.py b/tests/test_gnowee.py index 5763d78..d547e7a 100644 --- a/tests/test_gnowee.py +++ b/tests/test_gnowee.py @@ -20,7 +20,6 @@ from bssunfold.core._gnowee import ( GnoweeHeuristics, GnoweeSettings, - Parent, levy, rejection_bounds, run_gnowee,