{-# OPTIONS -Wall #-} module Main where import Data.List (sort, nub) import qualified Data.Map.Strict as M import LPFPCore ------------------------- -- Wave History method -- ------------------------- -- What is the meaning of timStep and posStep? -- According to the latex definitions in the notes, -- dt should be the time between nt = 0 and nt = 2. data WaveHistory = WaveHistory { timInitial :: R , timStep :: R , posInitial :: R , posStep :: R , velWave :: R , waveValue :: M.Map (Int,Int) R } deriving (Show) -- works for position too tim :: R -> R -> Int -> R tim t0 dt nt = t0 + dt / 2 * fromIntegral nt -- Wave equation with a source term. -- Assume boundaries are fixed at Ey = 0. -- Ey is on even nx, even nt -- Bz is on odd nx, odd nt -- We keep track of Ey from 2 .. 198 historyUpdateEy :: ((R,R) -> R) -> WaveHistory -> WaveHistory historyUpdateEy source wh = let dt = timStep wh dx = posStep wh t0 = timInitial wh x0 = posInitial wh -- x nx' = x0 + dx / 2 * fromIntegral nx' -- t nt' = t0 + dt / 2 * fromIntegral nt' stPoints = M.keys (waveValue wh) nt = maximum [nt' | (_,nt') <- stPoints] -- this should be odd nxMax = maximum [nx' | (nx',_) <- stPoints] -- this should be odd wv = waveValue wh f :: (Int,Int) -> R f (nx',nt') = case M.lookup (nx',nt') wv of Nothing -> error $ "bad lookup: " ++ show (nx',nt') Just val -> val -- The most important functions is newEy: newEy :: Int -> R -- newEy is for nt+1 newEy nx = f (nx,nt-1) - f (nx+1,nt) + f (nx-1,nt) - dt / epsilon0 * source (tim x0 dx nx, tim t0 dt nt) -- source = Jy wvNew = foldr (\nx -> M.insert (nx,nt+1) (newEy nx)) wv [2, 4 .. nxMax - 1] in wh { waveValue = wvNew } -- Wave equation with a source term. -- Assume boundaries are fixed at Ey = 0. -- Ey is on even nx, even nt -- Bz is on odd nx, odd nt -- We keep track from 1 .. 199 -- We actually keep track of c Bz, which has the same units as Ey. historyUpdateBz :: WaveHistory -> WaveHistory historyUpdateBz wh = let stPoints = M.keys (waveValue wh) nt = maximum [nt' | (_,nt') <- stPoints] -- this should be even nxMax = maximum [nx' | (nx',_) <- stPoints] -- this should be odd wv = waveValue wh f :: (Int,Int) -> R f (nx',nt') = case M.lookup (nx',nt') wv of Nothing -> if nx' == 0 || nx' == nxMax + 1 then 0 else error $ "bad lookup: " ++ show (nx',nt',nxMax,nt) Just val -> val -- The most important functions is newBz: newBz :: Int -> R -- newBz is for nt+1, which should be odd newBz nx = f (nx,nt-1) - f (nx+1,nt) + f (nx-1,nt) wvNew = foldr (\nx -> M.insert (nx,nt+1) (newBz nx)) wv [1, 3 .. nxMax] in wh { waveValue = wvNew } historyUpdate :: ((R,R) -> R) -> WaveHistory -> WaveHistory historyUpdate source = historyUpdateBz . historyUpdateEy source lineToText :: (R,R) -> String lineToText (x,y) = "L " ++ show x ++ "," ++ show (-y) ++ " " framePathText :: [(R,R)] -> String framePathText xys = 'M' : drop 1 (concatMap lineToText xys) oneCurve :: R -> R -> (Int,Int,Int) -> [(R,R)] -> String oneCurve t dt rgb xys = let fpt = framePathText xys pre = "" middle = " \n" post = "" in pre ++ middle ++ post makeSVGAnimation :: FilePath -> WaveHistory -> IO () makeSVGAnimation filename wh = let dt = timStep wh dx = posStep wh t0 = timInitial wh x0 = posInitial wh wv = waveValue wh nts = sort $ nub $ [nt | (_,nt) <- M.keys wv] keysTime :: Int -> [(Int,Int)] keysTime nt = sort $ filter (\(_,nt') -> nt' == nt) $ M.keys wv f :: (Int,Int) -> R f (nx',nt') = case M.lookup (nx',nt') wv of Nothing -> error $ "makeSVGAnimation: bad lookup: " ++ show (nx',nt') Just val -> val xyPairs :: Int -> [(R,R)] xyPairs nt = [(tim x0 dx nx', f (nx',nt')) | (nx',nt') <- keysTime nt] matterEy = concat [oneCurve (tim t0 dt nt) dt (0,0,255) (xyPairs nt) | nt <- nts, even nt] matterBz = concat [oneCurve (tim t0 dt nt) dt (255,0,0) (xyPairs nt) | nt <- nts, odd nt] svgStart = "\n" ++ "\n" ++ "\n" svgEnd = "\n" in writeFile filename $ svgStart ++ matterEy ++ matterBz ++ svgEnd makeSVGAnimation60 :: FilePath -> WaveHistory -> IO () makeSVGAnimation60 filename wh = let dt = timStep wh dx = posStep wh t0 = timInitial wh x0 = posInitial wh wv = waveValue wh nts = sort $ nub $ [nt | (_,nt) <- M.keys wv] keysTime :: Int -> [(Int,Int)] keysTime nt = sort $ filter (\(_,nt') -> nt' == nt) $ M.keys wv f :: (Int,Int) -> R f (nx',nt') = case M.lookup (nx',nt') wv of Nothing -> error $ "makeSVGAnimation: bad lookup: " ++ show (nx',nt') Just val -> val xyPairs :: Int -> [(R,R)] xyPairs nt = [(tim x0 dx nx', f (nx',nt')) | (nx',nt') <- keysTime nt] curvesEy = [oneCurve (tim t0 dt nt) dt (0,0,255) (xyPairs nt) | nt <- nts, even nt] curvesBz = [oneCurve (tim t0 dt nt) dt (255,0,0) (xyPairs nt) | nt <- nts, odd nt] ntMaxEven = maximum [nt | nt <- nts, even nt] ntMaxOdd = maximum [nt | nt <- nts, odd nt] matterEy = concat curvesEy ++ oneCurve (tim t0 dt ntMaxEven) 60 (0,0,255) (xyPairs ntMaxEven) matterBz = concat curvesBz ++ oneCurve (tim t0 dt ntMaxOdd ) 60 (255,0,0) (xyPairs ntMaxOdd ) svgStart = "\n" ++ "\n" ++ "\n" svgEnd = "\n" in writeFile filename $ svgStart ++ matterEy ++ matterBz ++ svgEnd -- x0 = 0, x1 = 200 * dt / 2 = 10 -- Every 0.1s of animation time, the wave proceeds one step in position (from nx = 100 -- to nx = 98, say. It should take 5 seconds to get to the boundary. -- That's about right. I want it to take 10 seconds. initialWaveZero :: R -> WaveHistory initialWaveZero dt = let dx = dt v = 1 in WaveHistory { timInitial = 0 , timStep = dt , posInitial = 0 , posStep = dx , velWave = v , waveValue = M.fromList $ [((nx,0),0) | nx <- [2, 4 .. 198]] ++ [((nx,1),0) | nx <- [1, 3 .. 199]] } -- Can depend on x and t. sourceTerm :: (R,R) -> R sourceTerm (x,t) = if x > 9.99 && x < 10.01 then 1e-10 * sin t else 0 initialWave2 :: R -> Int -> R -> WaveHistory initialWave2 dt nxmax boxLength = let dx = 2 * boxLength / fromIntegral nxmax v = dx / dt in WaveHistory { timInitial = 0 , timStep = dt , posInitial = 0 , posStep = dx , velWave = v , waveValue = M.fromList $ [((nx,0),0) | nx <- [2, 4 .. nxmax - 2]] ++ [((nx,1),0) | nx <- [1, 3 .. nxmax - 1]] } main08 :: IO () main08 = let wh = last $ take 150 $ iterate (historyUpdate sourceTerm) (initialWave2 0.1 600 20) in makeSVGAnimation60 "w08.svg" wh -- w08.svg is awesome. It freezes for a minute just as the wave hits the boundaries. -- If we were working in SI units, our source frequency would be 1 rad/s. -- f = 1/(2 pi) Hz, and T = 2 pi seconds. -- If 0.1s were a physical time, and 20m was a physical length, -- we'd have dx = 2 * 20 / 600 = 2/30 m -- and v = dx / dt = 2/3 m/s. -- This gives a wavelength of lambda = v T = 4 pi/3 meters. -- So, we get about 20 / (4 pi / 3) = 15 / pi wavelengths per box. -- A little less than 5 wavelengths per box. -- That seems correct. -- Standing wave -- initial Ey: -- Ey(x,t) = sin k x cos omega t -- Ey(x,t) = sin(k x - omega t) + sin(k x + omega t) -- omega = v k initialWaveStanding :: R -> Int -> R -> WaveHistory initialWaveStanding dt nxmax boxLength = let dx = 2 * boxLength / fromIntegral nxmax x0 = 0 v = dx / dt k = 2 * pi * 1.5 / boxLength ey x = sin (k * (x - x0)) omega = v * k bz (x,t) = 0.5 * sin(k * (x - x0) - omega * t) - 0.5 * sin(k * (x - x0) + omega * t) in WaveHistory { timInitial = 0 , timStep = dt , posInitial = x0 , posStep = dx , velWave = v , waveValue = M.fromList $ [((nx,0),ey (tim x0 dx nx)) | nx <- [2, 4 .. nxmax - 2]] ++ [((nx,1),bz (tim x0 dx nx,dt/2)) | nx <- [1, 3 .. nxmax - 1]] } main11 :: IO () main11 = let wh = last $ take 600 $ iterate (historyUpdate (const 0)) (initialWaveStanding 0.1 200 20) in makeSVGAnimation "w11.svg" wh main :: IO () main = main11 -- w11.svg is a nice example of an EM standing wave. Worth studying and talking about.