packages feed

exchangealgebra-0.5.0.0: src/ExchangeAlgebra/Optimize.hs

{-# LANGUAGE TypeFamilies #-}

{- |
    Module     : ExchangeAlgebra.Optimize
    Copyright  : (c) Kaya Akagi. 2026
    License    : OtherLicense

    A pluggable optimization solver interface.

    This module defines the strategy-agnostic 'Solver' class. Concrete
    strategies live in sibling modules:

    * "ExchangeAlgebra.Optimize.Annealing" — simulated annealing over an
      arbitrary candidate type.
    * "ExchangeAlgebra.Optimize.GA" — a real-coded genetic algorithm over
      numeric vector chromosomes.

    New strategies (differential evolution, particle swarm, CMA-ES, ...)
    are added by defining a new strategy type with a 'Solver' instance;
    the interface itself does not change.

    == Design notes

    * The objective is monadic (@'Candidate' strategy -> m Double@) so that
      it can run stateful simulations, e.g. the @ST s@ state spaces of
      "ExchangeAlgebra.Simulate". Solvers only require 'Monad', nothing
      stronger.
    * Randomness is threaded internally by each solver from a seed in its
      'Config' (a pure generator, sequentially updated), so no random-monad
      constraint leaks into the objective. Runs are reproducible given the
      same seed /provided the objective itself is deterministic/ (same
      state transitions, no external randomness, clocks or concurrency).
    * The returned 'Double' is the objective value observed when the
      returned candidate was evaluated, in the user's orientation.
      Solvers never re-evaluate a candidate they have already scored
      (re-evaluation would be a visible side effect for stateful
      objectives such as @ST s@ simulations).
    * Objective values must be finite: solvers reject @NaN@ and
      infinities with a call to 'error' as soon as they are observed,
      because non-finite energies silently break comparisons, sorting
      and best-candidate tracking. Configurations are also validated
      up front (fail-fast; invalid settings are never clamped).
    * This subsystem is a generic numeric layer: it does not touch the
      redundant-algebra core ("ExchangeAlgebra.Algebra",
      "ExchangeAlgebra.Journal") and imposes no Hat\/Not or non-negativity
      semantics on candidates. Objective values are plain 'Double's in the
      user's orientation (see 'Direction').
    * Parallel evaluation of candidates is deliberately left to the caller
      (e.g. run several 'optimize' calls with different seeds and pick the
      best); solvers themselves evaluate sequentially.
-}
module ExchangeAlgebra.Optimize
    ( -- * Solver interface
      Solver (..)
      -- * Optimization direction
    , Direction (..)
    , orient
    ) where

-- | Whether the objective is to be maximized or minimized.
--
--   Solvers convert objective values to an internal /minimization energy/
--   ('orient') and report the final score back in the user's orientation.
data Direction = Maximize | Minimize
    deriving (Show, Eq)

-- | Convert a score in the user's orientation to a minimization energy.
--
--   >>> orient Minimize 3.0
--   3.0
--
--   >>> orient Maximize 3.0
--   -3.0
orient :: Direction -> Double -> Double
orient Minimize = id
orient Maximize = negate
{-# INLINE orient #-}

-- | A pluggable optimization strategy.
--
--   @strategy@ is a dispatch value carrying no data of its own — a
--   /proxy/ such as @GA@ ("ExchangeAlgebra.Optimize.GA") or @Annealing@
--   ("ExchangeAlgebra.Optimize.Annealing"). The associated types fix what
--   a candidate solution looks like and what configuration the strategy
--   needs.
class Solver strategy where
    -- | Candidate solution type searched by this strategy.
    type Candidate strategy
    -- | Strategy-specific configuration (schedules, rates, seed,
    --   'Direction', ...).
    type Config strategy

    -- | Run the optimizer: given a configuration, a monadic objective and
    --   an initial candidate, return the best candidate found together
    --   with its objective value (in the user's orientation).
    optimize :: Monad m
             => strategy
             -> Config strategy
             -> (Candidate strategy -> m Double) -- ^ objective
             -> Candidate strategy               -- ^ initial candidate
             -> m (Candidate strategy, Double)