packages feed

hanalyze-0.2.0.0: src/Hanalyze/Design/Mixture.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns #-}
-- |
-- Module      : Hanalyze.Design.Mixture
-- Description : 配合計画 (Mixture Design) — 成分比合計 = 1 制約下の Simplex Lattice / Centroid
-- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License     : BSD-3-Clause
--
-- 配合計画 (Mixture Design) — 成分比の合計が常に 1 となる制約下の DoE。
--
-- 材料 / 化学プロセス向け。 各実験点は @[x_1, ..., x_m]@ で
-- @x_i ≥ 0@、 @Σ x_i = 1@ を満たす。
--
-- 提供方式:
--
--   * 'SimplexLattice' @d@ — 各成分が @{0, 1/d, ..., d/d}@ から値を取り、 合計が
--     1 になる全組合せ。 点数 = @C(m+d−1, d)@
--   * 'SimplexCentroid' — 1 ≤ k ≤ m について、 任意 k 成分を均等に @1/k@、 他は 0。
--     点数 = @2^m − 1@
--
-- 制約付き Extreme Vertices design は将来 Phase で追加予定。
module Hanalyze.Design.Mixture
  ( MixtureDesignType (..)
  , MixtureResult (..)
  , mixtureDesign
  ) where

import qualified Numeric.LinearAlgebra as LA
import           Data.Text             (Text)
import qualified Data.Text             as T

-- ===========================================================================
-- 型
-- ===========================================================================

-- | Mixture design の種別。
data MixtureDesignType
  = SimplexLattice !Int  -- ^ 次数 d。 各成分は @{0, 1/d, ..., 1}@ のいずれかの値
  | SimplexCentroid      -- ^ 2^m - 1 点 (頂点 + 辺中点 + ... + 全体重心)
  deriving (Show, Eq)

-- | Mixture design の結果。
data MixtureResult = MixtureResult
  { mdMatrix      :: !(LA.Matrix Double)
    -- ^ @nRuns × m@ 行列。 各行の合計 = 1、 各要素 ∈ @[0, 1]@
  , mdNComponents :: !Int               -- ^ m (成分数)
  , mdNRuns       :: !Int               -- ^ 実験数
  , mdType        :: !MixtureDesignType -- ^ 入力の種別を保持
  } deriving (Show)

-- ===========================================================================
-- 公開関数
-- ===========================================================================

-- | Mixture design を生成。
--
-- 失敗条件:
--
--   * 成分数 m < 2 → 'Left'
--   * SimplexLattice の次数 d < 1 → 'Left'
mixtureDesign :: MixtureDesignType -> Int -> Either Text MixtureResult
mixtureDesign typ m
  | m < 2 = Left (T.pack ("mixtureDesign: need m >= 2 components, got m=" <> show m))
  | otherwise = case typ of
      SimplexLattice d
        | d < 1 -> Left (T.pack ("mixtureDesign SimplexLattice: need d >= 1, got d=" <> show d))
        | otherwise ->
            let pts = simplexLatticePoints m d
                mat = LA.fromLists pts
            in Right MixtureResult
                 { mdMatrix      = mat
                 , mdNComponents = m
                 , mdNRuns       = length pts
                 , mdType        = typ
                 }
      SimplexCentroid ->
        let pts = simplexCentroidPoints m
            mat = LA.fromLists pts
        in Right MixtureResult
             { mdMatrix      = mat
             , mdNComponents = m
             , mdNRuns       = length pts
             , mdType        = typ
             }

-- ===========================================================================
-- 内部: Simplex Lattice
-- ===========================================================================

-- | Simplex Lattice {m, d} の全点。
--
-- 非負整数 m-tuple (n_1, ..., n_m) で sum = d を満たす全組合せを列挙し、
-- 各点を (n_1/d, ..., n_m/d) に正規化。
simplexLatticePoints :: Int -> Int -> [[Double]]
simplexLatticePoints m d =
  let intTuples = compositions m d
      scale = 1 / fromIntegral d :: Double
  in [ map ((scale *) . fromIntegral) t | t <- intTuples ]

-- | 非負整数 m-tuple (n_1, ..., n_m) で sum = total を満たすもの全列挙。
compositions :: Int -> Int -> [[Int]]
compositions 0 0     = [[]]
compositions 0 _     = []
compositions m total =
  [ k : rest
  | k <- [0 .. total]
  , rest <- compositions (m - 1) (total - k)
  ]

-- ===========================================================================
-- 内部: Simplex Centroid
-- ===========================================================================

-- | Simplex Centroid (m components) の全点。
--
-- 1 ≤ k ≤ m について、 m 成分から任意の k 個を選び、 その k 成分だけ @1/k@、
-- 他は 0 とする点を作る。 合計 @2^m - 1@ 点。
simplexCentroidPoints :: Int -> [[Double]]
simplexCentroidPoints m =
  [ centroidPointFromSubset m subset
  | k <- [1 .. m]
  , subset <- choose [0 .. m - 1] k
  ]

-- | サブセット (= component の index list、 size = k) から centroid 点を構築。
--   各位置 i が subset に含まれていれば @1/k@、 含まれていなければ 0。
centroidPointFromSubset :: Int -> [Int] -> [Double]
centroidPointFromSubset m subset =
  let k    = length subset
      val  = 1 / fromIntegral k :: Double
  in [ if i `elem` subset then val else 0 | i <- [0 .. m - 1] ]

-- | 長さ k の組合せを全列挙。 順序は lexicographic。
choose :: [a] -> Int -> [[a]]
choose _      0 = [[]]
choose []     _ = []
choose (x:xs) k =
  map (x :) (choose xs (k - 1)) ++ choose xs k