packages feed

hanalyze-design-0.2.0.1: 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 内部正規化形 (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 パッケージ
-- (現状 @CanvasApp/backend/@ にある) との依存関係を整理してから着手する
-- (未着手・後続コミットの候補)。
--
-- 現在の利用想定: Coordinate Exchange アルゴリズム (後続コミットで実装予定)
-- が ADT を inspect して候補 grid を事前 filter するための内部表現。
--
-- [English]: The internal normalized form of a Custom Design Constraint
-- (skeleton).
--
-- spec: doe-custom-design-spec v0.1.1 §2.3 / §9.3.
--
-- __This skeleton defines only the "internal ADT (normalized form)"__.
-- The ∀LIC∃Code surface syntax (= a @RawConstraint@ newtype around
-- @ExprRep@) and @normalize :: RawConstraint -> Constraint@ will be
-- tackled once the dependency on the DSL frontend package (currently in
-- @CanvasApp/backend/@) is sorted out (not yet started; a candidate
-- for a follow-up commit).
--
-- Intended current use: an internal representation the Coordinate
-- Exchange algorithm (to be implemented in a follow-up commit) can
-- inspect the ADT with, to pre-filter the candidate grid.
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 と
--   表現は同じだが名前空間を分けて使う)。
--   [English]: The relational operator for a linear constraint (on the
--   Custom side; the same representation as the classical
--   Hanalyze.Design.Constraint, but kept in a separate namespace).
data ConstraintRel = CLeq | CEq | CGeq
  deriving (Eq, Show)

-- | [日本語]: カテゴリ / 数値の混在値 (Forbidden に使う)。
--   [English]: A mixed category/numeric value (used by Forbidden).
data FactorValue
  = FVDouble !Double
  | FVText   !Text
  deriving (Eq, Show)

-- | [日本語]: 条件付制約のガード (AND/OR/単項、 NOT は v0.2 検討)。
--   [English]: The guard of a conditional constraint (AND/OR/unary; NOT is
--   under consideration for 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
--   依存解決後に追加)。
--   [English]: The normalized Constraint used internally by Custom Design.
--
--   Covers half-spaces / equalities on continuous factors (referenced by
--   name), forbidden combinations on category columns, conditionals, and
--   range overrides. The @Generic@ fallback (= carrying an ExprRep) for
--   when normalization from a surface ∀LIC∃Code Expr fails is not yet
--   supported by this skeleton (to be added once the DSL frontend
--   dependency is resolved).
data Constraint
  = LinearIneq  ![(Text, Double)] !ConstraintRel !Double
    -- ^ [日本語]: @sum_i (coef_i * x_{name_i}) \`rel\` rhs@ 連続因子のみ参照可
    --   [English]: @sum_i (coef_i * x_{name_i}) \`rel\` rhs@; may only
    --   reference continuous factors.
  | Forbidden   ![(Text, FactorValue)]
    -- ^ [日本語]: 全項が一致する row を禁止 (AND)
    --   [English]: Forbids a row where every term matches (AND).
  | Conditional !ConstraintGuard ![Constraint]
    -- ^ [日本語]: ガード成立時のみ inner 制約を活性化
    --   [English]: Activates the inner constraints only when the guard holds.
  | RangeBound  !Text !Double !Double
    -- ^ [日本語]: 範囲上書き (低、 高)
    --   [English]: A range override (low, high).
  deriving (Eq, Show)

-- | [日本語]: 1 row (= 因子名 → 値の Map) に対する制約評価。
--   skeleton では Categorical 因子は Text 値で照合、 連続因子は Double で照合。
--   値が見つからない / 型不一致は __その制約を 'False' (= 違反) と判定__。
--   [English]: Evaluates a constraint against a single row (= a Map from
--   factor name to value). In this skeleton, Categorical factors are
--   matched by Text value and continuous factors by Double. If the value
--   is missing or the type mismatches,
--   __the constraint is judged 'False' (= violated)__.
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

-- | [日本語]: ガード評価。 [English]: Evaluates a guard.
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 で使う想定。
--   [English]: Helper: converts a list of factor names and a single row's
--   values (the all-Double case) into a Map. Intended for use by Custom
--   Design Core in the inner loop of coordinate exchange.
compileRowFromFactors :: [Text] -> [Double] -> M.Map Text FactorValue
compileRowFromFactors names values =
  M.fromList (zip names (map FVDouble values))