packages feed

hanalyze-0.1.0.0: demo/io/PotentialGen.hs

-- | 半導体イオン注入後の **静電ポテンシャル** プロファイル風ダミーデータ。
-- git 管理外 (確認用)。
--
-- 物理直観 (簡易) — Dose 依存をサブ線形に変更:
--
--   V(z; E, D) = surface(z) - implant(z; E, D)
--
--     surface(z)        = +3.5 · exp(-z / L_surf)               [V]    表面 BC
--     implant(z; E, D)  = K · (D/D_ref)^α · exp(-(z-Rp)²/(2σ²)) [V]    注入井戸
--
--     Rp(E)  = 1.5 · E^0.7  [nm]    投影飛程 (B in Si 近似)
--     σ(E)   = 0.4 · Rp     [nm]
--     L_surf = 30           [nm]
--     K      = 8.0          [V]   D=D_ref で井戸深さ ≒ 8 V
--     D_ref  = 10           [/1e12 cm⁻²]   reference dose (base)
--     α      = 0.26              D 2 倍で y 変化 ~1.20 倍 (= 20% 増)
--
-- Dose 範囲 (ユーザー要望):
--   base = 10 (= 1e13 cm⁻²) を中心に ±20%、5 levels: {8, 9, 10, 11, 12}
--
-- 条件数: E 固定 × 5 doses = 5。各条件 100 z 点 (0..200 nm) 欠損なし。
-- 出力 2 種:
--   data/io/potential_long.csv  (long: name,energy,dose,z,y) — 既存互換
--   data/io/potential_wide.csv  (wide: dose, y_z001, ..., y_z100) — 多出力用
module Main where

import Text.Printf (printf)
import System.Random.MWC (createSystemRandom, GenIO, uniformR)
import qualified System.Random.MWC.Distributions as MWCD
import Data.List (sort, intercalate)
import System.Environment (getArgs)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import System.IO.Unsafe (unsafePerformIO)

unwordsBy :: String -> [String] -> String
unwordsBy = intercalate

-- | E は固定 (中央値)。
fixedEnergy :: Double
fixedEnergy = 100  -- keV

energies :: [Double]
energies = [fixedEnergy]

-- | Dose は 1e12 cm⁻² で正規化された値。base=10 (= 1e13)、6.0..14.0 を 21 水準。
doses :: [Double]
doses = [ 6.0 + 0.4 * fromIntegral i | i <- [0 .. 20 :: Int] ]

zRange :: (Double, Double)
zRange = (0, 200)

zPoints :: Int
zPoints = 100

