packages feed

hanalyze-0.2.0.0: src/Hanalyze/Design/Custom/Constraint.hs

{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module      : Hanalyze.Design.Custom.Constraint
-- Description : Custom Design の Constraint 内部正規化形 (Coordinate Exchange 用の候補行フィルタ ADT)
-- Copyright   : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License     : BSD-3-Clause
--
-- Custom Design の Constraint 内部正規化形 (Phase 24-1 skeleton)。
--
-- spec: doe-custom-design-spec v0.1.1 §2.3 / §9.3。
--
-- **本 skeleton では「内部 ADT (正規化済形)」 のみを定義する**。
-- ∀LIC∃Code 表面構文 (= @RawConstraint@ newtype around @ExprRep@) と
-- @normalize :: RawConstraint -> Constraint@ は、 DSL frontend パッケージ
-- (現状 @フロントエンド app のバックエンド/@ にある) との依存関係を整理してから着手する
-- (= Phase 24 後続 commit 候補)。
--
-- 現在の利用想定: Coordinate Exchange アルゴリズム (本 Phase 後続 commit)
-- が ADT を inspect して候補 grid を事前 filter するための内部表現。
module Hanalyze.Design.Custom.Constraint
  ( ConstraintRel (..)
  , FactorValue (..)
  , ConstraintGuard (..)
  , Constraint (..)
  , checkRowAgainst
  , compileRowFromFactors
  ) where

import           Data.Text (Text)
import qualified Data.Map.Strict as M

-- | 線形制約の関係子 (Custom 側、 古典 Hanalyze.Design.Constraint と
-- 表現は同じだが名前空間を分けて使う)。
data ConstraintRel = CLeq | CEq | CGeq
  deriving (Eq, Show)

-- | カテゴリ / 数値の混在値 (Forbidden に使う)。
data FactorValue
  = FVDouble !Double
  | FVText   !Text
  deriving (Eq, Show)

-- | 条件付制約のガード (AND/OR/単項、 NOT は v0.2 検討)。
data ConstraintGuard
  = GuardEq  !Text !FactorValue
  | GuardLeq !Text !Double
  | GuardGeq !Text !Double
  | GuardAnd ![ConstraintGuard]
  | GuardOr  ![ConstraintGuard]
  deriving (Eq, Show)

-- | Custom Design 内部の正規化済 Constraint。
--
-- 連続因子 (因子名で参照) の半空間 / 等式 / カテゴリ列の forbidden /
-- 条件付 / 範囲上書きを覆う。 表面 ∀LIC∃Code Expr からの正規化失敗時の
-- @Generic@ (= ExprRep 抱え込み) は本 skeleton では未対応 (DSL frontend
-- 依存解決後に追加)。
data Constraint
  = LinearIneq  ![(Text, Double)] !ConstraintRel !Double
    -- ^ @sum_i (coef_i * x_{name_i}) `rel` rhs@ 連続因子のみ参照可
  | Forbidden   ![(Text, FactorValue)]
    -- ^ 全項が一致する row を禁止 (AND)
  | Conditional !ConstraintGuard ![Constraint]
    -- ^ ガード成立時のみ inner 制約を活性化
  | RangeBound  !Text !Double !Double
    -- ^ 範囲上書き (低、 高)
  deriving (Eq, Show)

-- | 1 row (= 因子名 → 値の Map) に対する制約評価。
-- skeleton では Categorical 因子は Text 値で照合、 連続因子は Double で照合。
-- 値が見つからない / 型不一致は **その制約を 'False' (= 違反) と判定**。
checkRowAgainst :: M.Map Text FactorValue -> Constraint -> Bool
checkRowAgainst row (LinearIneq coefs rel rhs) =
  let lookupNum k = case M.lookup k row of
                      Just (FVDouble x) -> Just x
                      _                 -> Nothing
      ms = traverse (\(n, c) -> fmap (c *) (lookupNum n)) coefs
  in case ms of
       Nothing -> False
       Just xs ->
         let lhs = sum xs
         in case rel of
              CLeq -> lhs <= rhs + 1e-9
              CEq  -> abs (lhs - rhs) <= 1e-9
              CGeq -> lhs >= rhs - 1e-9
checkRowAgainst row (Forbidden vs) =
  not (all (\(n, v) -> M.lookup n row == Just v) vs)
checkRowAgainst row (Conditional guard cs) =
  if evalGuard row guard
    then all (checkRowAgainst row) cs
    else True
checkRowAgainst row (RangeBound n lo hi) =
  case M.lookup n row of
    Just (FVDouble x) -> lo - 1e-9 <= x && x <= hi + 1e-9
    _                  -> False

-- | ガード評価。
evalGuard :: M.Map Text FactorValue -> ConstraintGuard -> Bool
evalGuard row (GuardEq  n v)   = M.lookup n row == Just v
evalGuard row (GuardLeq n c)   = case M.lookup n row of
                                   Just (FVDouble x) -> x <= c + 1e-9
                                   _                 -> False
evalGuard row (GuardGeq n c)   = case M.lookup n row of
                                   Just (FVDouble x) -> x >= c - 1e-9
                                   _                 -> False
evalGuard row (GuardAnd gs)    = all (evalGuard row) gs
evalGuard row (GuardOr  gs)    = any (evalGuard row) gs

-- | ヘルパ: 因子名リストと 1 row 値リスト (= Double のみの場合) から Map に変換。
-- Custom Design Core が coordinate exchange の inner loop で使う想定。
compileRowFromFactors :: [Text] -> [Double] -> M.Map Text FactorValue
compileRowFromFactors names values =
  M.fromList (zip names (map FVDouble values))