packages feed

hanalyze-0.2.0.0: src/Hanalyze/Optim/Desirability.hs

-- |
-- Module      : Hanalyze.Optim.Desirability
-- Description : Desirability 関数 (Derringer & Suich 1980) による多目的スカラー化
-- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License     : BSD-3-Clause
--
-- Desirability functions (Derringer & Suich 1980).
--
-- A classical scalarization for multi-objective optimization. Each response
-- @y_j@ is mapped to a per-response desirability @d_j ∈ [0, 1]@, and the
-- overall desirability is the geometric mean:
--
-- @
-- D = (Π d_j)^(1/q)
-- @
--
-- The @x@ that maximizes @D@ is a point that satisfies all responses
-- reasonably well.
{-# LANGUAGE OverloadedStrings #-}
module Hanalyze.Optim.Desirability
  ( DesirabilityType (..)
  , individualDesirability
  , overallDesirability
  ) where

-- | The three desirability shapes.
data DesirabilityType
  = Maximize  Double Double          -- ^ Maximize: thresholds @low@ (→ 0) and @high@ (→ 1).
  | Minimize  Double Double          -- ^ Minimize: thresholds @high@ (→ 0) and @low@ (→ 1).
  | Target    Double Double Double   -- ^ Target value @t@ with allowed range @[low, high]@.
  deriving (Show, Eq)

-- | Compute the individual desirability @d_j(y)@.
individualDesirability :: DesirabilityType -> Double -> Double
individualDesirability dt y = case dt of
  Maximize lo hi
    | y <= lo   -> 0
    | y >= hi   -> 1
    | otherwise -> (y - lo) / (hi - lo)
  Minimize hi lo
    | y >= hi   -> 0
    | y <= lo   -> 1
    | otherwise -> (hi - y) / (hi - lo)
  Target t lo hi
    | y == t                  -> 1
    | y < lo || y > hi        -> 0
    | y < t                   -> (y - lo) / (t - lo)
    | otherwise               -> (hi - y) / (hi - t)

-- | Overall desirability @D = (Π d_j)^(1/q)@.
--
-- Any single zero collapses @D@ to zero — out-of-range responses are
-- strongly penalized.
overallDesirability :: [DesirabilityType] -> [Double] -> Double
overallDesirability dts ys
  | length dts /= length ys = 0
  | null ys                 = 0
  | otherwise =
      let ds = zipWith individualDesirability dts ys
          q  = fromIntegral (length ds) :: Double
      in if any (<= 0) ds then 0
           else (product ds) ** (1 / q)