-- | jagged モードで欠損率と jitter 倍率を上げる。
{-# NOINLINE jaggedRef #-}
jaggedRef :: IORef Bool
jaggedRef = unsafePerformIO (newIORef False)

isJagged :: Bool
isJagged = unsafePerformIO (readIORef jaggedRef)

missRate :: Double
missRate = if isJagged then 0.20 else 0.0

jitterFactor :: Double
jitterFactor = if isJagged then 2.5 else 0.3

-- 物理係数
projectedRange :: Double -> Double
projectedRange e = 1.5 * (e ** 0.7)

straggle :: Double -> Double
straggle e = 0.4 * projectedRange e

surfaceL :: Double
surfaceL = 30.0

-- D=D_ref でほぼ井戸深さ 8 V になるよう K を設定
implantK :: Double
implantK = 8.0

-- reference dose (base)。±20% 範囲の中心。
doseRef :: Double
doseRef = 10.0

-- Dose 依存指数: D が 2 倍 → 振幅が 2^α 倍。α=0.26 なら 1.20 倍。
doseAlpha :: Double
doseAlpha = 0.26

surfaceV :: Double -> Double
surfaceV z = 3.5 * exp (negate z / surfaceL)

implantWell :: Double -> Double -> Double -> Double
implantWell e d z =
  let rp = projectedRange e
      sg = straggle e
      amp = implantK * ((d / doseRef) ** doseAlpha)
  in amp * exp (negate ((z - rp) ** 2) / (2 * sg * sg))

potentialAt :: Double -> Double -> Double -> Double
potentialAt e d z = surfaceV z - implantWell e d z

condName :: Int -> Double -> Double -> String
condName i e d = printf "c%02d_E%g_D%g" i e d

-- | 共通固定 z grid (wide-form を作るため jitter なし)。
zGrid :: [Double]
zGrid =
  let (zlo, zhi) = zRange
      step = (zhi - zlo) / fromIntegral (zPoints - 1)
  in [ zlo + fromIntegral i * step | i <- [0 .. zPoints - 1] ]

main :: IO ()
main = do
  args <- getArgs
  let jagged = "--jagged" `elem` args || "jagged" `elem` args
  writeIORef jaggedRef jagged
  gen <- createSystemRandom
  let conds =
        [ (condName i e d, e, d)
        | (i, (e, d)) <- zip [1..] [(e, d) | e <- energies, d <- doses]
        ]
  -- wide-form 用: 各 dose で同じ z grid 上の y を観測 (ノイズ込)
  wideMatrix <- mapM (\(_, e, d) ->
                       mapM (\z -> do
                                eps <- MWCD.normal 0 0.1 gen
                                return (potentialAt e d z + eps))
                            zGrid)
                     conds
  let wideHeader =
        "dose," ++
        unwordsBy "," [ printf "y_z%03d" (i :: Int) | i <- [1 .. zPoints] ] ++
        "\n"
      wideBody =
        concat
          [ printf "%g,%s\n" d
              (unwordsBy "," [ printf "%.4f" v | v <- ys ])
          | ((_, _, d), ys) <- zip conds wideMatrix
          ]
      wideOut = "data/io/potential_wide.csv"
  writeFile wideOut (wideHeader ++ wideBody)
  putStrLn $ "Wrote " ++ wideOut ++ "  (" ++ show (length conds)
             ++ " rows × " ++ show (zPoints + 1) ++ " cols)"

  -- long-form (既存互換 / jagged モードでは別ファイル)
  rows <- mapM (genRows gen) conds
  let header = "name,energy,dose,z,y\n"
      body   = concat rows
      out    = if jagged then "data/io/potential_long_jagged.csv"
                         else "data/io/potential_long.csv"
      keptN  = length (lines body)
  writeFile out (header ++ body)
  putStrLn $ "Wrote " ++ out
  putStrLn $ "Conditions: " ++ show (length conds)
  putStrLn $ "z grid:     " ++ show zPoints ++ " points spanning "
             ++ show (fst zRange) ++ ".." ++ show (snd zRange) ++ " nm"
  putStrLn $ "Total cells: " ++ show (length conds * zPoints)
  putStrLn $ "After ~" ++ show (round (missRate * 100) :: Int)
             ++ "% drop:   " ++ show keptN ++ " rows"
  putStrLn ""
  putStrLn "Dose 依存の確認 (E=100 keV, base D=10 を中心):"
  mapM_ (\d -> do
           let zRp = projectedRange 100
               vWell = implantK * ((d / doseRef) ** doseAlpha)
               vAt   = potentialAt 100 d zRp
           printf "  D=%5.1f  振幅 = %5.2f V  V(Rp) = %+5.2f V\n"
                  d vWell vAt)
        doses
  printf "  → D 2 倍の比較: D=8 → D=16 想定で振幅比 = %.3f (= 2^%.2f)\n"
         ((16.0 / doseRef) ** doseAlpha
          / (8.0 / doseRef) ** doseAlpha)
         doseAlpha
  putStrLn ""
  putStrLn "条件サマリ:"
  mapM_ (\(lbl, e, d) -> do
           let zs = [0, 1 .. 200]
               vs = map (potentialAt e d) zs
               vmin = minimum vs
               vmax = maximum vs
           printf "  %-15s E=%5.0f  D=%5.1f  Rp=%6.1f  σ=%5.1f  V∈[%+5.2f,%+5.2f]\n"
             lbl e d (projectedRange e) (straggle e) vmin vmax)
        conds

genRows :: GenIO -> (String, Double, Double) -> IO String
genRows gen (label, e, d) = do
  let (zlo, zhi) = zRange
      baseStep = (zhi - zlo) / fromIntegral (zPoints - 1)
      jitter   = baseStep * jitterFactor
  zsRaw <- mapM (\i -> do
                    let zBase = zlo + fromIntegral i * baseStep
                    j <- uniformR (-jitter, jitter) gen
                    return (max zlo (min zhi (zBase + j))))
                [0 :: Int .. zPoints - 1]
  let zsSorted = sort zsRaw
  fmap concat $ mapM (mkRow gen label e d) zsSorted

mkRow :: GenIO -> String -> Double -> Double -> Double -> IO String
mkRow gen label e d z = do
  drop' <- uniformR (0, 1 :: Double) gen
  if drop' < missRate
    then return ""
    else do
      eps <- MWCD.normal 0 0.1 gen
      let v = potentialAt e d z + eps
      return (printf "%s,%g,%g,%.4f,%.4f\n" label e d z v)