├── cabal.project ├── Setup.hs ├── ChangeLog.md ├── run-ghcid.sh ├── .gitignore ├── notes ├── fig │ ├── logo-text.pdf │ └── logo-text-dark.pdf ├── beamer-trek │ ├── Antonio-Bold.ttf │ ├── beamerthemetrek.sty │ ├── beamerfontthemetrek.sty │ ├── beamercolorthemetrek.sty │ ├── trek-shapes.sty │ ├── beamerinnerthemetrek.sty │ └── beamerouterthemetrek.sty ├── Makefile ├── intro-slides.tex └── references.bib ├── .ghci ├── stack.yaml ├── test └── Test.hs ├── gh-pages ├── index.html └── css │ └── style.css ├── LICENSE ├── src ├── Staging │ └── Types.hs ├── Orphans.hs ├── Todo.hs ├── Hohmann │ └── Types.hs ├── Units.hs ├── Solutions │ ├── Hohmann.hs │ ├── Staging.hs │ └── ODE.hs ├── Staging.hs ├── Hohmann.hs ├── LunarAscent │ ├── AGC.hs │ └── Types.hs ├── ODE.hs ├── Examples │ └── ODEExamples.hs ├── Plot.hs └── LunarAscent.hs ├── app └── tex-plots │ └── Main.hs ├── .travis.yml ├── space-workshop.cabal ├── README.md └── logo.svg /cabal.project: -------------------------------------------------------------------------------- 1 | with-compiler: ghc-8.6.4 2 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog for space-workshop 2 | 3 | ## Unreleased changes 4 | -------------------------------------------------------------------------------- /run-ghcid.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | stack exec ghcid -- -c 'stack ghci' 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | .DS_Store 4 | *.ps 5 | dist-newstyle/ 6 | .ghc.environment.* 7 | -------------------------------------------------------------------------------- /notes/fig/logo-text.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lancelet/space-workshop/HEAD/notes/fig/logo-text.pdf -------------------------------------------------------------------------------- /notes/fig/logo-text-dark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lancelet/space-workshop/HEAD/notes/fig/logo-text-dark.pdf -------------------------------------------------------------------------------- /notes/beamer-trek/Antonio-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lancelet/space-workshop/HEAD/notes/beamer-trek/Antonio-Bold.ttf -------------------------------------------------------------------------------- /notes/beamer-trek/beamerthemetrek.sty: -------------------------------------------------------------------------------- 1 | \useoutertheme{trek} 2 | \useinnertheme{trek} 3 | \usefonttheme{trek} 4 | \usecolortheme{trek} -------------------------------------------------------------------------------- /.ghci: -------------------------------------------------------------------------------- 1 | :set prompt "ghci> " 2 | :seti -Wno-missing-import-lists 3 | :seti -XDataKinds 4 | :seti -XFlexibleContexts 5 | :seti -XScopedTypeVariables 6 | :seti -XTypeFamilies 7 | :seti -XTypeOperators 8 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-16.2 2 | packages: 3 | - . 4 | extra-deps: 5 | - iterm-show-0.1.0.1 6 | - diagrams-pgf-1.4.1.1 7 | - posix-pty-0.2.1.1 8 | - texrunner-0.0.1.2 9 | - units-2.4.1.3 10 | - units-defs-2.2 11 | - units-parser-0.1.1.3 12 | - wshterm-0.1.0.1 13 | 14 | nix: 15 | pure: false 16 | -------------------------------------------------------------------------------- /test/Test.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import qualified Test.DocTest as DocTest 4 | import qualified Test.Tasty as Tasty 5 | 6 | 7 | main :: IO () 8 | main = doctests 9 | >> Tasty.defaultMain tests 10 | 11 | 12 | tests :: Tasty.TestTree 13 | tests = Tasty.testGroup "Tests" 14 | [ 15 | ] 16 | 17 | 18 | doctests :: IO () 19 | doctests = do 20 | putStrLn "\nRunning doctests ..." 21 | DocTest.doctest [ "-isrc", "src/ODE.hs" ] 22 | putStrLn "... done running doctests.\n" 23 | -------------------------------------------------------------------------------- /notes/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: notes.pdf intro-slides.pdf 3 | 4 | notes.pdf: notes.tex references.bib 5 | latexmk -lualatex -latexoption=-shell-escape $(PREVIEW_CONTINUOUSLY) notes.tex 6 | 7 | intro-slides.pdf: intro-slides.tex 8 | TEXINPUTS=$(TEXINPUTS):./beamer-trek latexmk -lualatex $(PREVIEW_CONTINUOUSLY) intro-slides.tex 9 | 10 | .PHONY: watch 11 | watch: PREVIEW_CONTINUOUSLY='-pvc' 12 | watch: notes.pdf 13 | 14 | .PHONY: watch-slides 15 | watch-slides: PREVIEW_CONTINUOUSLY='-pvc' 16 | watch-slides: intro-slides.pdf 17 | 18 | .PHONY: clean 19 | clean: 20 | rm -rf *.aux *.bbl *.blg *.fdb_latexmk *.fls *.log *.out *.toc _minted-notes *.nav *.snm 21 | 22 | .PHONY: mrclean 23 | mrclean: clean 24 | rm -f notes.pdf intro-slides.pdf 25 | -------------------------------------------------------------------------------- /notes/beamer-trek/beamerfontthemetrek.sty: -------------------------------------------------------------------------------- 1 | \RequirePackage{fontspec} 2 | 3 | 4 | \def\familydefault{\sfdefault} 5 | \def\trek@font{\fontspec[Path=./beamer-trek/,Extension=.ttf]{Antonio-Bold}} 6 | 7 | \setbeamerfont{frametitle}{ 8 | family=\trek@font, 9 | size=\fontsize{14}{14} 10 | } 11 | \setbeamerfont{title in head/foot}{ 12 | family=\trek@font, 13 | size=\fontsize{8}{8} 14 | } 15 | \setbeamerfont{title}{ 16 | family=\trek@font, 17 | size=\fontsize{18}{18} 18 | } 19 | \setbeamerfont{subtitle}{ 20 | family=\trek@font, 21 | size=\fontsize{14}{14} 22 | } 23 | \setbeamerfont{author}{ 24 | family=\trek@font, 25 | size=\fontsize{10}{10} 26 | } 27 | \setbeamerfont{institute}{ 28 | family=\trek@font, 29 | size=\fontsize{14}{14} 30 | } 31 | \setbeamerfont{date}{ 32 | family=\trek@font, 33 | size=\fontsize{10}{10} 34 | } 35 | \setbeamerfont{enumerate item}{ 36 | family=\trek@font, 37 | size=\fontsize{6}{6} 38 | } 39 | \setbeamerfont{enumerate subitem}{ 40 | parent=enumerate item 41 | } 42 | \setbeamerfont{enumerate subsubitem}{ 43 | parent=enumerate item 44 | } 45 | \setbeamerfont{itemize/enumerate body}{ 46 | family=\sffamily, 47 | size=\normalsize 48 | } 49 | -------------------------------------------------------------------------------- /gh-pages/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Haskell Spaceflight Workshop 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 16 |
17 |

HASKELL SPACEFLIGHT WORKSHOP

18 |

The current workshop notes and introductory slides:

19 |
20 | 21 | 22 |

Last published: {{PUB_DATE}}.

23 |
24 |

Source code is available in 25 | the GitHub repository.

27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /gh-pages/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Archivo', sans-serif; 3 | font-size: 15pt; 4 | margin-top: 60px; 5 | margin-right: 60px; 6 | margin-bottom: 60px; 7 | margin-left: 60px; 8 | } 9 | h1 { 10 | text-align: center; 11 | font-size: 32pt; 12 | } 13 | p { 14 | margin-top: 5px; 15 | margin-bottom: 5px; 16 | } 17 | a:link { 18 | text-decoration: none; 19 | } 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | .title { 24 | margin-top: 5px; 25 | margin-bottom: 0px; 26 | border-bottom-width: 2px; 27 | border-bottom-style: solid; 28 | padding-bottom: 15px; 29 | } 30 | .notes-box { 31 | background: #efefef; 32 | border-color: #c9c9c9; 33 | padding-left: 15px; 34 | padding-right: 10px; 35 | padding-top: 5px; 36 | padding-bottom: 5px; 37 | border-left-width: 4px; 38 | border-left-style: solid; 39 | margin-top: 20px; 40 | margin-left: 50px; 41 | margin-bottom: 20px 42 | } 43 | .current-notes { 44 | margin-top: 20px; 45 | } 46 | .notes-link { 47 | font-size: 18pt; 48 | } 49 | .last-published { 50 | font-size: 12pt; 51 | } 52 | 53 | /* Grid */ 54 | .container { 55 | display: grid; 56 | grid-template-columns: auto 200px; 57 | grid-template-rows: 200px auto; 58 | } 59 | .logo { 60 | grid-column-start: 2; 61 | grid-column-end: 3; 62 | grid-row-start: 1; 63 | grid-row-end: 2; 64 | } 65 | .text { 66 | grid-column-start: 1; 67 | grid-column-end: 2; 68 | grid-row-start: 1; 69 | grid-row-end: 3; 70 | margin-right: 30px; 71 | } 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Author name here (c) 2019 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Author name here nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /src/Staging/Types.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Staging.Types 3 | Description : Types for the Staging module. 4 | 5 | These types are separated so that they can be imported by both the Staging and 6 | Solutions.Staging modules, without introducing cyclic dependencies. 7 | -} 8 | {-# LANGUAGE DeriveAnyClass #-} 9 | {-# LANGUAGE DeriveGeneric #-} 10 | {-# LANGUAGE TypeFamilies #-} 11 | module Staging.Types where 12 | 13 | import Data.AdditiveGroup (AdditiveGroup) 14 | import Data.AffineSpace (AffineSpace, Diff, (.+^), (.-.)) 15 | import Data.VectorSpace (VectorSpace) 16 | import GHC.Generics (Generic) 17 | 18 | 19 | -- | State of the system. 20 | data State 21 | = State 22 | { propellantMass :: Double 23 | , position :: Double 24 | , velocity :: Double 25 | } 26 | deriving (Show) 27 | 28 | 29 | -- | Delta in state of the system. 30 | data DState 31 | = DState 32 | { dPropellantMass :: Double 33 | , dPosition :: Double 34 | , dVelocity :: Double 35 | } 36 | deriving (Show, Generic, AdditiveGroup, VectorSpace) 37 | 38 | 39 | -- | State is an AffineSpace instance, with DState as its associated vector 40 | -- space. 41 | instance AffineSpace State where 42 | type Diff State = DState 43 | s1 .-. s2 = DState 44 | { dPropellantMass = propellantMass s1 - propellantMass s2 45 | , dPosition = position s1 - position s2 46 | , dVelocity = velocity s1 - velocity s2 47 | } 48 | s .+^ ds = State 49 | { propellantMass = propellantMass s + dPropellantMass ds 50 | , position = position s + dPosition ds 51 | , velocity = velocity s + dVelocity ds 52 | } 53 | -------------------------------------------------------------------------------- /notes/beamer-trek/beamercolorthemetrek.sty: -------------------------------------------------------------------------------- 1 | \mode { 2 | \definecolor{trek@lightyellow}{HTML}{FFFF99} 3 | \definecolor{trek@lightorange}{HTML}{FFCC66} 4 | \definecolor{trek@darkorange}{HTML}{FF9933} 5 | \definecolor{trek@darkpurple}{HTML}{664466} 6 | \definecolor{trek@lightpurple}{HTML}{CC99CC} 7 | \definecolor{trek@lightblue}{HTML}{99CCFF} 8 | \definecolor{trek@midblue}{HTML}{3366CC} 9 | 10 | \definecolor{hs@darkpurple}{HTML}{86D6B6} 11 | \definecolor{hs@lightpurple}{HTML}{B699FF} 12 | \definecolor{hs@pink}{HTML}{E793DC} 13 | 14 | \setbeamercolor{palette primary}{fg=trek@lightorange} 15 | \setbeamercolor{palette secondary}{fg=trek@darkorange} 16 | \setbeamercolor{palette tertiary}{fg=trek@lightpurple} 17 | \setbeamercolor{palette quaternary}{fg=trek@lightorange} 18 | \setbeamercolor{palette sidebar primary}{fg=trek@darkorange} 19 | \setbeamercolor{palette sidebar secondary}{fg=trek@lightorange} 20 | \setbeamercolor{palette sidebar tertiary}{fg=trek@lightpurple} 21 | \setbeamercolor{palette sidebar quaternary}{fg=trek@midblue} 22 | 23 | \setbeamercolor{background canvas}{bg=black} 24 | \setbeamercolor{normal text}{fg=white} 25 | \setbeamercolor{frametitle}{fg=hs@lightpurple} 26 | \setbeamercolor{sidebar}{fg=hs@pink} 27 | \setbeamercolor{title in head/foot}{fg=hs@lightpurple} 28 | \setbeamercolor{title}{fg=hs@pink} 29 | \setbeamercolor{subtitle}{parent=frametitle} 30 | \setbeamercolor{institute}{parent=frametitle} 31 | \setbeamercolor{author}{parent=title} 32 | \setbeamercolor{date}{parent=title} 33 | \setbeamercolor{item}{use={frametitle},fg=frametitle.fg} 34 | \setbeamercolor{item projected}{use={frametitle},fg=black,bg=frametitle.fg} 35 | } 36 | \mode { 37 | \setbeamercolor{background canvas}{bg=white} 38 | } 39 | -------------------------------------------------------------------------------- /src/Orphans.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE QuasiQuotes #-} 3 | {-# LANGUAGE ScopedTypeVariables #-} 4 | {-# LANGUAGE TypeFamilies #-} 5 | {-# LANGUAGE TypeSynonymInstances #-} 6 | {-# LANGUAGE UndecidableInstances #-} 7 | {-# OPTIONS_GHC -Wno-orphans #-} 8 | module Orphans where 9 | 10 | 11 | import Data.AdditiveGroup (AdditiveGroup, negateV, zeroV, (^+^), 12 | (^-^)) 13 | import Data.Basis (Basis, HasBasis, basisValue, decompose, 14 | decompose') 15 | import qualified Data.Dimensions.SI as D 16 | import qualified Data.Metrology.SI.Poly as SIPoly 17 | import Data.Metrology.Vector (( # ), (%)) 18 | import qualified Data.Metrology.Vector as DMV 19 | import Data.Units.SI.Parser (si) 20 | import Data.VectorSpace (InnerSpace, Scalar, VectorSpace, 21 | magnitude, (*^), (<.>)) 22 | import qualified Linear as L 23 | 24 | 25 | instance (Num a) => AdditiveGroup (L.V2 a) where 26 | zeroV = L.V2 0 0 27 | v1 ^+^ v2 = v1 L.^+^ v2 28 | v1 ^-^ v2 = v1 L.^-^ v2 29 | negateV v = L.negated v 30 | 31 | 32 | instance (Num a) => VectorSpace (L.V2 a) where 33 | type Scalar (L.V2 a) = a 34 | s *^ v = s L.*^ v 35 | 36 | 37 | instance (Num a, AdditiveGroup a) => InnerSpace (L.V2 a) where 38 | v1 <.> v2 = L.dot v1 v2 39 | 40 | 41 | -- | Provide a HasBasis instance for time units. 42 | -- 43 | -- This seems needlessly messy; I expect there's a better way! 44 | instance 45 | forall n s d l. 46 | ( VectorSpace n, InnerSpace n, HasBasis n, () ~ Basis n 47 | , s ~ Scalar n, Floating s 48 | , d ~ DMV.DimFactorsOf D.Time 49 | , l ~ SIPoly.SI 50 | ) => HasBasis (DMV.Qu d l n) where 51 | type Basis (DMV.Qu d l n) = () 52 | basisValue () = 53 | let 54 | bvn :: n 55 | bvn = basisValue () 56 | in 57 | bvn % [si| s |] 58 | decompose x = [ ((), magnitude(x # [si| s |])) ] 59 | decompose' x _ = magnitude(x # [si| s |]) 60 | -------------------------------------------------------------------------------- /app/tex-plots/Main.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Main.hs 3 | Description : Plotting diagrams for the notes. 4 | -} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | module Main where 7 | 8 | import Control.Monad (forM_) 9 | import System.IO (hFlush, stdout) 10 | 11 | import qualified Examples.ODEExamples as ODEExamples 12 | import qualified Hohmann as Hohmann 13 | import qualified LunarAscent as LunarAscent 14 | import qualified Plot as Plot 15 | import qualified Staging as Staging 16 | 17 | 18 | main :: IO () 19 | main = do 20 | let plots 21 | = [ (ODEExamples.plotEulerDoubleExpDecay, "euler-double-exp-decay") 22 | , (ODEExamples.plotEulerSHM, "euler-shm") 23 | , (ODEExamples.plotSHMComparison, "shm-comparison") 24 | , (LunarAscent.plotLunarAscentVerticalRise, "lunar-ascent-vertical-rise") 25 | , (LunarAscent.plotLunarAscentBurnOnly, "lunar-ascent-burn-only") 26 | , (LunarAscent.plotLunarAscentMoonView, "lunar-ascent-moon-view") 27 | , (Staging.plotVelocityComparison, "staging-velocity-comparison") 28 | , (ODEExamples.plotVerticalThrow, "vertical-throw") 29 | , (Hohmann.plotHighImpulseBurn, "hohmann-high-impulse") 30 | , (Hohmann.plotLowImpulseBurn, "hohmann-low-impulse") 31 | , (Hohmann.plotUltraLowImpulseBurn, "hohmann-ultra-low-impulse") 32 | ] 33 | 34 | putStrLn "Plotting diagrams for the notes." 35 | forM_ plots $ \(plotFn, name) -> plotPGF plotFn name 36 | putStrLn "Done plotting diagrams for the notes." 37 | 38 | 39 | plotPGF :: (Plot.Output -> IO ()) -> FilePath -> IO () 40 | plotPGF plotFn name = do 41 | let 42 | fullName = "./notes/fig/" ++ name ++ ".tex" 43 | 44 | fgReset = "\x1b[0m" 45 | fgGreen = "\x1b[32m" 46 | fgBlue = "\x1b[34m" 47 | 48 | inBlue x = fgBlue ++ x ++ fgReset 49 | inGreen x = fgGreen ++ x ++ fgReset 50 | 51 | putStr $ " -> Plotting " ++ (inBlue fullName) ++ "..." 52 | hFlush stdout 53 | plotFn (Plot.PGF fullName) 54 | putStr $ " " ++ (inGreen "DONE!") ++ "\n" 55 | hFlush stdout 56 | -------------------------------------------------------------------------------- /src/Todo.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Todo 3 | Description : Handle problem Todos. 4 | 5 | This is a hacky solution to allow the workshop code to run, 6 | unmodified, for the purpose of running tests (or to skip straight to 7 | results), yet still allow participants to write code themselves. 8 | 9 | If the environment variable @IDDQD@ is set then the 'todo' function 10 | will use its "fallback solution". The lookup is done using an 11 | 'unsafePerformIO'. 12 | -} 13 | {-# LANGUAGE FlexibleContexts #-} 14 | {-# LANGUAGE LambdaCase #-} 15 | {-# LANGUAGE ScopedTypeVariables #-} 16 | module Todo 17 | ( -- * Types 18 | FallbackSolution(..) 19 | -- * Functions 20 | , todo 21 | , getFallbackEnv 22 | ) where 23 | 24 | import GHC.Stack (HasCallStack, callStack, getCallStack, 25 | srcLocFile, srcLocStartLine) 26 | import System.Environment (lookupEnv) 27 | import System.IO.Unsafe (unsafePerformIO) 28 | 29 | 30 | -- | A fallback solution. 31 | data FallbackSolution a = FallbackSolution a 32 | 33 | 34 | -- | Indicate a problem to be completed by a workshop participant. 35 | todo :: (HasCallStack) => FallbackSolution a -> a 36 | todo (FallbackSolution fallback) = 37 | case (unsafePerformIO getFallbackEnv) of 38 | UseFallbackSolution -> fallback 39 | FailNow -> 40 | error $ case (getCallStack callStack) of 41 | ((_, srcLoc):_) -> 42 | "Solution not implemented: File " 43 | <> srcLocFile srcLoc 44 | <> ", line " 45 | <> show (srcLocStartLine srcLoc) 46 | <> "." 47 | _ -> "Solution not implemented. Location unknown (sorry)." 48 | 49 | 50 | -- | Indicate which solution to use. 51 | data UseWhichSolution 52 | = UseFallbackSolution -- ^ Use the fallback solution. 53 | | FailNow -- ^ Fail right now. 54 | 55 | 56 | -- | Read the environment to check if we should use fallback solutions. 57 | -- 58 | -- If the variable @IDDQD@ is set then the fallback solutions will be 59 | -- used. 60 | getFallbackEnv :: IO UseWhichSolution 61 | getFallbackEnv = lookupEnv "IDDQD" >>= \case 62 | Just _ -> pure UseFallbackSolution 63 | _ -> pure FailNow 64 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Based on a build template from: 2 | # 3 | # https://docs.haskellstack.org/en/stable/travis_ci/ 4 | 5 | # Choose a build environment 6 | dist: xenial 7 | 8 | # Do not choose a language; we provide our own build tools. 9 | language: generic 10 | 11 | # Caching so the next build will be fast too. 12 | cache: 13 | directories: 14 | - $HOME/.stack 15 | timeout: 1000 16 | 17 | 18 | before_install: 19 | # Download and unpack the stack executable 20 | - mkdir -p ~/.local/bin 21 | - export PATH=$HOME/.local/bin:$PATH 22 | - travis_retry curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' 23 | 24 | jobs: 25 | include: 26 | - stage: bigdeps 27 | name: "Cache some big / time-consuming dependencies" 28 | addons: 29 | apt: 30 | packages: 31 | - libgmp-dev 32 | script: stack --no-terminal install diagrams-core JuicyPixels lens singletons 33 | - stage: build 34 | name: "Build library and notes" 35 | addons: 36 | apt: 37 | sources: 38 | - sourceline: 'ppa:jonathonf/texlive-2018' 39 | packages: 40 | - libgmp-dev 41 | - texlive-fonts-recommended 42 | - texlive-generic-extra 43 | - texlive-latex-extra 44 | - texlive-luatex 45 | - texlive-publishers 46 | - texlive-science 47 | - texlive-xetex 48 | - latexmk 49 | - fonts-lmodern 50 | - fonts-texgyre 51 | - python-pygments 52 | script: 53 | - stack --no-terminal --install-ghc test --only-dependencies 54 | - export IDDQD=1 55 | - stack --no-terminal test --haddock --no-haddock-deps 56 | - stack exec tex-plots 57 | - export PUB_DATE=$(date '+%Y-%m-%dT%H:%M:%S%z') 58 | - sed -i -e "s/PUBDATE/${PUB_DATE}/" notes/notes.tex 59 | - sed -i -e "s/{{PUB_DATE}}/${PUB_DATE}/" gh-pages/index.html 60 | - pushd notes; make; popd 61 | - cp notes/notes.pdf gh-pages/notes.pdf 62 | - cp notes/intro-slides.pdf gh-pages/intro-slides.pdf 63 | - cp logo.svg gh-pages/logo.svg 64 | deploy: 65 | provider: pages 66 | local_dir: gh-pages 67 | skip_cleanup: true 68 | github_token: $GITHUB_TOKEN 69 | keep_history: true 70 | on: 71 | branch: master 72 | 73 | stages: 74 | - bigdeps 75 | - build 76 | -------------------------------------------------------------------------------- /src/Hohmann/Types.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Hohmann.Types 3 | Description : Types for the Hohmann module. 4 | 5 | These types are separated so that they can be imported by both the Hohmann and 6 | Solutions.Hohmann modules, without introducing cyclic dependencies. 7 | -} 8 | {-# LANGUAGE DeriveAnyClass #-} 9 | {-# LANGUAGE DeriveGeneric #-} 10 | {-# LANGUAGE TypeFamilies #-} 11 | module Hohmann.Types where 12 | 13 | import Data.AdditiveGroup (AdditiveGroup) 14 | import Data.AffineSpace (AffineSpace, Diff, (.+^), (.-.)) 15 | import Data.VectorSpace (VectorSpace, (^+^), (^-^)) 16 | import GHC.Generics (Generic) 17 | import Linear (V2) 18 | import Orphans () 19 | 20 | 21 | -- | Fixed parameters for the Hohmann simulation. 22 | data Params 23 | = Params 24 | { mu :: Double -- ^ Standard gravitational parameter (m^3/s^2). 25 | , mDot :: Double -- ^ Mass flow rate (kg/s) 26 | , isp :: Double -- ^ Vacuum specific impulse (s). 27 | , r1 :: Double -- ^ Inner circular orbit radius (m). 28 | , r2 :: Double -- ^ Outer circular orbit radius (m). 29 | , tStep :: Double -- ^ Time step (s). 30 | , tEps :: Double -- ^ t_epsilon: accuracy of start/stop (s). 31 | } 32 | 33 | 34 | -- | State of the system. 35 | data State 36 | = State 37 | { mass :: Double 38 | , distance :: Double 39 | , position :: V2 Double 40 | , velocity :: V2 Double 41 | } 42 | deriving (Show) 43 | 44 | 45 | -- | Delta in state of the system. 46 | data DState 47 | = DState 48 | { dMass :: Double 49 | , dDistance :: Double 50 | , dPosition :: V2 Double 51 | , dVelocity :: V2 Double 52 | } 53 | deriving (Show, Generic, AdditiveGroup, VectorSpace) 54 | 55 | 56 | -- | State is an AffineSpace instance, with DState as its associated vector 57 | -- space. 58 | instance AffineSpace State where 59 | type Diff State = DState 60 | s1 .-. s2 = DState 61 | { dMass = mass s1 - mass s2 62 | , dDistance = distance s2 ^-^ distance s2 63 | , dPosition = position s1 ^-^ position s2 64 | , dVelocity = velocity s1 ^-^ velocity s2 65 | } 66 | s .+^ ds = State 67 | { mass = mass s + dMass ds 68 | , distance = distance s + dDistance ds 69 | , position = position s ^+^ dPosition ds 70 | , velocity = velocity s ^+^ dVelocity ds 71 | } 72 | -------------------------------------------------------------------------------- /notes/intro-slides.tex: -------------------------------------------------------------------------------- 1 | \documentclass{beamer} 2 | \usepackage{import} 3 | 4 | \usetheme{trek} 5 | 6 | \begin{document} 7 | 8 | \title{HASKELL SPACEFLIGHT WORKSHOP} 9 | \author{JONATHAN MERRITT\hspace{0.5em}{\scriptsize\&}\hspace{0.5em}LUKE CLIFTON} 10 | \institute{HASKELL SPACEFLIGHT WORKSHOP} 11 | \subtitle{LAMBDAJAM 2019} 12 | \date{2019} 13 | \titlegraphic{\includegraphics[width=0.4\textwidth]{fig/logo-text-dark.pdf}} 14 | 15 | \begin{frame} 16 | \titlepage 17 | \end{frame} 18 | 19 | \begin{frame} 20 | \frametitle{DEPENDENCIES} 21 | The workshop is on GitHub, but has many dependencies: 22 | \begin{itemize} 23 | \item https://www.github.com/lancelet/space-workshop 24 | \item \texttt{stack} will fetch and build dependencies (slowly) 25 | \item Docker image available on USB flash drives 26 | \end{itemize} 27 | \end{frame} 28 | 29 | \begin{frame} 30 | \frametitle{OBJECTIVES} 31 | Main objectives: 32 | \begin{itemize} 33 | \item Solve spaceflight problems 34 | \item Promote numerical programming to FP people 35 | \item Get feedback on ideas and approaches 36 | \end{itemize} 37 | \vspace{1em} 38 | General approach: 39 | \begin{itemize} 40 | \item ``Traditional'' ODE solving, but with Haskell 41 | \item Initial value problems 42 | \item Engineering focus: 43 | \begin{itemize} 44 | \item Development and interpretation of ODEs 45 | \item Rich states, with typed vector components 46 | \item Units (example only) 47 | \item ODEs include control signals 48 | \end{itemize} 49 | \end{itemize} 50 | \end{frame} 51 | 52 | \begin{frame} 53 | \frametitle{OUTLINE} 54 | Outline: 55 | \begin{itemize} 56 | \item Introduction to numerical integration of ODEs 57 | \begin{itemize} 58 | \item Euler's method 59 | \item 4th-Order Runge Kutta 60 | \item Apollo lunar ascent guidance example 61 | \end{itemize} 62 | \item Tsiolkovsky Rocket Equation 63 | \begin{itemize} 64 | \item Propellant mass fraction / mass budget 65 | \item Specific impulse 66 | \item Staging example 67 | \item Tsiolkovsky Rocket Equation 68 | \end{itemize} 69 | \item Hohmann transfers 70 | \begin{itemize} 71 | \item Kepler's Problem and Keplerian Motion 72 | \item Instantaneous impulse approximation 73 | \item Finite impulse numerical simulations 74 | \end{itemize} 75 | \item Suggestions for further projects 76 | \end{itemize} 77 | \end{frame} 78 | 79 | \end{document} 80 | -------------------------------------------------------------------------------- /space-workshop.cabal: -------------------------------------------------------------------------------- 1 | name: space-workshop 2 | version: 0.1.0.0 3 | synopsis: LambdaJam 2019 Spaceflight Workshop 4 | description: Please see the README on GitHub at 5 | 6 | license: BSD3 7 | author: "Jonathan Merritt and Luke Clifton" 8 | maintainer: "j.s.merritt@gmail.com.com" 9 | copyright: "2019 Jonathan Merritt and Luke Clifton" 10 | category: Math 11 | build-type: Simple 12 | extra-source-files: 13 | README.md 14 | ChangeLog.md 15 | cabal-version: >=1.10 16 | 17 | homepage: https://github.com/lancelet/space-workshop 18 | bug-reports: https://github.com/lancelet/space-workshop/issues 19 | source-repository head 20 | type: git 21 | location: https://github.com/lancelet/space-workshop.git 22 | 23 | library 24 | exposed-modules: 25 | Examples.ODEExamples 26 | , Hohmann 27 | , Hohmann.Types 28 | , LunarAscent 29 | , LunarAscent.AGC 30 | , LunarAscent.Types 31 | , ODE 32 | , Orphans 33 | , Plot 34 | , Solutions.Hohmann 35 | , Solutions.ODE 36 | , Solutions.Staging 37 | , Staging 38 | , Staging.Types 39 | , Todo 40 | , Units 41 | 42 | build-depends: 43 | base >= 4.7 && < 5 44 | , bytestring 45 | , Chart 46 | , Chart-diagrams 47 | , colour 48 | , diagrams-lib 49 | , diagrams-pgf 50 | , diagrams-rasterific 51 | , iterm-show 52 | , JuicyPixels 53 | , lens 54 | , linear 55 | , MemoTrie 56 | , text 57 | , units 58 | , units-defs 59 | , vector 60 | , vector-space 61 | hs-source-dirs: src 62 | default-language: Haskell2010 63 | ghc-options: -Wall -Wredundant-constraints 64 | 65 | test-suite tests 66 | type: exitcode-stdio-1.0 67 | default-language: Haskell2010 68 | hs-source-dirs: test 69 | main-is: Test.hs 70 | build-depends: 71 | base 72 | , space-workshop 73 | , bytestring 74 | , Chart 75 | , Chart-diagrams 76 | , colour 77 | , diagrams-lib 78 | , diagrams-pgf 79 | , diagrams-rasterific 80 | , iterm-show 81 | , JuicyPixels 82 | , lens 83 | , linear 84 | , MemoTrie 85 | , text 86 | , units 87 | , units-defs 88 | , vector 89 | , vector-space 90 | 91 | , doctest 92 | , hedgehog 93 | , tasty 94 | , tasty-hedgehog 95 | 96 | executable tex-plots 97 | default-language: Haskell2010 98 | hs-source-dirs: app/tex-plots 99 | ghc-options: -Wall 100 | main-is: Main.hs 101 | build-depends: 102 | base >= 4.7 && < 5 103 | , space-workshop 104 | -------------------------------------------------------------------------------- /src/Units.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ExplicitNamespaces #-} 2 | {-# LANGUAGE TypeOperators #-} 3 | {-# LANGUAGE DataKinds #-} 4 | module Units 5 | ( -- * Base units 6 | Count 7 | , Length 8 | , Mass 9 | , Time 10 | -- * Standard derived units 11 | , Velocity 12 | , Acceleration 13 | , AngVelocity 14 | , AngAcceleration 15 | , Force 16 | -- * Apollo-specific derived units 17 | , MassFlowRate 18 | , SGPUnit 19 | , BThresholdUnit 20 | -- * Metric units 21 | , Second(Second) 22 | -- * Imperial units 23 | , Foot(Foot) 24 | , Pound(Pound) 25 | -- * Quasiquoter 26 | , si 27 | -- * Unit operators 28 | , (:/)((:/)), (%), (%.), (|*^|), (|*|), (|+|), (|-|), (|.+^|), (|.-.|) 29 | , (|.|), (|/), (|/|), (|^*|), (|^/|), type(@+), (|^), (*|), (:^)((:^)), (#) 30 | , (.#) 31 | -- * Unit functions 32 | , qDistanceSq, qMagnitudeSq, qNegate, qSq, qNormalized, quantity, zero 33 | , qMagnitude 34 | -- * Additional unit types 35 | , Normalize, Point(Point), Qu, Three 36 | ) where 37 | 38 | import qualified Data.Dimensions.SI as D 39 | import Data.Metrology.Show () 40 | import qualified Data.Metrology.SI.Poly as SIPoly 41 | import Data.Metrology.Vector ((:/) ((:/)), (:^) ((:^)), type (@+), 42 | Normalize, Point (Point), Qu, Three, 43 | Two, MOne, MTwo, qDistanceSq, qMagnitude, 44 | qMagnitudeSq, qNegate, qNormalized, 45 | qSq, quantity, zero, ( # ), (%), (%.), 46 | (*|), (|*^|), (|*|), (|+|), (|-|), 47 | (|.+^|), (|.-.|), (|.|), (|/), (|/|), 48 | (|^), (|^*|), (|^/|), (.#)) 49 | import qualified Data.Metrology.Vector as DMV 50 | import Data.Units.SI (Second (Second)) 51 | import Data.Units.SI.Parser (si) 52 | import Data.Units.US (Foot (Foot), Pound (Pound)) 53 | 54 | 55 | -- Make a quantity that uses the SI LCSU and a custom numeric type. 56 | type MkQu_DLSI dim = DMV.Qu (DMV.DimFactorsOf dim) SIPoly.SI 57 | 58 | type Count = Qu '[] SIPoly.SI 59 | 60 | type Length = MkQu_DLSI D.Length 61 | type Mass = MkQu_DLSI D.Mass 62 | type Time = MkQu_DLSI D.Time 63 | 64 | type Velocity = MkQu_DLSI D.Velocity 65 | type Acceleration = MkQu_DLSI D.Acceleration 66 | type AngVelocity = MkQu_DLSI (D.Time :^ MOne) 67 | type AngAcceleration = MkQu_DLSI (D.Time :^ MTwo) 68 | type Force = MkQu_DLSI D.Force 69 | 70 | type MassFlowRate = MkQu_DLSI (D.Mass :/ D.Time) 71 | type SGPUnit = MkQu_DLSI (D.Length :^ Three :/ D.Time :^ Two) 72 | type BThresholdUnit = MkQu_DLSI (D.Length :/ D.Time :^ Three) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logo 2 | 3 | # Haskell Spaceflight Workshop 4 | 5 | [![Build Status](https://travis-ci.org/lancelet/space-workshop.svg?branch=master)](https://travis-ci.org/lancelet/space-workshop) 6 | 7 | The workshop contents are now complete. Final modifications and updates may still be made prior to LambdaJam 2019, but the current version is representative. 8 | 9 | The current notes for the workshop are available from 10 | [GitHub pages](https://lancelet.github.io/space-workshop), built using Travis CI. 11 | 12 | ## Instructions for Participants 13 | 14 | Please make sure that you download the required dependencies for the workshop and [the notes](https://lancelet.github.io/space-workshop) in advance. We support the [`stack`](https://www.haskellstack.org) and `cabal` build tools. Dependencies can be fetched by doing: 15 | 16 | ``` 17 | $ stack --install-ghc test --only-dependencies 18 | ``` 19 | 20 | or 21 | 22 | ``` 23 | $ cabal new-build --only-dependencies test:tests 24 | ``` 25 | 26 | in the checked-out repository. 27 | 28 | ## Docker Option 29 | 30 | If you are unable to obtain the required dependencies prior to the workshop, we will be distributing (on temporary loan only) USB flash drives containing a Docker image with the dependencies pre-installed. If you use this option, please copy everything off the USB drive before passing it on. Please then follow the instructions in the README from the flash drive. 31 | 32 | For reference, the Docker image itself was built using the [CI pipeline here](https://gitlab.com/jmerritt/haskell-space-workshop-docker-image), but the USB Flash drive also contains a snapshot of this GitHub repository and the notes. 33 | 34 | ## Solutions 35 | 36 | Solutions are provided in the `Solutions` sub-modules. To use them, either call them directly from the problem code, OR set the environment variable `IDDQD=1`, which will cause the `todo` function to use the fallback solutions: 37 | 38 | ``` 39 | $ export IDDQD=1 # causes the `todo` function to use the provided solutions 40 | ``` 41 | 42 | ## Developer Notes 43 | 44 | See the `.travis.yml` file for detailed CI build instructions. 45 | 46 | Building the manual requires: 47 | - TexLive (I use a full installation via nixpkgs) 48 | - pygments, for code formatting 49 | 50 | A manual build can be performed as follows: 51 | 52 | ``` 53 | $ export IDDQD=1 54 | $ stack test 55 | $ stack exec tex-plots # generates PGF-format plots for the LaTeX notes 56 | $ cd notes 57 | $ make # generates the LaTeX notes.pdf 58 | ``` 59 | 60 | Or with `cabal`: 61 | 62 | ``` 63 | $ export IDDQD=1 64 | $ cabal new-test 65 | $ cabal new-run tex-plots # generates PGF-format plots for the LaTeX notes 66 | $ cd notes 67 | $ make # generates the LaTeX notes.pdf 68 | ``` 69 | 70 | Be careful: the LaTeX file has `\nonstopmode` set so that it doesn't hang the CI build. It may be best to remove this when making local changes so that errors are more obvious. 71 | 72 | The Makefile for the notes supports a `watch` phony target to continuously watch the source files and re-run LaTeX as required: 73 | 74 | ``` 75 | $ make watch 76 | ``` 77 | -------------------------------------------------------------------------------- /notes/beamer-trek/trek-shapes.sty: -------------------------------------------------------------------------------- 1 | \RequirePackage{tikz} 2 | 3 | \makeatletter 4 | 5 | % Trek cursor shape 6 | \pgfkeys{/tikz/trek/cursor/width/.initial = 20mm} 7 | \pgfkeys{/tikz/trek/cursor/height/.initial = 7.5mm} 8 | \pgfdeclareshape{trek cursor} 9 | { 10 | \saveddimen\width{\pgf@x = \pgfkeysvalueof{/tikz/trek/cursor/width}} 11 | \saveddimen\height{\pgf@x = \pgfkeysvalueof{/tikz/trek/cursor/height}} 12 | \savedanchor\centerpoint{ 13 | \pgf@x = 0.5\wd\pgfnodeparttextbox 14 | \pgf@y = 0.5\ht\pgfnodeparttextbox 15 | } 16 | \anchor{center}{\centerpoint} 17 | 18 | \backgroundpath{ 19 | \pgf@xc = \height 20 | \divide \pgf@xc by 2 21 | \pgf@xa = \width 22 | \advance \pgf@xa by -\pgf@xc 23 | \pgf@ya = \height 24 | \pgf@xb = \pgf@xa 25 | \pgf@yb = 0mm 26 | \pgfpathmoveto{\pgfpoint{0}{0}} 27 | \pgfpathlineto{\pgfpoint{0}{\height}} 28 | \pgfpathlineto{\pgfpoint{\pgf@xa}{\pgf@ya}} 29 | \pgfpatharcto{\height/2}{\height/2}{0}{0}{0}{\pgfpoint{\pgf@xb}{\pgf@yb}} 30 | \pgfpathclose 31 | \pgfusepath{fill,draw} 32 | } 33 | } 34 | 35 | % Trek circular bullet 36 | \pgfkeys{/tikz/trek/circular bullet/radius/.initial = 1.25mm} 37 | \pgfdeclareshape{trek circular bullet} 38 | { 39 | \saveddimen\width{\pgf@x = 2\pgfkeysvalueof{/tikz/trek/circular bullet/radius}} 40 | \saveddimen\height{\pgf@x = 2\pgfkeysvalueof{/tikz/trek/circular bullet/radius}} 41 | \savedanchor\centerpoint{ 42 | \pgf@x = 0.5\wd\pgfnodeparttextbox 43 | \pgf@y = 0.5\ht\pgfnodeparttextbox 44 | } 45 | \anchor{center}{\centerpoint} 46 | \savedanchor\basepoint{ 47 | \pgf@x = 0.5\wd\pgfnodeparttextbox 48 | \pgf@y = 0.0\wd\pgfnodeparttextbox 49 | } 50 | \anchor{base}{\basepoint} 51 | 52 | \foregroundpath{ 53 | \pgfpathcircle{\pgfpoint{0}{0}}{\pgfkeysvalueof{/tikz/trek/circular bullet/radius}} 54 | \pgfpathclose 55 | \pgfusepath{fill} 56 | } 57 | } 58 | % Command to draw the circle inline in a document 59 | \newcommand{\trekcircle}{% 60 | \newdimen\@trek@lineheight% 61 | \setbox0=\hbox{I}% 62 | \@trek@lineheight=\ht0% 63 | \begin{tikzpicture}[baseline=-0.5\@trek@lineheight] 64 | \draw node (0,0) [trek circular bullet]{}; 65 | \end{tikzpicture}% 66 | } 67 | 68 | % Trek elbow shape 69 | \pgfkeys{/tikz/trek/elbow/bar height/.initial = 7.5mm} 70 | \pgfkeys{/tikz/trek/elbow/outer radius/.initial = 3mm} 71 | \pgfkeys{/tikz/trek/elbow/inner radius/.initial = 1mm} 72 | \pgfkeys{/tikz/trek/elbow/sidebar width/.initial = 5mm} 73 | \pgfkeys{/tikz/trek/elbow/width/.initial = 30mm} 74 | \pgfkeys{/tikz/trek/elbow/height/.initial = 10mm} 75 | \pgfdeclareshape{trek elbow} 76 | { 77 | \saveddimen\barheight{\pgf@x = \pgfkeysvalueof{/tikz/trek/elbow/bar height}} 78 | \saveddimen\outerradius{\pgf@x = \pgfkeysvalueof{/tikz/trek/elbow/outer radius}} 79 | \saveddimen\innerradius{\pgf@x = \pgfkeysvalueof{/tikz/trek/elbow/inner radius}} 80 | \saveddimen\sidebarwidth{\pgf@x = \pgfkeysvalueof{/tikz/trek/elbow/sidebar width}} 81 | \saveddimen\width{\pgf@x = \pgfkeysvalueof{/tikz/trek/elbow/width}} 82 | \saveddimen\height{\pgf@x = \pgfkeysvalueof{/tikz/trek/elbow/height}} 83 | \savedanchor\centerpoint{ 84 | \pgf@x = 0.5\wd\pgfnodeparttextbox 85 | \pgf@y = 0.5\ht\pgfnodeparttextbox 86 | } 87 | \anchor{center}{\centerpoint} 88 | 89 | \backgroundpath{ 90 | \pgf@xa = -\width 91 | \advance \pgf@xa by \outerradius 92 | \pgf@ya = \barheight 93 | \advance \pgf@ya by -\outerradius 94 | \pgf@yb = -\height 95 | \advance \pgf@yb by \barheight 96 | \pgf@xb = -\width 97 | \advance \pgf@xb by \sidebarwidth 98 | \pgf@xc = \pgf@xb 99 | \advance \pgf@xc by \innerradius 100 | \pgfpathmoveto{\pgfpoint{0}{0}} 101 | \pgfpathlineto{\pgfpoint{0}{\barheight}} 102 | \pgfpathlineto{\pgfpoint{\pgf@xa}{\barheight}} 103 | \pgfpatharcto{\outerradius}{\outerradius}{0}{0}{1}{\pgfpoint{-\width}{\pgf@ya}} 104 | \pgfpathlineto{\pgfpoint{-\width}{\pgf@yb}} 105 | \pgfpathlineto{\pgfpoint{\pgf@xb}{\pgf@yb}} 106 | \pgfpathlineto{\pgfpoint{\pgf@xb}{-\innerradius}} 107 | \pgfpatharcto{\innerradius}{\innerradius}{0}{0}{0}{\pgfpoint{\pgf@xc}{0}} 108 | \pgfpathclose 109 | \pgfusepath{fill,draw} 110 | } 111 | } 112 | 113 | \makeatother 114 | -------------------------------------------------------------------------------- /src/Solutions/Hohmann.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Solutions.Hohmann 3 | Description : Solutions for the Hohmann module. 4 | -} 5 | {-# LANGUAGE NegativeLiterals #-} 6 | {-# LANGUAGE TypeOperators #-} 7 | module Solutions.Hohmann where 8 | 9 | import Control.Lens ((^.)) 10 | import Data.LinearMap ((:-*), linear) 11 | import Data.List.NonEmpty (NonEmpty) 12 | import Linear (V2, norm, normalize, quadrance, (*^), 13 | (^+^), (^/), _x, _y) 14 | 15 | import qualified Hohmann.Types as T 16 | import qualified ODE as ODE 17 | 18 | 19 | -- | Standard earth gravity. 20 | g0 :: Double 21 | g0 = 9.80665 -- m/s^2 22 | 23 | 24 | -- | Find the craft angle (theta) from the state. 25 | -- 26 | -- The angle should run from [0, 2*pi). Note that this range is different 27 | -- from the normal atan2 function. 28 | angle :: T.State -> Double 29 | angle state = 30 | let 31 | p = T.position state 32 | q = atan2 (p^._y) (p^._x) 33 | in 34 | if q > 0 35 | then q 36 | else (2*pi) + q 37 | 38 | 39 | -- | Find the gravitational force from the state. 40 | gravity :: T.Params -> T.State -> V2 Double 41 | gravity params state = 42 | let 43 | rMag2 = quadrance (T.position state) 44 | rHat = normalize (T.position state) 45 | in -(T.mu params) * (T.mass state) / rMag2 *^ rHat 46 | 47 | 48 | -- | Find the thrust force from the state. 49 | thrust :: T.Params -> T.State -> V2 Double 50 | thrust params state = 51 | let vHat = normalize (T.velocity state) 52 | in g0 * (T.isp params) * (T.mDot params) *^ vHat 53 | 54 | 55 | -- | Return @Nothing@ when @shouldTerminate == True@; @Just@ otherwise. 56 | terminateWhen 57 | :: Bool -- ^ when True, terminate 58 | -> a -- ^ value to wrap 59 | -> Maybe a 60 | terminateWhen shouldTerminate input 61 | = if shouldTerminate 62 | then Nothing 63 | else Just input 64 | 65 | 66 | -- | Compute a coasting trajectory. 67 | -- 68 | -- The coasting trajectory is terminated once the craft reaches a given 69 | -- termination condition. 70 | coast 71 | :: T.Params -- ^ Simulation parameters. 72 | -> (T.State -> Bool) -- ^ Termination condition (True to terminate). 73 | -> (Double, T.State) -- ^ Initial time and state. 74 | -> NonEmpty (Double, T.State) -- ^ Simulation steps. 75 | coast params shouldTerminate s0 = 76 | let 77 | gradFn :: (Double, T.State) -> Maybe (Double :-* T.DState) 78 | gradFn (_, state) 79 | = terminateWhen (shouldTerminate state) 80 | $ linear 81 | $ \dt -> T.DState 82 | { T.dMass = 0 83 | , T.dDistance = dt * norm (T.velocity state) 84 | , T.dPosition = dt *^ T.velocity state 85 | , T.dVelocity = dt *^ gravity params state ^/ T.mass state 86 | } 87 | 88 | in ODE.integrateTerminating ODE.rk4StepTerminating 89 | (T.tEps params) 90 | (T.tStep params) 91 | s0 92 | gradFn 93 | 94 | 95 | -- | Compute a burn trajectory. 96 | -- 97 | -- The burn trajectory is terminated once the craft reaches a given 98 | -- velocity magnitude. 99 | burn 100 | :: T.Params -- ^ Simulation parameters. 101 | -> Double -- ^ Termination velocity (m/s). 102 | -> (Double, T.State) -- ^ Initial time and state. 103 | -> NonEmpty (Double, T.State) -- ^ Simulation steps. 104 | burn params velF s0 = 105 | let 106 | gradFn :: (Double, T.State) -> Maybe (Double :-* T.DState) 107 | gradFn (_, state) 108 | = terminateWhen (norm (T.velocity state) >= velF) 109 | $ linear 110 | $ \dt -> T.DState 111 | { T.dMass = dt * -1 * T.mDot params 112 | , T.dDistance = dt * norm (T.velocity state) 113 | , T.dPosition = dt *^ T.velocity state 114 | , T.dVelocity = dt *^ (gravity params state ^+^ thrust params state) ^/ T.mass state 115 | } 116 | 117 | in ODE.integrateTerminating ODE.rk4StepTerminating 118 | (T.tEps params) 119 | (T.tStep params) 120 | s0 121 | gradFn 122 | -------------------------------------------------------------------------------- /src/Staging.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Staging 3 | Description : Simulating staging of rockets. 4 | -} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | {-# LANGUAGE TypeOperators #-} 7 | {-# OPTIONS_GHC -Wno-unused-imports #-} -- re-enable after completing solutions 8 | module Staging where 9 | 10 | import Data.LinearMap ((:-*), linear) 11 | import Data.List.NonEmpty (NonEmpty) 12 | import qualified Data.List.NonEmpty as NonEmpty 13 | import Data.VectorSpace ((^*)) 14 | 15 | import qualified ODE 16 | import qualified Plot 17 | import qualified Solutions.Staging 18 | import qualified Staging.Types as T 19 | import Todo (FallbackSolution (FallbackSolution), todo) 20 | 21 | 22 | -- | Equation of motion for a rocket stage. 23 | -- 24 | -- The rocket engine is burning in a vacuum in the absence of gravity. 25 | equationOfMotion 26 | :: Double -- ^ Mass flow rate (kg / s). 27 | -> Double -- ^ Specific impulse (s). 28 | -> Double -- ^ Dry mass of this stage (kg). 29 | -> Double -- ^ Total mass of remaining stages (kg). 30 | -> (Double, T.State) -- ^ Current time and state (s, State). 31 | -> Double :-* T.DState -- ^ Linear map of derivatives. 32 | equationOfMotion {- mDot isp mDry mRemaining (_, state) -} 33 | = todo (FallbackSolution Solutions.Staging.equationOfMotion) 34 | {- 35 | = linear ((^*) grad) 36 | where 37 | -- grad is a DState for a unit time increment 38 | grad :: T.DState 39 | grad = T.DState 40 | { T.dPropellantMass = undefined 41 | , T.dPosition = undefined 42 | , T.dVelocity = undefined 43 | } 44 | 45 | g0 :: Double -- m/s^2 46 | g0 = 9.80665 47 | -} 48 | 49 | 50 | -- | Burn a stage of a rocket (can be the first or second stage). 51 | burnStage 52 | :: Double -- ^ Initial propellant mass (kg). 53 | -> Double -- ^ Dry mass of the current stage (kg). 54 | -> Double -- ^ Total mass of remaining mass of stages (kg). 55 | -> Int -- ^ Number of integration steps. 56 | -> Double -- ^ Initial time (s). 57 | -> T.State -- ^ Initial state. 58 | -> NonEmpty (Double, T.State) -- ^ List of time and state tuples. 59 | burnStage {- mp0 mDry mRemaining nSteps time0 state0 -} 60 | = todo (FallbackSolution Solutions.Staging.burnStage) 61 | {- 62 | where 63 | -- Duration of the burn (s). 64 | burnDuration :: Double 65 | burnDuration = undefined 66 | 67 | -- Evaluation times (s). 68 | times :: NonEmpty Double 69 | times = NonEmpty.fromList 70 | $ ODE.linspace nSteps time0 (time0 + burnDuration) 71 | 72 | -- Gradient function for ODE solver. 73 | gradFn :: (Double, T.State) -> Double :-* T.DState 74 | gradFn = undefined 75 | 76 | -- Mass flow rate (kg / s). 77 | mDot :: Double 78 | mDot = 290.0 79 | 80 | -- Specific impulse (s). 81 | isp :: Double 82 | isp = 300.0 83 | -} 84 | 85 | 86 | -- | Burn the single stage rocket only. 87 | burnSingleStage 88 | :: Int -- ^ Number of integration steps per stage. 89 | -> NonEmpty (Double, T.State) -- ^ Time and state list. 90 | burnSingleStage {- nSteps = burnStage mp0 mDry 0.0 nSteps state0 -} 91 | = todo (FallbackSolution Solutions.Staging.burnSingleStage) 92 | 93 | 94 | -- | Burn the two stage rocket only. 95 | burnTwoStage 96 | :: Int -- ^ Number of integration steps per stage. 97 | -> NonEmpty (Double, T.State) -- ^ Time and state list. 98 | burnTwoStage {- nSteps = stage1 <> stage2 -} 99 | = todo (FallbackSolution Solutions.Staging.burnTwoStage) 100 | 101 | 102 | -- | Plot the comparison of velocity history for the staging options. 103 | plotVelocityComparison :: Plot.Output -> IO () 104 | plotVelocityComparison output = 105 | let 106 | nSteps :: Int 107 | nSteps = 100 108 | 109 | extractVel :: (Double, T.State) -> (Double, Double) 110 | extractVel (t, state) = (t, T.velocity state) 111 | 112 | v1Stage :: [(Double, Double)] 113 | v1Stage = NonEmpty.toList (extractVel <$> burnSingleStage nSteps) 114 | 115 | v2Stage :: [(Double, Double)] 116 | v2Stage = NonEmpty.toList (extractVel <$> burnTwoStage nSteps) 117 | in 118 | Plot.xyChart 119 | output 120 | "Single Stage vs Two Stage Velocity Comparison" 121 | "Time (s)" 122 | "Velocity (m/s)" 123 | [] 124 | [ Plot.Line "Single Stage" v1Stage 125 | , Plot.Line "Two Stage" v2Stage ] 126 | -------------------------------------------------------------------------------- /src/Solutions/Staging.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Solutions.Staging 3 | Description : Solutions for the 'Staging' module. 4 | -} 5 | {-# LANGUAGE TypeOperators #-} 6 | module Solutions.Staging where 7 | 8 | import Data.LinearMap ((:-*), linear) 9 | import Data.List.NonEmpty (NonEmpty) 10 | import qualified Data.List.NonEmpty as NonEmpty 11 | import Data.VectorSpace ((^*)) 12 | 13 | import qualified ODE 14 | import qualified Staging.Types as T 15 | 16 | 17 | -- | Equation of motion for a rocket stage. 18 | -- 19 | -- The rocket engine is burning in a vacuum in the absence of gravity. 20 | equationOfMotion 21 | :: Double -- ^ Mass flow rate (kg / s). 22 | -> Double -- ^ Specific impulse (s). 23 | -> Double -- ^ Dry mass of this stage (kg). 24 | -> Double -- ^ Total mass of remaining stages (kg). 25 | -> (Double, T.State) -- ^ Current time and state (s, State). 26 | -> Double :-* T.DState -- ^ Linear map of derivatives. 27 | equationOfMotion mDot isp mDry mRemaining (_, state) = linear ((^*) grad) 28 | where 29 | -- grad is a DState for a unit time increment 30 | grad :: T.DState 31 | grad = T.DState 32 | { T.dPropellantMass = -mDot 33 | , T.dPosition = v 34 | , T.dVelocity = g0 * isp * mDot / (mp + mDry + mRemaining) 35 | } 36 | 37 | v = T.velocity state 38 | mp = T.propellantMass state 39 | 40 | g0 :: Double -- m/s^2 41 | g0 = 9.80665 42 | 43 | 44 | -- | Burn a stage of a rocket (can be the first or second stage). 45 | burnStage 46 | :: Double -- ^ Initial propellant mass (kg). 47 | -> Double -- ^ Dry mass of the current stage (kg). 48 | -> Double -- ^ Total mass of remaining mass of stages (kg). 49 | -> Int -- ^ Number of integration steps. 50 | -> Double -- ^ Initial time (s). 51 | -> T.State -- ^ Initial state. 52 | -> NonEmpty (Double, T.State) -- ^ List of time and state tuples. 53 | burnStage mp0 mDry mRemaining nSteps time0 state0 54 | = ODE.integrate ODE.rk4Step state0 times gradFn 55 | where 56 | -- Duration of the burn (s). 57 | burnDuration :: Double 58 | burnDuration = mp0 / mDot 59 | 60 | -- Evaluation times (s). 61 | times :: NonEmpty Double 62 | times = NonEmpty.fromList 63 | $ ODE.linspace nSteps time0 (time0 + burnDuration) 64 | 65 | -- Gradient function for ODE solver. 66 | gradFn :: (Double, T.State) -> Double :-* T.DState 67 | gradFn = equationOfMotion mDot isp mDry mRemaining 68 | 69 | -- Mass flow rate (kg / s). 70 | mDot :: Double 71 | mDot = 290.0 72 | 73 | -- Specific impulse (s). 74 | isp :: Double 75 | isp = 300.0 76 | 77 | 78 | -- | Burn the single stage rocket only. 79 | burnSingleStage 80 | :: Int -- ^ Number of integration steps per stage. 81 | -> NonEmpty (Double, T.State) -- ^ Time and state list. 82 | burnSingleStage nSteps = burnStage mp0 mDry 0.0 nSteps 0.0 state0 83 | where 84 | -- Initial state. 85 | state0 :: T.State 86 | state0 = T.State 87 | { T.propellantMass = mp0 88 | , T.position = 0.0 89 | , T.velocity = 0.0 90 | } 91 | 92 | -- Starting propellant mass (kg). 93 | mp0 :: Double 94 | mp0 = 500000.0 95 | 96 | -- Dry mass of the stage (kg). 97 | mDry :: Double 98 | mDry = 55556.0 99 | 100 | 101 | -- | Burn the two stage rocket only. 102 | burnTwoStage 103 | :: Int -- ^ Number of integration steps per stage. 104 | -> NonEmpty (Double, T.State) -- ^ Time and state list. 105 | burnTwoStage nSteps = stage1 <> stage2 106 | where 107 | -- Results from burning the first stage. 108 | stage1 :: NonEmpty (Double, T.State) 109 | stage1 = burnStage mp0 mDry (mp0 + mDry) nSteps 0.0 state01 110 | 111 | -- Results from burning the second stage. 112 | stage2 :: NonEmpty (Double, T.State) 113 | stage2 = burnStage mp0 mDry 0 nSteps time2 state02 114 | 115 | -- Initial state of the first stage. 116 | state01 :: T.State 117 | state01 = T.State 118 | { T.propellantMass = mp0 119 | , T.position = 0.0 120 | , T.velocity = 0.0 121 | } 122 | 123 | -- Initial state of the second stage. 124 | state02 :: T.State 125 | state02 = (snd (NonEmpty.last stage1)) { T.propellantMass = mp0 } 126 | 127 | -- Starting time of the second stage. 128 | time2 :: Double 129 | time2 = fst (NonEmpty.last stage1) 130 | 131 | -- Starting propellant mass for each stage. 132 | mp0 :: Double 133 | mp0 = 250000.0 134 | 135 | -- Dry mass for each stage. 136 | mDry :: Double 137 | mDry = 27778.0 138 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Hohmann.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Hohmann 3 | Description : Simulation of Hohmann transfer. 4 | -} 5 | {-# LANGUAGE NegativeLiterals #-} 6 | {-# LANGUAGE OverloadedStrings #-} 7 | {-# LANGUAGE TypeOperators #-} 8 | {-# OPTIONS_GHC -Wno-unused-imports #-} -- re-enable after completing solutions 9 | module Hohmann where 10 | 11 | import Control.Lens ((^.)) 12 | import Data.LinearMap ((:-*), linear) 13 | import Data.List.NonEmpty (NonEmpty) 14 | import qualified Data.List.NonEmpty as NonEmpty 15 | import qualified Diagrams.Prelude as D 16 | import Linear (V2 (V2), norm, normalize, (*^), (^+^), 17 | (^/), _x, _y) 18 | 19 | import qualified Hohmann.Types as T 20 | import qualified ODE as ODE 21 | import qualified Plot as Plot 22 | import qualified Solutions.Hohmann 23 | import Todo (FallbackSolution (FallbackSolution), todo) 24 | 25 | 26 | -- | Standard earth gravity. 27 | g0 :: Double 28 | g0 = 9.80665 -- m/s^2 29 | 30 | 31 | -- | Find the craft angle (theta) from the state. 32 | angle :: T.State -> Double 33 | angle = todo (FallbackSolution Solutions.Hohmann.angle) 34 | 35 | 36 | -- | Find the gravitational force from the state. 37 | gravity :: T.Params -> T.State -> V2 Double 38 | gravity = todo (FallbackSolution Solutions.Hohmann.gravity) 39 | 40 | 41 | -- | Find the thrust force from the state. 42 | thrust :: T.Params -> T.State -> V2 Double 43 | thrust = todo (FallbackSolution Solutions.Hohmann.thrust) 44 | 45 | 46 | -- | Return @Nothing@ when @shouldTerminate == True@; @Just@ otherwise. 47 | terminateWhen 48 | :: Bool -- ^ when True, terminate 49 | -> a -- ^ value to wrap 50 | -> Maybe a 51 | terminateWhen {- shouldTerminate value -} 52 | = todo (FallbackSolution Solutions.Hohmann.terminateWhen) 53 | 54 | 55 | -- | Compute a coasting trajectory. 56 | -- 57 | -- This will involve using ODE.integrateTerminating, with the ODE for coasting 58 | -- (see the notes). 59 | coast 60 | :: T.Params -- ^ Simulation parameters. 61 | -> (T.State -> Bool) -- ^ Termination condition (@True@ to terminate). 62 | -> (Double, T.State) -- ^ Initial time and state. 63 | -> NonEmpty (Double, T.State) -- ^ Simulation steps. 64 | coast = todo (FallbackSolution Solutions.Hohmann.coast) 65 | 66 | 67 | -- | Compute a burn trajectory. 68 | -- 69 | -- The burn trajectory is terminated once the craft reaches a given 70 | -- velocity magnitude. 71 | -- 72 | -- This will involve using ODE.integrateTerminating, with the ODE for a burn 73 | -- (see the notes). 74 | burn 75 | :: T.Params -- ^ Simulation parameters. 76 | -> Double -- ^ Termination velocity (m/s). 77 | -> (Double, T.State) -- ^ Initial time and state. 78 | -> NonEmpty (Double, T.State) -- ^ Simulation steps. 79 | burn = todo (FallbackSolution Solutions.Hohmann.burn) 80 | 81 | 82 | -- | Plots a simulation of the Hohmann transfer with a normal, high-impulse 83 | -- burn. 84 | plotHighImpulseBurn :: Plot.Output -> IO () 85 | plotHighImpulseBurn output = plotBurn output 0.1 310 5.13 86 | 87 | 88 | -- | Plots a simulation of the Hohmann transfer with a low-impulse burn. 89 | plotLowImpulseBurn :: Plot.Output -> IO () 90 | plotLowImpulseBurn output = plotBurn output 2 200 0.1 91 | 92 | 93 | -- | Plots a simulation of the Hohmann transfer with an ultra-low-impulse burn. 94 | plotUltraLowImpulseBurn :: Plot.Output -> IO () 95 | plotUltraLowImpulseBurn output = plotBurn output 5 75 0.1 96 | 97 | 98 | -- | General Hohmann transfer simulation. 99 | plotBurn 100 | :: Plot.Output -- ^ Plotting device for output. 101 | -> Double -- ^ dt to use for the burn phase (s). 102 | -> Double -- ^ specific impulse (s) 103 | -> Double -- ^ mass flow rate (kg/s) 104 | -> IO () 105 | plotBurn output burndt isp mDot = do 106 | let 107 | -- Convert simulation result to a trajectory using km units. 108 | kmTrajectory :: NonEmpty (Double, T.State) -> [(Double, Double)] 109 | kmTrajectory = fmap (\(_, state) -> 110 | let p = T.position state 111 | in (p^._x / 1000.0, p^._y / 1000.0)) 112 | . NonEmpty.toList 113 | 114 | -- Simulation parameters. 115 | params :: T.Params 116 | params = T.Params 117 | { T.mu = 4.905e12 -- value for the moon (m^3/s^2) 118 | , T.mDot = mDot -- kg/s 119 | , T.isp = isp -- Specific impulse (s) 120 | , T.r1 = 1780e3 -- inner radius (m) ; ~20 nautical miles 121 | , T.r2 = 1860e3 -- outer radius (m) ; ~60 nautical miles 122 | , T.tStep = 10 -- time step (s) 123 | , T.tEps = 0.01 -- event accuracy (s) 124 | } 125 | 126 | -- Initial state at some radius. 127 | state0 :: Double -> T.State 128 | state0 radius = T.State 129 | { T.mass = 3000 -- kg 130 | , T.distance = 0 -- m 131 | , T.position = V2 radius 0 132 | , T.velocity = V2 0 (sqrt(T.mu params / radius)) 133 | } 134 | 135 | -- Parameters for a burn. 136 | burnParams :: T.Params 137 | burnParams = params { T.tStep = burndt } 138 | 139 | -- Periapsis velocity of transfer trajectory. This is use as a terminal 140 | -- velocity for the first burn. 141 | vp = sqrt(T.mu burnParams * (2 / T.r1 burnParams - 2 / (T.r1 burnParams + T.r2 burnParams))) 142 | 143 | -- Velocity of final outer circular orbit. This is used as a terminal 144 | -- velocity for the second burn. 145 | vf = sqrt(T.mu params / T.r2 params) 146 | 147 | -- Trajectories. 148 | coastInner = coast params 149 | -- termination is complicated because angles are periodic 150 | (\state -> angle state >= 0 151 | && angle state < pi/2 152 | && T.distance state > 1e7) 153 | (0, state0 (T.r1 params)) 154 | burn1 = burn burnParams vp (0, state0 (T.r1 params)) 155 | transferCoast = coast params (\state -> angle state >= pi) (NonEmpty.last burn1) 156 | burn2 = burn burnParams vf (NonEmpty.last transferCoast) 157 | coastOuter = coast params 158 | -- termination is complicated because angles are periodic 159 | (\state -> angle state >= pi/2 160 | && angle state < 3*pi/2 161 | && T.distance state > 2e7) 162 | (NonEmpty.last burn2) 163 | 164 | Plot.plotOrbitSystem output 165 | 20.0 166 | (Plot.OrbitSystem 167 | { Plot.planet = (Plot.Planet "Moon" 1737.1 D.lightgray) 168 | , Plot.systemItems = 169 | [ Plot.Trajectory (kmTrajectory burn1) D.red 170 | , Plot.Trajectory (kmTrajectory transferCoast) D.black 171 | , Plot.Trajectory (kmTrajectory burn2) D.red 172 | , Plot.Trajectory (kmTrajectory coastInner) D.skyblue 173 | , Plot.Trajectory (kmTrajectory coastOuter) D.skyblue 174 | , Plot.AltitudeCircle 1780 "40 km" D.gray 175 | , Plot.AltitudeCircle 1820 "80 km" D.gray 176 | , Plot.AltitudeCircle 1860 "120 km" D.gray 177 | ]}) 178 | -------------------------------------------------------------------------------- /notes/references.bib: -------------------------------------------------------------------------------- 1 | @book{battin1999, 2 | author = "Richard H. Battin", 3 | title = "An introduction to the Mathematics and Methods of Astrodynamics, Revised Edition", 4 | publisher = "American Institute of Aeronautics and Astronautics", 5 | year = "1999" 6 | } 7 | 8 | @article{bennett1970, 9 | author = {F. V. Bennett}, 10 | title = "{Apollo Lunar Descent and Ascent Trajectories. NASA TM X-58040}", 11 | year = 1970, 12 | journal = {AIAA 8th Aerospace Sciences Meeting, New York}, 13 | url = {https://www.hq.nasa.gov/alsj/nasa58040.pdf} 14 | } 15 | 16 | @book{brauer1977, 17 | author = {G. L. Brauer and D. E. Cornick and R. Stevenson}, 18 | title = {Capabilities and Applications of the Program to Optimize Simulated Trajectories (POST). Program Summary Document. NASA CR-2770}, 19 | year = 1977, 20 | publisher = {NASA}, 21 | url = {https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770012832.pdf} 22 | } 23 | 24 | @article{davis2017, 25 | author = {Diane C. Davis and Sean M. Phillips and Kathleen C. Howell and Srianish Vutukuri and Brian P. McCarthy}, 26 | title = "{Stationkeeping and Transfer Trajectory Design for Spacecraft in Cislunar Space}", 27 | year = 2017, 28 | journal = {AAS/AIAA Astrodynamics Specialist Conference, Columbia River Gorge, Stevenson, Washington}, 29 | url = {https://engineering.purdue.edu/people/kathleen.howell.1/Publications/Conferences/2017_AAS_DavPhiHow.pdf} 30 | } 31 | 32 | @misc{dennis2018, 33 | author = {Michael Aaron Dennis}, 34 | title = "{Encyclopaedia Britannica: Charles Stark Draper}", 35 | url = {https://www.britannica.com/biography/Charles-Stark-Draper} 36 | } 37 | 38 | @InProceedings{dileep2015, 39 | author = "M. V. Dileep and Surekha Kamath and Vishnu G. Nair", 40 | title = "Particle Swarm Optimization Applied to Ascent Phase Launch Vehicle Trajectory Optimization Problem", 41 | booktitle = "11th International Multi-Conference on Information Processing", 42 | year = 2015, 43 | doi = {10.1016/j.procs.2015.06.059}, 44 | url = {https://core.ac.uk/download/pdf/82182140.pdf} 45 | } 46 | 47 | @article{dukeman2008, 48 | author = {Greg A. Dukeman and Ashley D. Hill}, 49 | title = "{Rapid Trajectory Optimization for the ARES I Launch Vehicle}", 50 | year = 2008, 51 | journal = {AIAA Guidance, Navigation and Control Conference and Exhibit, Honolulu, Hawaii}, 52 | url = {https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20080048217.pdf} 53 | } 54 | 55 | @InProceedings{elliott2009, 56 | author = "Conal Elliott", 57 | title = "Beautiful differentiation", 58 | booktitle = "International Conference on Functional Programming (ICFP)", 59 | year = 2009, 60 | url = {http://conal.net/papers/beautiful-differentiation}, 61 | } 62 | 63 | @book{gantmacher1950, 64 | author = "F. R. Gantmacher and L. M. Levin", 65 | title = "Equations of Motion of a Rocket (Technical Memorandum 1255)", 66 | publisher = "National Advisory Committee for Aeronautics (NACA)", 67 | year = "1950", 68 | url = {https://apps.dtic.mil/dtic/tr/fulltext/u2/a801581.pdf} 69 | } 70 | 71 | @InProceedings{gerberich2013, 72 | author = "Matthew W. Gerberich and Steven R. Oleson", 73 | title = "{Estimation Model of Spacecraft Parameters and Cost Based on a Statistical Analysis of COMPASS Designs}", 74 | booktitle = "AIAA Space 2013 Conference and Exposition", 75 | year = 2013, 76 | url = {https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20140011472.pdf}, 77 | doi = {10.2514/6.2013-5432} 78 | } 79 | 80 | @article{guelman1991, 81 | author = {M. Guelman}, 82 | title = "{Guidance for asteroid rendezvous}", 83 | year = 1991, 84 | pages = {1080--1083}, 85 | volume = 14, 86 | number = 5, 87 | journal = {Journal of Guidance, Control and Dynamics}, 88 | doi = {10.2514/3.20759}, 89 | url = {https://sci-hub.se/10.2514/3.20759} 90 | } 91 | 92 | @InProceedings{holt2009, 93 | author = "James B. Holt and Timothy S. Monk", 94 | title = "{Propellant Mass Fraction Calculation Methodology for Launch Vehicles and Application to Ares Vehicles}", 95 | booktitle = "AIAA Space 2009 Conference and Exposition. Session ST-2: Advanced Vehicle Systems II", 96 | year = 2009, 97 | url = {https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20090037584.pdf}, 98 | doi = {10.2514/6.2009-6655} 99 | } 100 | 101 | @book{huzel1967, 102 | author = "Dieter K. Huzel and David H. Huang", 103 | title = "Design of Liquid Propellant Rocket Engines, 2nd Edition. NASA SP-125", 104 | publisher = "Office of Technology Utilization, NASA", 105 | year = "1967 (may be 1971 -- date uncertain)", 106 | url = {https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19710019929.pdf} 107 | } 108 | 109 | @article{kephart1971, 110 | author = "D. C. Kephart", 111 | title = "{BOOST: On-Line Computer Program for Estimating Powered-Rocket Performance. R-670-PR}", 112 | publisher = "United States Air Force Project Rand", 113 | year = 1971, 114 | url = {https://www.rand.org/content/dam/rand/pubs/reports/2006/R670.pdf} 115 | } 116 | 117 | @article{kibbey2015, 118 | author = "T. P. Kibbey", 119 | title = "Rho-{I}sp Revisited and Basic Stage Mass Estimating for Launch Vehicle Conceptual Sizing Studies", 120 | journal = "AIAA/SAE/ASEE Joint Propulsion Conference, AIAA Propulsion and Energy Forum", 121 | publisher = "American Institute of Aeronautics and Astronautics", 122 | year = "2015", 123 | doi = {10.2514/6.2015-3791}, 124 | url = {https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20150016561.pdf} 125 | } 126 | 127 | @book{levine1971, 128 | editor = "G. M. Levine", 129 | title = "Apollo: Guidance, Navigation and Control", 130 | volume = "Section 5: Guidance Equations (Rev.~11)", 131 | publisher = "Charles Stark Draper Laboratory, MIT", 132 | year = "1971", 133 | url = {https://www.ibiblio.org/apollo/Documents/j2-80-R-567-SEC5-REV11_text.pdf} 134 | } 135 | 136 | @book{marble1964, 137 | editor = "F. E. Marble", 138 | title = "Spacecraft Propulsion", 139 | publisher = "Space Technology Summer Institute. California Institute of Technology", 140 | year = "1964", 141 | url = {https://authors.library.caltech.edu/62399/1/ST-3.pdf} 142 | } 143 | 144 | @book{maynard1966, 145 | author = "O. E. Maynard and J. R. Sevier", 146 | title = "Apollo Lunar Landing Mission Symposium", 147 | publisher = "NASA", 148 | year = "1966", 149 | volume = "1, General Mission Summary" 150 | } 151 | 152 | @book{press2007, 153 | author = {Press, William H. and Teukolsky, Saul A. and Vetterling, William T. and Flannery, Brian P.}, 154 | title = {Numerical Recipes 3rd Edition: The Art of Scientific Computing}, 155 | year = {2007}, 156 | isbn = {0521880688, 9780521880688}, 157 | edition = {3}, 158 | publisher = {Cambridge University Press}, 159 | address = {New York, NY, USA}, 160 | } 161 | 162 | @article{prince2011, 163 | author = {Jill L. Prince and Prasun N. Desai and Eric M. Queen and Myron R. Grover}, 164 | title = {Mars Phoenix Entry, Descent and Landing Simulation Design and Modelling Analysis}, 165 | journal = {Journal of Spacecraft and Rockets}, 166 | volume = 48, 167 | number = 5, 168 | pages = {754--764}, 169 | year = 2011, 170 | doi = {10.2514/1.46561}, 171 | url = {https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/20080033126.pdf} 172 | } 173 | 174 | @article{rein2017, 175 | author = {Rein, Hanno and Tamayo, Daniel}, 176 | title = "{JANUS: a bit-wise reversible integrator for N-body dynamics}", 177 | journal = {Monthly Notices of the Royal Astronomical Society}, 178 | volume = {473}, 179 | number = {3}, 180 | pages = {3351--3357}, 181 | year = {2017}, 182 | month = {09}, 183 | issn = {0035-8711}, 184 | doi = {10.1093/mnras/stx2479}, 185 | url = {https://arxiv.org/abs/1704.07715}, 186 | } 187 | 188 | @article{springmann2004, 189 | author = "Philip N. Springmann and Olivier L. de Weck", 190 | title = "Parametric Scaling Model for Nongeosynchronous Communications Satellites", 191 | journal = "Journal of Spacecraft and Rockets", 192 | volume = {41}, 193 | number = {3}, 194 | pages = {472--477}, 195 | year = {2004}, 196 | month = {05}, 197 | doi = {10.2514/1.6220}, 198 | url = {http://web.mit.edu/deweck/www/PDF_archive/2%20Refereed%20Journal/2_3_JSR_parametric_NGSO.pdf} 199 | } 200 | 201 | @book{townsend1968, 202 | author = {G. E. Townsend and A. S. Abbott and R. R. Palmer}, 203 | title = "{Guidance, Flight Mechanics and Trajectory Optimization. NASA CR-1007}", 204 | year = {1968}, 205 | volume = "{VIII -- Boost Guidance Equations}", 206 | publisher = "{NASA}", 207 | url = {https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19680010980.pdf} 208 | } 209 | 210 | @book{widnall2008, 211 | author = {S. Widnall and J. Peraire}, 212 | title = "{16.07 Dynamics, Lecture L17 -- Orbit Transfers and Interplanetary Trajectories. MIT Open Courseware}", 213 | year = {2008}, 214 | publisher = "{MIT}", 215 | url = {https://ocw.mit.edu/courses/aeronautics-and-astronautics/16-07-dynamics-fall-2009/lecture-notes/MIT16_07F09_Lec17.pdf} 216 | } -------------------------------------------------------------------------------- /src/LunarAscent/AGC.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ExplicitNamespaces #-} 2 | {-# LANGUAGE FlexibleContexts #-} 3 | {-# LANGUAGE NegativeLiterals #-} 4 | {-# LANGUAGE QuasiQuotes #-} 5 | {-# LANGUAGE ScopedTypeVariables #-} 6 | {-# LANGUAGE TypeApplications #-} 7 | {-# LANGUAGE TypeFamilies #-} 8 | {-# LANGUAGE TypeOperators #-} 9 | module LunarAscent.AGC where 10 | 11 | import Control.Lens ((^.)) 12 | import Data.Basis (Basis, HasBasis) 13 | import Data.VectorSpace (InnerSpace, Scalar, VectorSpace, zeroV) 14 | 15 | import LunarAscent.Types (AGCCommand (AGCCommand), 16 | AGCState (AGCState), AscentTarget, 17 | Constants, EngineShutoff (EngineShutoff), 18 | LVCS, MFCS, P2, Sim, 19 | ThrustAngle (ThrustAngle), V2, accel, 20 | agcState, apsExhaustVelocity, 21 | apsMassFlowRate, bThreshold, dynamics, mass, 22 | moonSGP, pos, prevThrustAngle, qcsAssignV2, 23 | qv2, rDotFLVPEnd, t2_HoldAll, 24 | t3_PositionControl, tEngineThreshold, 25 | targetRadius, targetVel, tgo, time, vel) 26 | import Units (type (@+), Qu, qMagnitude, qNegate, 27 | qNormalized, qSq, si, ( # ), (%), (%.), 28 | (*|), (|*^|), (|*|), (|+|), (|-|), (|.+^|), 29 | (|.-.|), (|.|), (|/), (|/|), (|^*|)) 30 | import qualified Units as U 31 | 32 | 33 | -- | Ascent guidance program P12. 34 | p12 35 | :: forall a. 36 | ( InnerSpace a, a ~ Scalar a, Floating a, Ord a, Basis a ~ (), HasBasis a ) 37 | => Constants a 38 | -> AscentTarget a 39 | -> Sim a 40 | -> (AGCCommand a, AGCState a) 41 | p12 constants target sim = 42 | let 43 | -- convert position and velocity from moon-fixed coordinates 44 | -- (MFCS) to local vertical coordinates (LVCS) 45 | p_l :: U.Length (P2 LVCS a) 46 | v_l :: U.Velocity (V2 LVCS a) 47 | a_l :: U.Acceleration (V2 LVCS a) 48 | (p_l, v_l, a_l) = mfcsToLocal (sim^.dynamics.pos) 49 | (sim^.dynamics.vel) 50 | (sim^.accel) 51 | p_lv :: U.Length (V2 LVCS a) 52 | p_lv = p_l |.-.| (zeroV %. [si| m |]) -- LVCS position as a vector 53 | 54 | -- unit vector in the radial direction of the local coordinate system 55 | u_R = qv2 @LVCS 1 0 56 | 57 | -- radial position and velocity 58 | r = qMagnitude p_lv 59 | rDot = v_l |.| u_R 60 | 61 | -- target radial position and velocity 62 | r_D = target^.targetRadius 63 | rDot_D = target^.targetVel |.| u_R 64 | 65 | -- v_G' - the velocity to be gained, which is the target velocity 66 | -- minus current LVCS velocity 67 | v_G' = target^.targetVel |-| v_l 68 | 69 | -- compensate v_G' for g_eff 70 | g_N = averageG (constants^.moonSGP) p_l v_l a_l (2 % [si| s |]) 71 | g_eff = qSq (p_lv `qCross2D` v_l) |/| (r |*| r |*| r) |-| qMagnitude g_N 72 | v_G = v_G' |-| (0.5 *| sim^.agcState.tgo |*| g_eff |*^| u_R) 73 | 74 | -- update time-to-go estimate 75 | tau = sim^.dynamics.mass |/| constants^.apsMassFlowRate 76 | ve = constants^.apsExhaustVelocity 77 | v_G_mag = qMagnitude v_G 78 | tgo' = tau |*| v_G_mag |/| ve |*| (1 |-| 0.5 *| v_G_mag |/| ve) -- Apollo original 79 | -- tgo' = tau |*| (1 |-| exp (-1 *| v_G_mag |/| ve)) -- more accurate Tsiolkovsky solution 80 | 81 | -- compute control rate signals 82 | l = log (1 |-| tgo' |/| tau) -- ~= -1 *| v_G_mag |/| ve 83 | d12 = tau |+| tgo' |/| l 84 | d21 = tgo' |-| d12 85 | e = (tgo' |/ 2) |-| d21 86 | b' = (d21 |*| (rDot_D |-| rDot) |-| (r_D |-| r |-| rDot |^*| tgo')) 87 | |/| (tgo' |*| e) 88 | -- "B" control parameter 89 | b 90 | -- in "Injection Position Control" mode, the B parameter is set to zero 91 | | tgo' < constants^.t3_PositionControl = 0 % [si| m/s^2 |] 92 | -- B parameter must be negative; it's clamped at zero 93 | | b' > 0 % [si| m/s^2 |] = 0 % [si| m/s^2 |] 94 | -- the B parameter has a low threshold that it can't drop below 95 | | b' < constants^.bThreshold |*| tau = constants^.bThreshold |*| tau 96 | -- in between the lower and upper thresholds; use the computed value 97 | | otherwise = b' 98 | -- "A" control parameter. 99 | a = (-1) *| b |*| d12 |-| (rDot_D |-| rDot) |/| l 100 | 101 | -- compute thrust angle 102 | aTR = (a |+| (1 % [si| s |]) |*| b) |/| tau |-| g_eff -- desired rad accel 103 | -- In the real ACG, the maximum acceleration is computed from 104 | -- filtered measurements of thrust; that's not necessary 105 | -- here... just using the nominal value based on the exhaust 106 | -- velocity. 107 | aMax = constants^.apsExhaustVelocity |/| tau -- maximum acceleration 108 | angle = if aTR > aMax 109 | -- if we request more radial thrust than the maximum 110 | -- possible acceleration, then just command the thrust 111 | -- straight down 112 | then ThrustAngle 0 113 | -- otherwise, compute the thrust angle by comparing the 114 | -- required radial thrust to the total available thrust 115 | else ThrustAngle $ acos (aTR |/| aMax) # [si| |] 116 | 117 | -- near the end of the burn, we schedule the engine to cut-off; 118 | -- this is done once the time-to-go has passed a threshold point 119 | shutoff = if tgo' < constants^.tEngineThreshold 120 | then Just (EngineShutoff (sim^.time |+| tgo')) 121 | else Nothing 122 | 123 | -- compute the new commanded thrust angle 124 | thrustAngle 125 | -- during the vertical ascent we use a fixed zero thrust 126 | -- angle. vertical ascent is any time prior to achieving a 127 | -- vertical velocity of (nominally) 40feet-per-second 128 | | rDot < constants^.rDotFLVPEnd = ThrustAngle 0 129 | -- during the final control-hold stage (nominally 2 sec), we use 130 | -- the previously-commanded thrust angle 131 | | tgo' < constants^.t2_HoldAll = sim^.agcState.prevThrustAngle 132 | -- all other times; use the computed control thrust angle 133 | | otherwise = angle 134 | in 135 | (AGCCommand thrustAngle shutoff, AGCState tgo' thrustAngle) 136 | 137 | 138 | -- | Convert position and velocity in moon-fixed coordinates ('MFCS') 139 | -- to local vertical coordinates ('LVCS'). 140 | mfcsToLocal 141 | :: forall a. 142 | ( InnerSpace a, Floating (Scalar a), Num a ) 143 | => U.Length (P2 MFCS a) -- ^ Position in moon-fixed coords. 144 | -> U.Velocity (V2 MFCS a) -- ^ Velocity in moon-fixed coords. 145 | -> U.Acceleration (V2 MFCS a) -- ^ Acceleration in moon-fixed coords. 146 | -> ( U.Length (P2 LVCS a) 147 | , U.Velocity (V2 LVCS a) 148 | , U.Acceleration (V2 LVCS a) ) 149 | mfcsToLocal p v a = 150 | let 151 | pv = qcsAssignV2 $ p |.-.| (zeroV %. [si| m |]) 152 | 153 | u_R = qNormalized $ pv 154 | u_Z = qRotatedCW u_R 155 | 156 | r_LVCS = qv2 @LVCS 1 0 157 | z_LVCS = qv2 @LVCS 0 1 158 | 159 | p' = (zeroV %. [si| m |]) 160 | |.+^| ((pv |.| u_R) |*^| r_LVCS) 161 | |.+^| ((pv |.| u_Z) |*^| z_LVCS) 162 | v' = ((v |.| u_R) |*^| r_LVCS) 163 | |+| ((v |.| u_Z) |*^| z_LVCS) 164 | a' = ((a |.| u_R) |*^| r_LVCS) 165 | |+| ((a |.| u_Z) |*^| z_LVCS) 166 | in 167 | (p', v', a') 168 | 169 | 170 | -- | Rotate vector 90 degrees clockwise. 171 | qRotatedCW 172 | :: forall d l c a. 173 | ( VectorSpace a, Num a, InnerSpace a 174 | , U.Normalize (U.Normalize d) ~ U.Normalize d ) 175 | => U.Qu d l (V2 c a) -> U.Qu (U.Normalize d) l (V2 c a) 176 | qRotatedCW v = 177 | let 178 | i = qv2 1 0 179 | j = qv2 0 1 180 | x = v |.| i 181 | y = v |.| j 182 | x' = y 183 | y' = qNegate x 184 | in 185 | (x' |*^| i) |+| (y' |*^| j) 186 | 187 | 188 | -- | Instantaneous acceleration due to gravity. 189 | gravAccel 190 | :: forall a c. 191 | ( InnerSpace a, Floating a, a ~ Scalar a ) 192 | => U.SGPUnit a 193 | -> U.Length (P2 c a) 194 | -> U.Acceleration (V2 c a) 195 | gravAccel mu r = 196 | let 197 | vr = r |.-.| (zeroV %. [si| m |]) 198 | rhat = U.qNormalized vr 199 | r2 = U.qMagnitudeSq vr 200 | in 201 | -1 *| mu |/| r2 |*^| rhat 202 | 203 | 204 | -- | Average gravity during a time period. 205 | averageG 206 | :: ( InnerSpace a, a ~ Scalar a, Floating a ) 207 | => U.SGPUnit a -- Specific gravitational parameter. 208 | -> U.Length (P2 LVCS a) -- Position of the vehicle. 209 | -> U.Velocity (V2 LVCS a) -- Velocity of the vehicle. 210 | -> U.Acceleration (V2 LVCS a) -- Acceleration of the vehicle. 211 | -> U.Time a -- Time increment. 212 | -> U.Acceleration (V2 LVCS a) -- Average velocity during the time increment. 213 | averageG mu r v a dt = 214 | let 215 | r' = r |.+^| ((1/8) *| a |^*| (dt |*| dt)) |.+^| ((1/2) *| v |^*| dt) 216 | in 217 | gravAccel mu r' 218 | 219 | 220 | -- | Magnitude of a cross-product between 2D vectors as though they were 3D. 221 | qCross2D 222 | :: ( Scalar a ~ a, InnerSpace a, Num a 223 | , U.Normalize (U.Normalize d1 @+ U.Normalize d2) ~ U.Normalize (d1 @+ d2) ) 224 | => Qu d1 l (V2 c a) 225 | -> Qu d2 l (V2 c a) 226 | -> Qu (U.Normalize (d1 @+ d2)) l a 227 | qCross2D va vb = 228 | let 229 | i = qv2 1 0 230 | j = qv2 0 1 231 | 232 | vax = va |.| i 233 | vay = va |.| j 234 | vbx = vb |.| i 235 | vby = vb |.| j 236 | in 237 | (vax |*| vby) |-| (vay |*| vbx) 238 | -------------------------------------------------------------------------------- /notes/beamer-trek/beamerinnerthemetrek.sty: -------------------------------------------------------------------------------- 1 | \makeatletter 2 | 3 | \def\largecircle{% 4 | \tikz[baseline]{ 5 | \fill (0,0.75ex) circle (0.75ex); 6 | }% 7 | } 8 | 9 | \defbeamertemplate*{itemize item}{trek}{\largecircle} 10 | 11 | \defbeamertemplate*{itemize subitem}{trek}{\largecircle} 12 | 13 | \defbeamertemplate*{itemize subsubitem}{trek}{\largecircle} 14 | 15 | \defbeamertemplate*{itemize/enumerate body begin}{trek}{% 16 | \renewcommand{\theenumi}{\ifnum\value{enumi}<10 0\fi\arabic{enumi}}% 17 | \renewcommand{\theenumii}{\ifnum\value{enumii}<10 0\fi\arabic{enumii}}% 18 | \renewcommand{\theenumiii}{\ifnum\value{enumiii}<10 0\fi\arabic{enumiii}}% 19 | \setlength\labelsep{\the\trek@titlegap}% 20 | %\setlength\itemsep{0pt}% TODO: arrange item separation nicely 21 | } 22 | 23 | \defbeamertemplate*{enumerate item}{trek}{% 24 | % height of "cursor button" 25 | \newdimen\cursor@height% 26 | \setbox0=\hbox{\usebeamerfont{itemize/enumerate body}ML8}% 27 | \cursor@height=\ht0% 28 | \advance\cursor@height by 0.6ex% 29 | % draw the enumerate item 30 | \tikz[baseline]{ 31 | \draw (0,-0.3ex) node [ 32 | xscale=-1, 33 | trek cursor, 34 | trek/cursor/width=\the\trek@cursorwidth, 35 | trek/cursor/height=\the\cursor@height 36 | ] {}; 37 | \draw (-0.3ex,0) node [anchor=south east,inner sep=0,line width=0] {% 38 | \color{black}\insertenumlabel% 39 | }; 40 | \filldraw (\the\trek@buttongap,-0.3ex) rectangle ++(\the\trek@titlegap,\the\cursor@height); 41 | }% 42 | } 43 | 44 | \defbeamertemplate*{enumerate subitem}{trek}{% 45 | % height of "cursor button" 46 | \newdimen\cursor@height% 47 | \setbox0=\hbox{\usebeamerfont{itemize/enumerate subbody}ML8}% 48 | \cursor@height=\ht0% 49 | \advance\cursor@height by 0.6ex% 50 | % width of "00" 51 | \newdimen\prev@width% 52 | \setbox0=\hbox{00}% 53 | \prev@width=\wd0% 54 | % width of cursor 55 | \newdimen\cursor@width% 56 | \cursor@width=\the\trek@cursorwidth% 57 | \advance\cursor@width by \prev@width% 58 | % draw the enumerate subitem 59 | \tikz[baseline]{ 60 | \draw (0,-0.3ex) node [ 61 | xscale=-1, 62 | trek cursor, 63 | trek/cursor/width=\the\cursor@width, 64 | trek/cursor/height=\the\cursor@height 65 | ]{}; 66 | \draw (-0.3ex,0) node [anchor=south east,inner sep=0,line width=0] {% 67 | \color{black}\insertenumlabel-\insertsubenumlabel% 68 | }; 69 | \filldraw (\the\trek@buttongap,-0.3ex) rectangle ++(\the\trek@titlegap,\the\cursor@height); 70 | }% 71 | } 72 | 73 | \defbeamertemplate*{enumerate subsubitem}{trek}% 74 | {% 75 | % height of "cursor button" 76 | \newdimen\cursor@height% 77 | \setbox0=\hbox{\usebeamerfont{itemize/enumerate subsubbody}ML8}% 78 | \cursor@height=\ht0% 79 | \advance\cursor@height by 0.6ex% 80 | % width of "00" 81 | \newdimen\prev@width% 82 | \setbox0=\hbox{00}% 83 | \prev@width=\wd0% 84 | % width of cursor 85 | \newdimen\cursor@width% 86 | \cursor@width=\the\trek@cursorwidth% 87 | \advance\cursor@width by \prev@width% 88 | \advance\cursor@width by \prev@width% 89 | % draw the enumerate subsubitem 90 | \tikz[baseline]{ 91 | \draw (0,-0.3ex) node [ 92 | xscale=-1, 93 | trek cursor, 94 | trek/cursor/width=\the\cursor@width, 95 | trek/cursor/height=\the\cursor@height 96 | ]{}; 97 | \draw (-0.3ex,0) node [anchor=south east,inner sep=0,line width=0] {% 98 | \color{black}\insertenumlabel-\insertsubenumlabel-\insertsubsubenumlabel% 99 | }; 100 | \filldraw (\the\trek@buttongap,-0.3ex) rectangle ++(\the\trek@titlegap,\the\cursor@height); 101 | }% 102 | } 103 | 104 | \defbeamertemplate*{title page}{trek}{% 105 | % Measure size of subtitle, to go in the top right corner 106 | \newdimen\@subtitlewidth% 107 | \newdimen\@subtitleheight% 108 | \setbox0=\hbox{\usebeamerfont{subtitle}\insertsubtitle}% 109 | \@subtitlewidth=\wd0% 110 | \setbox1=\hbox{\usebeamerfont{subtitle}8ML}% 111 | \@subtitleheight=\ht1% 112 | % Compute height of frametitle region 113 | \newdimen\frametitle@height% 114 | \frametitle@height=\@subtitleheight% 115 | \advance \frametitle@height by \trek@margin% 116 | % Measure size of institute, to go in the bottom right corner 117 | \newdimen\@institutewidth% 118 | \newdimen\@instituteheight% 119 | \setbox0=\hbox{\usebeamerfont{institute}\insertinstitute}% 120 | \@institutewidth=\wd0% 121 | \setbox1=\hbox{\usebeamerfont{institute}8ML}% 122 | \@instituteheight=\ht1% 123 | % Origin of the NE "cursor" (right-most knobbly thing) 124 | \newdimen\cursorne@x% 125 | \newdimen\cursorne@y% 126 | \cursorne@x=\paperwidth% 127 | \advance \cursorne@x by -\the\trek@margin% 128 | \advance \cursorne@x by -\the\trek@cursorwidth% 129 | \cursorne@y=\paperheight% 130 | \advance \cursorne@y by -\the\trek@margin% 131 | \advance \cursorne@y by -\the\@subtitleheight% 132 | % Origin of NW "cursor" 133 | \newdimen\cursornw@x% 134 | \newdimen\cursornw@y% 135 | \cursornw@x=\trek@cursorwidth% 136 | \advance \cursornw@x by \trek@margin% 137 | \cursornw@y=\cursorne@y% 138 | % Origin of the title (center of baseline) 139 | \newdimen\subtitle@x% 140 | \newdimen\subtitle@y% 141 | \subtitle@x=-\the\@subtitlewidth% 142 | \divide \subtitle@x by 2% 143 | \advance \subtitle@x by \the\cursorne@x% 144 | \advance \subtitle@x by -\the\trek@titlegap% 145 | \subtitle@y=\the\cursorne@y% 146 | % Top bar 147 | \newdimen\topbar@x% 148 | \newdimen\topbar@y% 149 | \newdimen\topbar@width% 150 | \topbar@x=\the\cursornw@x% 151 | \advance \topbar@x by \the\trek@titlegap% 152 | \topbar@y=\the\cursornw@y% 153 | \topbar@width=\the\paperwidth% 154 | \advance \topbar@width by -\the\topbar@x% 155 | \advance \topbar@width by -\the\trek@margin% 156 | \advance \topbar@width by -\the\@subtitlewidth% 157 | \advance \topbar@width by -\the\trek@cursorwidth% 158 | \advance \topbar@width by -\the\trek@titlegap% 159 | \advance \topbar@width by -\the\trek@titlegap% 160 | % Origin of the SW "cursor" (left-most knobbly thing) 161 | \newdimen\cursorsw@x% 162 | \newdimen\cursorsw@y% 163 | \cursorsw@x=\the\trek@cursorwidth% 164 | \advance \cursorsw@x by \the\trek@margin% 165 | \cursorsw@y=\the\trek@margin% 166 | % Origin of the institute (center of baseline) 167 | \newdimen\institute@x% 168 | \newdimen\institute@y% 169 | \institute@x=\the\@institutewidth% 170 | \divide \institute@x by 2% 171 | \advance \institute@x by \the\cursorsw@x% 172 | \advance \institute@x by \the\trek@titlegap% 173 | \institute@y=\cursorsw@y% 174 | % Bottom bar 175 | \newdimen\bottombar@x% 176 | \newdimen\bottombar@y% 177 | \newdimen\bottombar@width% 178 | \bottombar@x=\the\cursorsw@x% 179 | \advance \bottombar@x by \the\@institutewidth% 180 | \advance \bottombar@x by \the\trek@titlegap% 181 | \advance \bottombar@x by \the\trek@titlegap% 182 | \bottombar@y=\the\cursorsw@y% 183 | \bottombar@width=\the\paperwidth% 184 | \advance \bottombar@width by -\the\bottombar@x% 185 | \advance \bottombar@width by -\the\trek@margin% 186 | \advance \bottombar@width by -\the\trek@cursorwidth% 187 | \advance \bottombar@width by -\the\trek@titlegap% 188 | % Origin of the SE "cursor" 189 | \newdimen\cursorse@x% 190 | \newdimen\cursorse@y% 191 | \cursorse@x=\paperwidth% 192 | \advance\cursorse@x by -\the\trek@margin% 193 | \advance\cursorse@x by -\the\trek@cursorwidth% 194 | \cursorse@y=\the\cursorsw@y% 195 | % Typeset the title 196 | \vspace{-1.2pt}% 197 | \leavevmode% 198 | \hskip-\the\trek@full@sidebar@width% 199 | \hbox{% 200 | \begin{beamercolorbox}[wd=\paperwidth,ht=\paperheight,left]{subtitle}% 201 | \begin{tikzpicture} 202 | \useasboundingbox (0,0) rectangle (\paperwidth,\paperheight); 203 | %\draw [draw=none,fill=red] (0,0) rectangle (\paperwidth,\paperheight); 204 | % NE cursor 205 | \draw (\the\cursorne@x,\the\cursorne@y) node [ 206 | trek cursor, 207 | trek/cursor/width=\the\trek@cursorwidth, 208 | trek/cursor/height=\the\@subtitleheight 209 | ]{}; 210 | % subtitle text 211 | \draw (\the\subtitle@x,\the\subtitle@y) node [anchor=base,inner sep=0] 212 | {\usebeamerfont{subtitle}\insertsubtitle}; 213 | % top bar 214 | \filldraw (\the\topbar@x,\the\topbar@y) rectangle ++(\the\topbar@width,\the\@subtitleheight); 215 | % NW cursor 216 | \draw (\the\cursornw@x,\the\cursornw@y) node [ 217 | trek cursor, 218 | xscale=-1, 219 | trek/cursor/width=\the\trek@cursorwidth, 220 | trek/cursor/height=\the\@subtitleheight 221 | ]{}; 222 | % cursor at left 223 | \draw (\the\cursorsw@x,\the\cursorsw@y) node [ 224 | trek cursor, 225 | xscale=-1, 226 | trek/cursor/width=\the\trek@cursorwidth, 227 | trek/cursor/height=\the\@instituteheight 228 | ]{}; 229 | % institute text 230 | \draw (\the\institute@x,\the\institute@y) node [anchor=base,inner sep=0] 231 | {\usebeamerfont{institute}\insertinstitute}; 232 | % bottom bar 233 | \filldraw (\the\bottombar@x,\the\bottombar@y) rectangle ++(\the\bottombar@width,\the\@subtitleheight); 234 | % SE cursor 235 | \draw (\the\cursorse@x,\the\cursorse@y) node [ 236 | trek cursor, 237 | trek/cursor/width=\the\trek@cursorwidth, 238 | trek/cursor/height=\the\@instituteheight 239 | ]{}; 240 | % logo and title 241 | \draw (\paperwidth/2,\paperheight/2) node [anchor=center] { 242 | \parbox{\paperwidth}{ 243 | \begin{center} 244 | \inserttitlegraphic\par 245 | {\usebeamercolor[fg]{title}\usebeamerfont{title}\vspace{1ex}\inserttitle}\par 246 | {\usebeamercolor[fg]{author}\usebeamerfont{author}\insertauthor}\par 247 | {\usebeamercolor[fg]{date}\usebeamerfont{date}\insertdate}\par 248 | \end{center} 249 | } 250 | }; 251 | \end{tikzpicture}% 252 | \end{beamercolorbox}% 253 | }% 254 | }% 255 | 256 | \makeatother 257 | -------------------------------------------------------------------------------- /src/Solutions/ODE.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Solutions.ODE 3 | Description : Solutions for the 'ODE' module. 4 | -} 5 | {-# LANGUAGE FlexibleContexts #-} 6 | {-# LANGUAGE ScopedTypeVariables #-} 7 | {-# LANGUAGE TypeFamilies #-} 8 | {-# LANGUAGE TypeOperators #-} 9 | module Solutions.ODE 10 | ( -- * Functions 11 | -- ** terminating versions 12 | integrateTerminating 13 | , rk4StepTerminating 14 | -- ** vector-space versions 15 | , integrate 16 | , integrateWithDiff 17 | , rk4Step 18 | , eulerStep 19 | -- ** Euler's method for Double only 20 | , integrateEulerDouble 21 | , eulerStepDouble 22 | ) where 23 | 24 | import Data.AffineSpace (AffineSpace, Diff, (.+^)) 25 | import Data.Basis (Basis, HasBasis) 26 | import Data.LinearMap ((:-*), lapply) 27 | import qualified Data.List as List 28 | import Data.List.NonEmpty (NonEmpty ((:|))) 29 | import qualified Data.List.NonEmpty as NonEmpty 30 | import Data.MemoTrie (HasTrie) 31 | import Data.VectorSpace (Scalar, VectorSpace, (*^), (^+^), (^-^), 32 | (^/)) 33 | 34 | ------------------------------------------------------------------------------- 35 | -- Integration of ODEs with termination 36 | ------------------------------------------------------------------------------- 37 | 38 | -- | Stepper function for ODE integration that can terminate. 39 | type TerminatingStepper time state 40 | = time 41 | -> ((time, state) -> Maybe (time :-* Diff state)) 42 | -> (time, state) 43 | -> Maybe (time, state) 44 | 45 | 46 | -- | Driver for an ODE that terminates itself. 47 | -- 48 | -- Bisection is used to find a terminal value for the ODE at a time whose error 49 | -- is less than @tEpsilon@. 50 | integrateTerminating 51 | :: forall state diff time s. 52 | ( diff ~ Diff state 53 | , HasBasis time 54 | , s ~ Scalar time, Ord time, Fractional s ) 55 | => TerminatingStepper time state -- ^ Stepper to use. 56 | -> time -- ^ Allowable error in the final time. 57 | -> time -- ^ Step size @h@. 58 | -> (time, state) -- ^ Initial state. 59 | -> ((time, state) -> Maybe (time :-* diff)) -- ^ Gradient function. 60 | -> NonEmpty (time, state) -- ^ Computed states. 61 | integrateTerminating stepper tEpsilon h state0 f = 62 | let 63 | 64 | -- Use a list internally, to avoid duplicating starting elements 65 | -- if/when we halve the time step. 66 | listIntegrator 67 | :: time -- ^ dt 68 | -> (time, state) -- ^ initial state 69 | -> [(time, state)] -- ^ list of states produced 70 | listIntegrator dt' state0' = 71 | let 72 | stepFn :: (time, state) -> Maybe ((time, state), (time, state)) 73 | stepFn timeState = stepper dt' f timeState >>= \r -> pure (r, r) 74 | 75 | unfold1 :: [(time, state)] 76 | unfold1 = List.unfoldr stepFn state0' 77 | in 78 | -- If our time step is less than the required error, then we 79 | -- know that our end point is definitely within tEpsilon and 80 | -- we can terminate. Otherwise we have to get closer to the end 81 | -- point, which we can do by halving the time step and acquiring 82 | -- new samples if necessary. 83 | if dt' < tEpsilon 84 | then unfold1 85 | else 86 | let 87 | -- We may not have generated any samples at all so far; so we 88 | -- need to handle that case by starting from the initial state. 89 | state0'' = case unfold1 of 90 | [] -> state0' 91 | _ -> last unfold1 92 | in 93 | unfold1 ++ listIntegrator (dt'^/2) state0'' 94 | 95 | in state0 :| listIntegrator h state0 96 | 97 | 98 | -- | Single step of terminating 4th-order Runge-Kutta integration. 99 | -- 100 | -- A result is only returned if all none of the function evaluations indicate 101 | -- termination. 102 | rk4StepTerminating 103 | :: ( AffineSpace state 104 | , diff ~ Diff state, VectorSpace diff 105 | , HasBasis time, HasTrie (Basis time) 106 | , s ~ Scalar time, s ~ Scalar diff, Fractional s ) 107 | => time -- ^ Step size @h@ 108 | -> ((time, state) -> Maybe (time :-* Diff state)) -- ^ Gradient function @f (x, t)@ 109 | -> (time, state) -- ^ Before the step @(t, x)@ 110 | -> Maybe (time, state) -- ^ Optional @(t, x)@ after the step 111 | rk4StepTerminating h f (t, x) = 112 | let 113 | o6 = 1/6 114 | o3 = 1/3 115 | tf = t ^+^ h 116 | midt = t ^+^ (h ^/ 2) 117 | ldx dxdt = lapply dxdt h 118 | in do 119 | k1 <- ldx <$> f(t, x ) 120 | k2 <- ldx <$> f(midt, x .+^ k1^/2) 121 | k3 <- ldx <$> f(midt, x .+^ k2^/2) 122 | k4 <- ldx <$> f(tf, x .+^ k3 ) 123 | let xf = x .+^ o6*^k1 .+^ o3*^k2 .+^ o3*^k3 .+^ o6*^k4 124 | pure (tf, xf) 125 | 126 | 127 | ------------------------------------------------------------------------------- 128 | -- Integration of ODEs; generalized to work with vector-space classes 129 | ------------------------------------------------------------------------------- 130 | 131 | -- | Integrate an ODE. 132 | integrate 133 | :: forall state diff time. 134 | ( diff ~ Diff state 135 | , HasBasis time ) 136 | => Stepper time state -- ^ Stepper function 137 | -> state -- ^ Initial state 138 | -> NonEmpty time -- ^ Evaluation times 139 | -> ((time, state) -> time :-* diff) -- ^ Gradient function 140 | -> NonEmpty (time, state) -- ^ Computed states 141 | integrate stepper x0 (t0 :| ts) f = 142 | let 143 | stepFn :: (time, state) -> time -> (time, state) 144 | stepFn q@(ti, _) tf = stepper (tf ^-^ ti) f q 145 | in 146 | NonEmpty.scanl stepFn (t0, x0) ts 147 | 148 | 149 | -- | Integrate an ODE, recording the gradient. 150 | integrateWithDiff 151 | :: forall state diff time. 152 | ( diff ~ Diff state 153 | , HasBasis time ) 154 | => Stepper time state -- ^ Stepper function 155 | -> state -- ^ Initial state 156 | -> NonEmpty time -- ^ Evaluation times 157 | -> ((time, state) -> time :-* diff) -- ^ Gradient function 158 | -> NonEmpty (time, state, time :-* diff) -- ^ Computed states 159 | integrateWithDiff stepper x0 (t0 :| ts) f = 160 | let 161 | stepFn 162 | :: (time, state, time :-* diff) 163 | -> time 164 | -> (time, state, time :-* diff) 165 | stepFn (ti, si, _) tf = 166 | let 167 | (t', state') = stepper (tf ^-^ ti) f (ti, si) 168 | in 169 | (t', state', f (ti, si)) 170 | in 171 | NonEmpty.scanl stepFn (t0, x0, f (t0, x0)) ts 172 | 173 | 174 | -- | Stepper function used in ODE integration. 175 | type Stepper time state 176 | = time 177 | -> ((time, state) -> time :-* Diff state) 178 | -> (time, state) 179 | -> (time, state) 180 | 181 | 182 | -- | Single step of 4th-order Runge-Kutta integration. 183 | rk4Step 184 | :: ( AffineSpace state 185 | , diff ~ Diff state, VectorSpace diff 186 | , HasBasis time, HasTrie (Basis time) 187 | , s ~ Scalar diff, s ~ Scalar time, Fractional s ) 188 | => time -- ^ Step size @h@ 189 | -> ((time, state) -> time :-* diff) -- ^ Gradient function @f (x, t)@ 190 | -> (time, state) -- ^ Before the step @(t, x)@ 191 | -> (time, state) -- ^ After the step @(t, x)@ 192 | rk4Step h f (t, x) = 193 | let 194 | o6 = 1/6 195 | o3 = 1/3 196 | tf = t ^+^ h 197 | midt = t ^+^ (h ^/ 2) 198 | 199 | k1 = lapply (f (t, x )) h 200 | k2 = lapply (f (midt, x .+^ k1^/2)) h 201 | k3 = lapply (f (midt, x .+^ k2^/2)) h 202 | k4 = lapply (f (tf, x .+^ k3 )) h 203 | xf = x .+^ o6*^k1 .+^ o3*^k2 .+^ o3*^k3 .+^ o6*^k4 204 | in 205 | (tf, xf) 206 | 207 | 208 | -- | Single step of Euler integration. 209 | eulerStep 210 | :: ( AffineSpace state 211 | , diff ~ Diff state, VectorSpace diff 212 | , HasBasis time, HasTrie (Basis time) 213 | , s ~ Scalar diff, s ~ Scalar time ) 214 | => time -- ^ Step size @h@ 215 | -> ((time, state) -> time :-* diff) -- ^ Gradient function @f (x, t)@ 216 | -> (time, state) -- ^ Before the step @(t, x)@ 217 | -> (time, state) -- ^ After the step @(t, x)@ 218 | eulerStep h f q@(t, x) = (t ^+^ h, x .+^ lapply (f q) h) 219 | 220 | 221 | ------------------------------------------------------------------------------- 222 | -- Euler's method specialized to Double only 223 | ------------------------------------------------------------------------------- 224 | 225 | -- | Integrate an ODE using Euler's method (specialized to 'Double'). 226 | integrateEulerDouble 227 | :: ((Double, Double) -> Double) -- ^ Gradient function @f (t, x)@ 228 | -> Double -- ^ Initial state @x0@ 229 | -> NonEmpty Double -- ^ NonEmpty of @t@ values 230 | -> NonEmpty (Double, Double) -- ^ NonEmpty of @(t, x)@ values 231 | integrateEulerDouble f x0 (t0 :| ts) = 232 | let 233 | stepFn :: (Double, Double) -> Double -> (Double, Double) 234 | stepFn q@(tStart, _) tEnd = eulerStepDouble (tEnd - tStart) f q 235 | in 236 | NonEmpty.scanl stepFn (t0, x0) ts 237 | 238 | 239 | -- | Single step of Euler integration (specialized to 'Double'). 240 | eulerStepDouble 241 | :: Double -- ^ Step size 242 | -> ((Double, Double) -> Double) -- ^ Gradient function @f@ 243 | -> (Double, Double) -- ^ State before the step @(x, y)@ 244 | -> (Double, Double) -- ^ State after the step @(x, y)@ 245 | eulerStepDouble h f q@(t, x) = (t + h, x + (h*f q)) 246 | -------------------------------------------------------------------------------- /src/ODE.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : ODE 3 | Description : Integration of Ordinary Differential Equations. 4 | 5 | Solutions for the problems in this module are contained in the 6 | 'Solutions.ODE' module. 7 | -} 8 | {-# LANGUAGE FlexibleContexts #-} 9 | {-# LANGUAGE NegativeLiterals #-} 10 | {-# LANGUAGE ScopedTypeVariables #-} 11 | {-# LANGUAGE TypeFamilies #-} 12 | {-# LANGUAGE TypeOperators #-} 13 | {-# OPTIONS_GHC -Wno-unused-imports #-} -- re-enable after completing solutions 14 | module ODE 15 | ( -- * Types 16 | Stepper 17 | -- * Functions 18 | -- ** Useful utilities 19 | , linspace 20 | -- ** vector-space versions 21 | , integrate 22 | , integrateWithDiff 23 | , rk4Step 24 | , eulerStep 25 | -- ** self-terminating versions 26 | , integrateTerminating 27 | , rk4StepTerminating 28 | -- ** Euler's method for Double only 29 | , integrateEulerDouble 30 | , eulerStepDouble 31 | ) where 32 | 33 | import Data.AffineSpace (AffineSpace, Diff, (.+^)) 34 | import Data.Basis (Basis, HasBasis) 35 | import Data.LinearMap ((:-*), lapply) 36 | import qualified Data.List as List 37 | import Data.List.NonEmpty (NonEmpty ((:|))) 38 | import qualified Data.List.NonEmpty as NonEmpty 39 | import Data.MemoTrie (HasTrie) 40 | import Data.VectorSpace (Scalar, VectorSpace, (*^), (^+^), (^-^), 41 | (^/)) 42 | 43 | import qualified Solutions.ODE 44 | import Todo (FallbackSolution (FallbackSolution), todo) 45 | 46 | 47 | -- | Single step of Euler integration (specialized to 'Double'). 48 | -- 49 | -- Euler's method specialized to Double. The relevant equations are: 50 | -- 51 | -- tNext = t + h 52 | -- 53 | -- xNext = x + h 54 | -- = x + (dx/dt)*h 55 | -- = x + h * f (t, x) 56 | -- 57 | -- Example: 58 | -- 59 | -- >>> f (_, x) = -0.2 * x -- this is an exponential decay equation 60 | -- >>> eulerStepDouble 1 f (2, 5) 61 | -- (3.0,4.0) 62 | -- 63 | -- @ 64 | -- ^ ^ ^ ^ ^ 65 | -- | | | | | 66 | -- | | | | --- x = 5 before the time step 67 | -- | | | ------ t = 2 before the time step 68 | -- | | ----------- h = 1 is the time step 69 | -- | ---- x = 4 after the time step 70 | -- -------- t = 3 after the time step 71 | -- @ 72 | -- 73 | eulerStepDouble 74 | :: Double -- ^ Step size @h@ 75 | -> ((Double, Double) -> Double) -- ^ Gradient function @f (t, x)@ 76 | -> (Double, Double) -- ^ Time and state before the step @(t, x)@ 77 | -> (Double, Double) -- ^ Time and state after the step @(t, x)@ 78 | eulerStepDouble -- h f q@(t, x) 79 | = todo (FallbackSolution Solutions.ODE.eulerStepDouble) 80 | 81 | 82 | -- | Integrate an ODE using Euler's method (specialized to 'Double'). 83 | -- 84 | -- NOTE: The NonEmpty.scanl function is a convenient way to drive the 85 | -- computation. 86 | -- 87 | -- Example: 88 | -- 89 | -- >>> f (_, x) = -0.2 * x -- this is an exponential decay equation 90 | -- >>> times = NonEmpty.fromList [ 1, 2, 3 ] 91 | -- >>> x0 = 50.0 92 | -- >>> integrateEulerDouble f x0 times 93 | -- (1.0,50.0) :| [(2.0,40.0),(3.0,32.0)] 94 | integrateEulerDouble 95 | :: ((Double, Double) -> Double) -- ^ Gradient function @f (t, x)@ 96 | -> Double -- ^ Initial state @x0@ 97 | -> NonEmpty Double -- ^ NonEmpty of @t@ values 98 | -> NonEmpty (Double, Double) -- ^ NonEmpty of @(t, x)@ values 99 | integrateEulerDouble -- f x0 (t0 :| ts) 100 | = todo (FallbackSolution Solutions.ODE.integrateEulerDouble) 101 | {- Template: 102 | let 103 | stepFn :: (Double, Double) -> Double -> (Double, Double) 104 | stepFn q@(tStart, _) tEnd = -- TODO 105 | in 106 | NonEmpty.scanl stepFn (t0, x0) ts 107 | -} 108 | 109 | 110 | -- | Linearly-spaced samples. 111 | -- 112 | -- Example: 113 | -- 114 | -- >>> linspace 5 0.0 10.0 :: [Double] 115 | -- [0.0,2.0,4.0,6.0,8.0,10.0] 116 | linspace 117 | :: ( VectorSpace a, s ~ Scalar a, Fractional s ) 118 | => Int -- ^ Number of divisions (1 less than the number of samples) 119 | -> a -- ^ Start of the sample range (== first sample) 120 | -> a -- ^ End of the sample range (== last sample for n > 2) 121 | -> [a] -- ^ Samples 122 | linspace n xStart xEnd = 123 | let 124 | n' = fromIntegral n 125 | range = xEnd ^-^ xStart 126 | f i = fromIntegral i *^ range ^/ n' ^+^ xStart 127 | in 128 | [ f i | i <- [0 .. n] ] 129 | 130 | 131 | -- | Single step of Euler integration. 132 | -- 133 | -- Example: 134 | -- 135 | -- >>> f (_, x) = linear $ \dt -> -0.2 * x * dt 136 | -- >>> eulerStep 1 f (2, 5) 137 | -- (3.0,4.0) 138 | eulerStep 139 | :: ( AffineSpace state 140 | , diff ~ Diff state, VectorSpace diff 141 | , HasBasis time, HasTrie (Basis time) 142 | , s ~ Scalar diff, s ~ Scalar time ) 143 | => time -- ^ Step size @h@ 144 | -> ((time, state) -> time :-* diff) -- ^ Gradient function @f (x, t)@ 145 | -> (time, state) -- ^ Before the step @(t, x)@ 146 | -> (time, state) -- ^ After the step @(t, x)@ 147 | eulerStep -- h f (t, x) 148 | = todo (FallbackSolution Solutions.ODE.eulerStep) 149 | 150 | 151 | -- | Stepper function used in ODE integration. 152 | type Stepper time state 153 | = time 154 | -> ((time, state) -> time :-* Diff state) 155 | -> (time, state) 156 | -> (time, state) 157 | 158 | 159 | -- | Integrate an ODE. 160 | -- 161 | -- Example: 162 | -- 163 | -- >>> f (_, x) = linear $ \dt -> -0.2 * x * dt 164 | -- >>> times = NonEmpty.fromList [1, 2, 3] 165 | -- >>> x0 = 50.0 166 | -- >>> integrate eulerStep x0 times f 167 | -- (1.0,50.0) :| [(2.0,40.0),(3.0,32.0)] 168 | integrate 169 | :: ( diff ~ Diff state 170 | , HasBasis time ) 171 | => Stepper time state -- ^ Stepper function 172 | -> state -- ^ Initial state 173 | -> NonEmpty time -- ^ Evaluation times 174 | -> ((time, state) -> time :-* diff) -- ^ Gradient function 175 | -> NonEmpty (time, state) -- ^ Computed states 176 | integrate -- stepper x0 (t0 :| ts) f 177 | = todo (FallbackSolution Solutions.ODE.integrate) 178 | 179 | 180 | -- | Integrate an ODE, recording the gradient. 181 | integrateWithDiff 182 | :: forall state diff time. 183 | ( diff ~ Diff state 184 | , HasBasis time ) 185 | => Stepper time state -- ^ Stepper function 186 | -> state -- ^ Initial state 187 | -> NonEmpty time -- ^ Evaluation times 188 | -> ((time, state) -> time :-* diff) -- ^ Gradient function 189 | -> NonEmpty (time, state, time :-* diff) -- ^ Computed states 190 | integrateWithDiff -- stepper x0 (t0 :| ts) f = 191 | = todo (FallbackSolution Solutions.ODE.integrateWithDiff) 192 | 193 | 194 | -- | Single step of 4th-order Runge-Kutta integration. 195 | -- 196 | -- 197 | -- The equations for a single step of RK4 are as follows: 198 | -- 199 | -- k1 = h * f (t, x) 200 | -- k2 = h * f (t + 0.5*h, x + 0.5*k1) 201 | -- k3 = h * f (t + 0.5*h, x + 0.5*k2) 202 | -- k4 = h * f (t + h, x + k3) 203 | -- 204 | -- dx = (1/6)*k1 + (1/3)*k2 + (1/3)*k3 + (1/6)*k4 205 | -- 206 | -- xNext = x + dx 207 | -- tNext = t + h 208 | rk4Step 209 | :: ( AffineSpace state 210 | , diff ~ Diff state, VectorSpace diff 211 | , HasBasis time, HasTrie (Basis time) 212 | , s ~ Scalar diff, s ~ Scalar time, Fractional s ) 213 | => time -- ^ Step size @h@ 214 | -> ((time, state) -> time :-* diff) -- ^ Gradient function @f (x, t)@ 215 | -> (time, state) -- ^ Before the step @(t, x)@ 216 | -> (time, state) -- ^ After the step @(t, x)@ 217 | rk4Step -- h f q@(t, x) 218 | = todo (FallbackSolution Solutions.ODE.rk4Step) 219 | 220 | 221 | -- | Stepper function for ODE integration that can terminate. 222 | type TerminatingStepper time state 223 | = time 224 | -> ((time, state) -> Maybe (time :-* Diff state)) 225 | -> (time, state) 226 | -> Maybe (time, state) 227 | 228 | 229 | -- | Driver for an ODE that terminates itself. 230 | -- 231 | -- Bisection is used to find a terminal value for the ODE at a time whose error 232 | -- is less than @tEpsilon@. 233 | integrateTerminating 234 | :: forall state diff time s. 235 | ( diff ~ Diff state 236 | , HasBasis time 237 | , s ~ Scalar time, Ord time, Fractional s ) 238 | => TerminatingStepper time state -- ^ Stepper to use. 239 | -> time -- ^ Allowable error in the final time. 240 | -> time -- ^ Step size @h@. 241 | -> (time, state) -- ^ Initial state. 242 | -> ((time, state) -> Maybe (time :-* diff)) -- ^ Gradient function. 243 | -> NonEmpty (time, state) -- ^ Computed states. 244 | integrateTerminating {- stepper tEpsilon h state0 f -} 245 | = todo (FallbackSolution Solutions.ODE.integrateTerminating) 246 | 247 | 248 | -- | Single step of terminating 4th-order Runge-Kutta integration. 249 | -- 250 | -- A result is only returned if all none of the function evaluations indicate 251 | -- termination. 252 | rk4StepTerminating 253 | :: ( AffineSpace state 254 | , diff ~ Diff state, VectorSpace diff 255 | , HasBasis time, HasTrie (Basis time) 256 | , s ~ Scalar time, s ~ Scalar diff, Fractional s ) 257 | => time -- ^ Step size @h@ 258 | -> ((time, state) -> Maybe (time :-* Diff state)) -- ^ Gradient function @f (x, t)@ 259 | -> (time, state) -- ^ Before the step @(t, x)@ 260 | -> Maybe (time, state) -- ^ Optional @(t, x)@ after the step 261 | rk4StepTerminating {- h f (t, x) -} 262 | = todo (FallbackSolution Solutions.ODE.rk4StepTerminating) 263 | 264 | 265 | {- $setup 266 | 267 | >>> :set -XFlexibleContexts -XNegativeLiterals -XTypeFamilies 268 | >>> import Data.LinearMap (linear) 269 | 270 | -- >>> :set -XQuasiQuotes -XFlexibleContexts -XTypeFamilies 271 | -- >>> import Data.Metrology.Vector ((%), (|*|)) 272 | -- >>> import Data.Units.SI.Parser (si) 273 | 274 | -} 275 | -------------------------------------------------------------------------------- /notes/beamer-trek/beamerouterthemetrek.sty: -------------------------------------------------------------------------------- 1 | \RequirePackage{etex} 2 | \RequirePackage{tikz} 3 | \RequirePackage{trek-shapes} 4 | 5 | \mode 6 | 7 | \makeatletter 8 | 9 | % Basic dimensions of the trek theme 10 | \newdimen\trek@margin 11 | \newdimen\trek@titlegap 12 | \newdimen\trek@elbow@height 13 | \newdimen\trek@elbow@outer@radius 14 | \newdimen\trek@elbow@inner@radius 15 | \newdimen\trek@cursorwidth 16 | \newdimen\trek@sidebar@width 17 | \newdimen\trek@left@footline@elbow@width 18 | \newdimen\trek@buttongap 19 | \trek@margin=2mm 20 | \trek@titlegap=1.0mm 21 | \trek@elbow@height=10mm 22 | \trek@elbow@outer@radius=6mm 23 | \trek@elbow@inner@radius=3mm 24 | \trek@cursorwidth=5mm 25 | \trek@sidebar@width=16mm 26 | \trek@left@footline@elbow@width=25mm 27 | \trek@buttongap=0.5mm 28 | 29 | % Derived dimensions 30 | \newdimen\trek@full@sidebar@width 31 | \newdimen\trek@margin@right 32 | \trek@full@sidebar@width=\trek@sidebar@width 33 | \advance \trek@full@sidebar@width by \trek@margin 34 | \trek@margin@right=\trek@margin 35 | \advance \trek@margin@right by \trek@margin 36 | 37 | \setbeamersize{sidebar width left=\the\trek@full@sidebar@width} 38 | \setbeamersize{text margin left=\the\trek@margin} 39 | \setbeamersize{text margin right=\the\trek@margin@right} 40 | 41 | \defbeamertemplate*{headline}{trek theme}{} 42 | 43 | \defbeamertemplate*{navigation symbols}{trek theme}{} 44 | 45 | \defbeamertemplate*{frametitle}{trek theme} 46 | {% 47 | % Measure height and width of the frametitle text 48 | \newdimen\@titletextwidth% 49 | \newdimen\@titletextheight% 50 | \setbox0=\hbox{\usebeamerfont{frametitle}\insertframetitle}% 51 | \@titletextwidth=\wd0% 52 | \setbox1=\hbox{\usebeamerfont{frametitle}8ML}% 53 | \@titletextheight=\ht1% 54 | % Compute height of frametitle region (including top margin) 55 | \newdimen\frametitle@height% 56 | \frametitle@height=\trek@elbow@height% 57 | \advance \frametitle@height by \trek@margin% 58 | % Origin of the "cursor" (right-most knobbly thing) 59 | \newdimen\cursor@x% 60 | \newdimen\cursor@y% 61 | \cursor@x=\paperwidth% 62 | \advance \cursor@x by -\the\trek@margin% 63 | \advance \cursor@x by -\the\trek@cursorwidth% 64 | \cursor@y=\trek@elbow@height% 65 | \advance \cursor@y by -\the\@titletextheight% 66 | % Origin of the title (center of baseline) 67 | \newdimen\title@x% 68 | \newdimen\title@y% 69 | \title@x=-\the\@titletextwidth% 70 | \divide \title@x by 2% 71 | \advance \title@x by \the\cursor@x% 72 | \advance \title@x by -\the\trek@titlegap% 73 | \title@y=\cursor@y% 74 | % Handle first page and other pages differently 75 | \ifnum\c@framenumber=1% 76 | % === TITLE SLIDE === 77 | \else% 78 | % === NORMAL SLIDE === 79 | % Origin and width of the "elbow" 80 | \newdimen\elbow@x% 81 | \newdimen\elbow@y% 82 | \elbow@x=-\the\@titletextwidth% 83 | \divide \elbow@x by 2% 84 | \advance \elbow@x by \title@x% 85 | \advance \elbow@x by -\the\trek@titlegap% 86 | \elbow@y=\cursor@y% 87 | \newdimen\elbow@width% 88 | \elbow@width=\the\elbow@x% 89 | \advance \elbow@width by -\the\trek@margin% 90 | % Typeset the title 91 | \vspace{-1.2pt}% 92 | \leavevmode% 93 | \hskip-\the\trek@full@sidebar@width% skip horizontally back across the sidebar width 94 | \hbox{% 95 | \begin{beamercolorbox}[wd=\paperwidth,ht=\the\frametitle@height,left]{frametitle}% 96 | \begin{tikzpicture} 97 | \useasboundingbox (0,0) rectangle (\paperwidth,\the\frametitle@height); 98 | % \draw [draw=none,fill=red] (0,0) rectangle (\paperwidth,\the\frametitle@height); 99 | % cursor at the right 100 | \draw (\the\cursor@x,\the\cursor@y) node [ 101 | trek cursor, 102 | trek/cursor/width=\the\trek@cursorwidth, 103 | trek/cursor/height=\the\@titletextheight 104 | ]{}; 105 | % title text 106 | \draw (\the\title@x,\the\title@y) node [anchor=base,inner sep=0] 107 | {\usebeamerfont{frametitle}\insertframetitle}; 108 | % elbow 109 | \draw (\the\elbow@x,\the\elbow@y) node [ 110 | trek elbow, 111 | trek/elbow/bar height=\the\@titletextheight, 112 | trek/elbow/height=\the\trek@elbow@height, 113 | trek/elbow/sidebar width=\the\trek@sidebar@width, 114 | trek/elbow/outer radius=\the\trek@elbow@outer@radius, 115 | trek/elbow/inner radius=\the\trek@elbow@inner@radius, 116 | trek/elbow/width=\the\elbow@width 117 | ]{}; 118 | \end{tikzpicture}% 119 | \end{beamercolorbox}% 120 | }% 121 | \fi% 122 | } 123 | 124 | \defbeamertemplate*{footline}{trek theme} 125 | {% 126 | % Height and width of footer title text 127 | \newdimen\@footertextwidth% 128 | \newdimen\@footertextheight% 129 | \setbox0=\hbox{\usebeamerfont{title in head/foot}\insertshorttitle}% 130 | \@footertextwidth=\wd0% 131 | \setbox1=\hbox{\usebeamerfont{title in head/foot}ML8}% 132 | \@footertextheight\ht1% 133 | % Height of entire footer region 134 | \newdimen\footline@height% 135 | \footline@height=\the\trek@elbow@height% 136 | \advance\footline@height by \the\trek@margin% 137 | % Position and dimensions of elbow 138 | \newdimen\elbow@x% 139 | \newdimen\elbow@y% 140 | \newdimen\elbow@height% 141 | \elbow@x = \the\trek@left@footline@elbow@width% 142 | \advance \elbow@x by \the\trek@margin% 143 | \elbow@y = \the\trek@margin% 144 | \advance \elbow@y by \the\@footertextheight% 145 | \elbow@height = \the\footline@height% 146 | \advance \elbow@height by -\the\trek@margin% 147 | % Position of title 148 | \newdimen\half@title@width% 149 | \newdimen\title@x% 150 | \newdimen\title@y% 151 | \half@title@width=\@footertextwidth% 152 | \divide \half@title@width by 2% 153 | \title@x = \elbow@x% 154 | \advance \title@x by \the\trek@titlegap% 155 | \title@y = \elbow@y% 156 | \advance \title@y by -\the\@footertextheight% 157 | % Position of right cursor 158 | \newdimen\cursor@x% 159 | \newdimen\cursor@y% 160 | \cursor@x = \paperwidth% 161 | \advance \cursor@x by -\the\trek@margin% 162 | \advance \cursor@x by -\the\trek@cursorwidth% 163 | \cursor@y = \title@y% 164 | % Dimensions of central bar 165 | \newdimen\bar@x% 166 | \newdimen\bar@y% 167 | \newdimen\bar@width% 168 | \bar@x = \title@x% 169 | \advance \bar@x by \the\@footertextwidth% 170 | \advance \bar@x by \the\trek@titlegap% 171 | \bar@y = \title@y% 172 | \bar@width = \cursor@x% 173 | \advance \bar@width by -\the\bar@x% 174 | \advance \bar@width by -\the\trek@titlegap% 175 | % Handle title slide differently 176 | \ifnum\c@framenumber=1% 177 | % === TITLE SLIDE === 178 | \else% 179 | % === NORMAL SLIDE === 180 | % Typeset the footline 181 | \leavevmode% 182 | \hbox{% 183 | \begin{beamercolorbox}[wd=\paperwidth,ht=\the\footline@height,left]{title in head/foot}% 184 | \begin{tikzpicture}% 185 | \useasboundingbox (0,0) rectangle (\paperwidth,\the\footline@height); 186 | %\draw [draw=none,fill=red] (0,0) rectangle (\paperwidth,\the\footline@height); 187 | % left footline elbow 188 | \draw (\the\elbow@x,\the\elbow@y) node [ 189 | yscale=-1, 190 | trek elbow, 191 | trek/elbow/bar height=\the\@footertextheight, 192 | trek/elbow/sidebar width=\the\trek@sidebar@width, 193 | trek/elbow/inner radius=\the\trek@elbow@inner@radius, 194 | trek/elbow/outer radius=\the\trek@elbow@outer@radius, 195 | trek/elbow/height=\the\elbow@height, 196 | trek/elbow/width=\the\trek@left@footline@elbow@width 197 | ]{}; 198 | % left footline title 199 | \draw (\the\title@x,\the\title@y) node [anchor=base west,inner sep=0] 200 | {\usebeamerfont{title in head/foot}\insertshorttitle}; 201 | % central bar 202 | \filldraw (\the\bar@x,\the\bar@y) rectangle ++(\the\bar@width,\the\@footertextheight); 203 | % right cursor 204 | \draw (\the\cursor@x,\the\cursor@y) node [ 205 | trek cursor, 206 | trek/cursor/width=\the\trek@cursorwidth, 207 | trek/cursor/height=\the\@footertextheight 208 | ]{}; 209 | \end{tikzpicture}% 210 | \end{beamercolorbox}% 211 | }% 212 | \fi% 213 | } 214 | 215 | \defbeamertemplate*{sidebar left}{trek theme} 216 | {% 217 | % Measure height and width of the frametitle text 218 | \newdimen\@titletextheight% 219 | \setbox0=\hbox{\usebeamerfont{frametitle}8ML}% 220 | \@titletextheight=\ht0% 221 | % Compute height of frametitle region (including top margin) 222 | \newdimen\frametitle@height% 223 | \frametitle@height=\trek@elbow@height% 224 | \advance \frametitle@height by \trek@margin% 225 | % Height and width of footer title text 226 | \newdimen\@footertextheight% 227 | \setbox0=\hbox{\usebeamerfont{title in head/foot}ML8}% 228 | \@footertextheight\ht0% 229 | % Height of entire footer region 230 | \newdimen\footline@height% 231 | \footline@height=\the\trek@elbow@height% 232 | \advance\footline@height by \the\trek@margin% 233 | % Dimensions of sidebar 234 | \newdimen\sidebar@x% 235 | \newdimen\sidebar@y% 236 | \newdimen\sidebar@width% 237 | \newdimen\sidebar@height% 238 | \newdimen\sidebar@totalwidth% 239 | \newdimen\sidebar@totalheight% 240 | \sidebar@x = \the\trek@margin% 241 | \sidebar@y = \the\footline@height% 242 | \advance \sidebar@y by \the\trek@titlegap% 243 | \sidebar@width = \the\trek@sidebar@width% 244 | \sidebar@height = \paperheight% 245 | \advance \sidebar@height by -\the\frametitle@height% 246 | \advance \sidebar@height by -\the\footline@height% 247 | \advance \sidebar@height by -\the\trek@titlegap% 248 | \advance \sidebar@height by -\the\trek@titlegap% 249 | \sidebar@totalwidth = \the\trek@sidebar@width% 250 | \advance \sidebar@totalwidth by \the\trek@margin% 251 | \sidebar@totalheight = \paperheight% 252 | % Handle title slide differently 253 | \ifnum\c@framenumber=1% 254 | % === TITLE SLIDE === 255 | \else% 256 | % === NORMAL SLIDE === 257 | % Typeset the sidebar 258 | \leavevmode% 259 | \hbox{% 260 | \begin{beamercolorbox}[wd=\the\sidebar@totalwidth,ht=\paperheight,left]{sidebar}% 261 | \begin{tikzpicture} 262 | \useasboundingbox (0,0) rectangle (\the\sidebar@totalwidth,\the\sidebar@totalheight); 263 | %\draw [draw=none,fill=red] (0,0) rectangle (\the\sidebar@totalwidth,\the\sidebar@totalheight); 264 | % sidebar rectangle 265 | \filldraw (\the\sidebar@x,\the\sidebar@y) rectangle ++(\the\sidebar@width,\the\sidebar@height); 266 | \end{tikzpicture}% 267 | \end{beamercolorbox}% 268 | }% 269 | \fi% 270 | } 271 | 272 | \makeatother 273 | 274 | \mode 275 | -------------------------------------------------------------------------------- /src/Examples/ODEExamples.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Examples.ODEExamples 3 | Description : Plots of ODE integration. 4 | 5 | The examples in this module accompany the problems outlined in the 6 | 'ODE' module. 7 | -} 8 | {-# LANGUAGE DeriveAnyClass #-} 9 | {-# LANGUAGE DeriveGeneric #-} 10 | {-# LANGUAGE FlexibleContexts #-} 11 | {-# LANGUAGE NegativeLiterals #-} 12 | {-# LANGUAGE OverloadedStrings #-} 13 | {-# LANGUAGE QuasiQuotes #-} 14 | {-# LANGUAGE ScopedTypeVariables #-} 15 | {-# LANGUAGE TemplateHaskell #-} 16 | {-# LANGUAGE TypeFamilies #-} 17 | {-# LANGUAGE TypeOperators #-} 18 | module Examples.ODEExamples where 19 | 20 | import Control.Lens (makeLenses, (^.)) 21 | import Data.AdditiveGroup (AdditiveGroup) 22 | import Data.AffineSpace (AffineSpace, Diff, (.+^), (.-.)) 23 | import Data.LinearMap ((:-*), linear) 24 | import Data.List.NonEmpty (NonEmpty) 25 | import qualified Data.List.NonEmpty as NonEmpty 26 | import Data.Metrology.Vector (qNegate, qSqrt, (%), (|*|), (|+|), 27 | (|-|), (|/|)) 28 | import Data.Units.SI.Parser (si) 29 | import Data.VectorSpace (VectorSpace, (*^)) 30 | import GHC.Generics (Generic) 31 | import Orphans () 32 | 33 | import qualified ODE 34 | import qualified Plot 35 | import qualified Units as U 36 | 37 | 38 | ------------------------------------------------------------------------------- 39 | -- Exponential Decay Example 40 | ------------------------------------------------------------------------------- 41 | 42 | 43 | -- | Plot Euler integration of an exponential decay ODE. 44 | -- 45 | -- The example here is decay of Plutonium-238, with a half-life of 87.7 years. 46 | plotEulerDoubleExpDecay :: Plot.Output -> IO () 47 | plotEulerDoubleExpDecay out = do 48 | let 49 | tHalf = 87.7 -- half-life of Pu-238 (years) 50 | ln = logBase (exp 1) 51 | lambda = ln 2 / tHalf -- decay constant of Pu-238 (1/years) 52 | 53 | -- the analytic equation; takes a list of times; produces a list of 54 | -- (time, x) 55 | analytic :: [Double] -> [(Double, Double)] 56 | analytic times = [ (t, exp(-lambda * t)) | t <- times ] 57 | 58 | -- the numerical solution; takes a list of times; produces a list of 59 | -- (time, x) 60 | numerical :: [Double] -> [(Double, Double)] 61 | numerical times = 62 | let 63 | f (_, x) = -lambda * x -- the gradient function 64 | in 65 | NonEmpty.toList $ 66 | ODE.integrateEulerDouble f 1.0 (NonEmpty.fromList times) 67 | 68 | -- plot everything 69 | Plot.xyChart 70 | out 71 | "Radioactive Decay of Pu-238 - Analytical vs Euler" 72 | "Time (t) - years" 73 | "Amount (N) - fraction of original" 74 | [] 75 | [ Plot.Line "Analytical Solution" (analytic (ODE.linspace 50 0.0 200.0)) 76 | , Plot.Points "Euler (dt=40 years)" (numerical (ODE.linspace 6 0.0 200.0)) 77 | , Plot.Points "Euler (dt=20 years)" (numerical (ODE.linspace 11 0.0 200.0)) 78 | , Plot.Points "Euler (dt=4 years)" (numerical (ODE.linspace 51 0.0 200.0)) ] 79 | 80 | 81 | ------------------------------------------------------------------------------- 82 | -- Simple Harmonic Oscillator Example 83 | ------------------------------------------------------------------------------- 84 | 85 | 86 | -- | State of a 1D simple harmonic oscillator. 87 | -- 88 | -- The state uses lenses and has SI units for both position and 89 | -- velocity. @a@ is the underlying numeric type. 90 | data StateSHM a 91 | = StateSHM 92 | { _pos :: U.Length a -- ^ Position. 93 | , _vel :: U.Velocity a -- ^ Velocity. 94 | } deriving (Show, Eq) 95 | makeLenses ''StateSHM 96 | 97 | 98 | -- | Delta in the state of a 1D simple harmonic oscillator. 99 | -- 100 | -- The delta uses lenses and has SI units for both the difference in 101 | -- position and difference in velocity. @a@ is the underlying numeric 102 | -- type. 103 | data DStateSHM a 104 | = DStateSHM 105 | { _dpos :: U.Length a -- ^ Delta in position. 106 | , _dvel :: U.Velocity a -- ^ Delta in velocity. 107 | } deriving (Show, Eq, Generic, AdditiveGroup, VectorSpace) 108 | makeLenses ''DStateSHM 109 | 110 | 111 | -- Connects State and DState into an AffineSpace/VectorSpace structure. 112 | instance (AdditiveGroup a) => AffineSpace (StateSHM a) where 113 | type Diff (StateSHM a) = DStateSHM a 114 | s1 .-. s2 = DStateSHM 115 | { _dpos = s1^.pos |-| s2^.pos 116 | , _dvel = s1^.vel |-| s2^.vel 117 | } 118 | s .+^ ds = StateSHM 119 | { _pos = s^.pos |+| ds^.dpos 120 | , _vel = s^.vel |+| ds^.dvel 121 | } 122 | 123 | 124 | -- | Plot Euler integration of the Simple Harmonic Motion example. 125 | -- 126 | -- This example uses units and involves free conversion between them. 127 | plotEulerSHM :: Plot.Output -> IO () 128 | plotEulerSHM out = do 129 | let 130 | -- parameters 131 | ti = 0 % [si| ms |] -- initial simulation time 132 | tf = 1500 % [si| ms |] -- final simulation time 133 | x0 = 10 % [si| mm |] -- initial position 134 | v0 = 0 % [si| m/s |] -- initial velocity 135 | k = 10 % [si| mN/mm |] -- spring stiffness 136 | m = 500 % [si| g |] -- mass 137 | omega = qSqrt (k |/| m) -- angular frequency 138 | 139 | -- ODE we're solving 140 | shmODE :: (U.Time Double, StateSHM Double) -> (U.Time Double :-* DStateSHM Double) 141 | shmODE (_, state) = linear $ \dt -> 142 | DStateSHM { _dpos = state^.vel |*| dt 143 | , _dvel = qNegate(state^.pos |*| k |/| m) |*| dt } 144 | 145 | -- analytical solution 146 | analytic :: [U.Time Double] -> [(U.Time Double, U.Length Double)] 147 | analytic times = [ (t, x0 |*| cos (t |*| omega)) | t <- times ] 148 | 149 | -- numerical solution 150 | numerical :: [U.Time Double] -> [(U.Time Double, U.Length Double)] 151 | numerical times = 152 | let 153 | state0 = StateSHM { _pos = x0, _vel = v0 } 154 | tstates = ODE.integrate ODE.eulerStep 155 | state0 156 | (NonEmpty.fromList times) 157 | shmODE 158 | in 159 | NonEmpty.toList $ (\(t, s) -> (t, s^.pos)) <$> tstates 160 | 161 | Plot.xyChartUnits 162 | out 163 | "Simple Harmonic Motion - Analytical vs Euler" 164 | "Time (ms)" 165 | "Position (mm)" 166 | ( [si| ms |], [si| mm |] ) 167 | [ Plot.Line "Analytical Solution" $ analytic (ODE.linspace 200 ti tf) 168 | , Plot.Points "Euler (dt=75.0 ms)" $ numerical (ODE.linspace 20 ti tf) 169 | , Plot.Points "Euler (dt=37.5 ms)" $ numerical (ODE.linspace 40 ti tf) 170 | , Plot.Points "Euler (dt=7.5 ms)" $ numerical (ODE.linspace 200 ti tf) ] 171 | 172 | 173 | -- | Plot comparison of Euler and RK4 integration of the Simple 174 | -- Harmonic Motion example. 175 | plotSHMComparison :: Plot.Output -> IO () 176 | plotSHMComparison out = do 177 | let 178 | -- parameters 179 | ti = 0 % [si| s |] -- initial simulation time 180 | tf = 1500 % [si| ms |] -- final simulation time 181 | x0 = 10 % [si| mm |] -- initial position 182 | v0 = 0 % [si| m/s |] -- initial velocity 183 | k = 10 % [si| mN/mm |] -- spring stiffness 184 | m = 500 % [si| g |] -- mass 185 | omega = qSqrt (k |/| m) -- angular frequency 186 | 187 | -- ODE we're solving 188 | shmODE :: (U.Time Double, StateSHM Double) -> (U.Time Double :-* DStateSHM Double) 189 | shmODE (_, state) = linear $ \dt -> 190 | DStateSHM { _dpos = state^.vel |*| dt 191 | , _dvel = qNegate(state^.pos |*| k |/| m) |*| dt } 192 | 193 | -- analytical solution 194 | analytic :: [U.Time Double] -> [(U.Time Double, U.Length Double)] 195 | analytic times = [ (t, x0 |*| cos (t |*| omega)) | t <- times ] 196 | 197 | -- numerical solution 198 | numerical 199 | :: ODE.Stepper (U.Time Double) (StateSHM Double) 200 | -> [U.Time Double] 201 | -> [(U.Time Double, U.Length Double)] 202 | numerical stepper times = 203 | let 204 | state0 = StateSHM { _pos = x0, _vel = v0 } 205 | tstates = ODE.integrate stepper 206 | state0 207 | (NonEmpty.fromList times) 208 | shmODE 209 | in 210 | NonEmpty.toList $ (\(t, s) -> (t, s^.pos)) <$> tstates 211 | 212 | -- Euler and RK4 numerical solutions 213 | euler = numerical ODE.eulerStep 214 | rk4 = numerical ODE.rk4Step 215 | 216 | Plot.xyChartUnits 217 | out 218 | "Simple Harmonic Motion - Analytical, Euler and RK4" 219 | "Time (ms)" 220 | "Position (mm)" 221 | ( [si| ms |], [si| mm |] ) 222 | [ Plot.Line "Analytical Solution" $ analytic (ODE.linspace 200 ti tf) 223 | , Plot.Points "Euler (dt=37.5 ms)" $ euler (ODE.linspace 40 ti tf) 224 | , Plot.Points "RK4 (dt=150.0 ms)" $ rk4 (ODE.linspace 10 ti tf) ] 225 | 226 | 227 | ------------------------------------------------------------------------------- 228 | -- Vertical throw example 229 | ------------------------------------------------------------------------------- 230 | 231 | 232 | type StateVT = (Double, Double) -- ^ (height, velocity) 233 | type DStateVT = (Double, Double) -- ^ (dheight, dvelocity) 234 | 235 | 236 | -- Plots height vs time for a vertial throw; terminating at apogee. 237 | -- 238 | -- The tEpsilon and dt values are chosen so that the time step is bisected 239 | -- several times to find the apogee with an accuracy of 0.001s. 240 | plotVerticalThrow :: Plot.Output -> IO () 241 | plotVerticalThrow out = do 242 | 243 | let 244 | -- ODE to solve; non-terminating 245 | vtODE :: (Double, StateVT) -> Double :-* DStateVT 246 | vtODE (_, (_, v)) = linear $ \dt -> dt *^ (v, -9.81) 247 | 248 | -- Compose a termination condition with the ODE 249 | vtODEt :: (Double, StateVT) -> Maybe (Double :-* DStateVT) 250 | vtODEt ts@(_, (_, v)) = if v > 0 then Just (vtODE ts) else Nothing 251 | 252 | -- Required time accuracy for termination 253 | tEpsilon :: Double 254 | tEpsilon = 0.001 -- seconds 255 | 256 | -- Initial state 257 | state0 :: StateVT 258 | state0 = (0, 29) 259 | 260 | -- Run the ODE integration 261 | states :: Double -> NonEmpty (Double, StateVT) 262 | states dt = ODE.integrateTerminating ODE.rk4StepTerminating 263 | tEpsilon dt 264 | (0, state0) 265 | vtODEt 266 | 267 | -- List of (time, height) 268 | heights :: Double -> [(Double, Double)] 269 | heights dt = (\(t, (h, _)) -> (t, h)) <$> (NonEmpty.toList (states dt)) 270 | 271 | Plot.xyChart 272 | out 273 | "Vertical Throw Example - Bisection of Termination" 274 | "Time (s)" 275 | "Height (m)" 276 | [] 277 | [ Plot.Line "RK4 (dt = 0.01s)" (heights 0.01) 278 | , Plot.Points "RK4 (dt = 0.5s)" (heights 0.5) ] 279 | -------------------------------------------------------------------------------- /src/Plot.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Plot 3 | Description : Abstracted plotting capabilities. 4 | 5 | This module adds a thin layer of abstraction on top of the Chart library. It 6 | has served well as a way to switch between Chart and gnuplot during 7 | development of the workshop. 8 | -} 9 | {-# LANGUAGE DataKinds #-} 10 | {-# LANGUAGE DuplicateRecordFields #-} 11 | {-# LANGUAGE FlexibleContexts #-} 12 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 13 | {-# LANGUAGE LambdaCase #-} 14 | {-# LANGUAGE NegativeLiterals #-} 15 | {-# LANGUAGE ScopedTypeVariables #-} 16 | module Plot 17 | ( -- * XY Charts 18 | -- ** Types 19 | Output (Screen, PNG, PGF) 20 | , XYChart (title, xLabel, yLabel, chartItems) 21 | , Title(Title) 22 | , XLabel(XLabel) 23 | , YLabel(YLabel) 24 | , XYOption(XRange) 25 | , Item(Line, Points) 26 | -- ** Functions 27 | , xyChart 28 | , xyChartUnits 29 | -- * Orbit system plot 30 | -- ** Types 31 | , OrbitSystem(OrbitSystem, planet, systemItems) 32 | , Planet(Planet, name, radius, color) 33 | , OrbitSystemItem(Trajectory, points, color, AltitudeCircle, altitude, 34 | altLabel) 35 | -- ** Functions 36 | , plotOrbitSystem 37 | ) where 38 | 39 | import qualified Codec.Picture.Png as Png 40 | import Control.Lens ((.=)) 41 | import Control.Monad (forM_) 42 | import qualified Data.ByteString.Lazy as LBS 43 | import Data.Colour (Colour) 44 | import qualified Data.Metrology.Vector as DMV 45 | import Data.String (IsString) 46 | import Data.Text (Text) 47 | import qualified Data.Text as Text 48 | import Data.VectorSpace (Scalar, VectorSpace) 49 | import qualified Diagrams.Backend.PGF as PGF 50 | import qualified Diagrams.Backend.Rasterific as BR 51 | import Diagrams.Prelude (( # )) 52 | import qualified Diagrams.Prelude as D 53 | import qualified Diagrams.TwoD.Text as D 54 | import qualified Graphics.Rendering.Chart.Backend.Diagrams as Chart 55 | import qualified Graphics.Rendering.Chart.Easy as Chart 56 | import qualified ITermShow 57 | import System.IO (stdout) 58 | 59 | 60 | -- | Output location for a plot. 61 | data Output 62 | = Screen -- ^ Output to the screen (ITermShow and wshterm) 63 | | PNG FilePath -- ^ Save to a PNG file 64 | | PGF FilePath -- ^ Save to a PGF file (for LaTeX) 65 | 66 | 67 | -- | Data for an xy-chart. 68 | -- 69 | -- An @XYChart@ is a chart on x-y orthogonal axes. 70 | data XYChart 71 | = XYChart 72 | { -- | Title for the chart. 73 | title :: !Title 74 | -- | Label for the x-axis. 75 | , xLabel :: !XLabel 76 | -- | Label for the y-axis. 77 | , yLabel :: !YLabel 78 | -- | Options for the chart. 79 | , chartOptions :: [XYOption Double Double] 80 | -- | Items in the chart. 81 | , chartItems :: [Item Double Double] 82 | } 83 | 84 | 85 | newtype Title = Title { unTitle :: Text } deriving (IsString) 86 | newtype XLabel = XLabel { unXLabel :: Text } deriving (IsString) 87 | newtype YLabel = YLabel { unYLabel :: Text } deriving (IsString) 88 | 89 | 90 | -- | Items that can appear in an 'XYChart'. 91 | data Item x y 92 | = Line Text [(x, y)] -- ^ A plotted line. 93 | | Points Text [(x, y)] -- ^ A set of plotted points. 94 | 95 | 96 | -- | Options for an XYChart. 97 | data XYOption x y 98 | = XRange !x !x -- ^ Specify a range for the x-axis. 99 | 100 | 101 | -- | Plot an 'XYChart'. 102 | xyChart 103 | :: Output -- ^ Output location. 104 | -> Title -- ^ Title of the chart. 105 | -> XLabel -- ^ X-axis label. 106 | -> YLabel -- ^ Y-axis label. 107 | -> [XYOption Double Double] -- ^ Options for the plot. 108 | -> [Item Double Double] -- ^ List of items to plot. 109 | -> IO () -- ^ IO action. 110 | xyChart out t x y options items = 111 | let chart = XYChart t x y options items 112 | in case out of 113 | Screen -> do 114 | pngBS <- plotXYChartPNGBS chart 115 | LBS.hPutStr stdout (ITermShow.displayImage pngBS) 116 | putStrLn "" 117 | 118 | PGF filePath -> plotXYChartPGF filePath chart 119 | 120 | PNG filePath -> do 121 | pngBS <- plotXYChartPNGBS chart 122 | LBS.writeFile filePath pngBS 123 | 124 | 125 | -- | Plot an 'XYChart' whose plot items contain units. 126 | xyChartUnits 127 | :: forall dimx dimy unitx unity l. 128 | ( DMV.ValidDLU dimx l unitx 129 | , DMV.ValidDLU dimy l unity ) 130 | => Output -- ^ Output location. 131 | -> Title -- ^ Title of the chart. 132 | -> XLabel -- ^ X-axis label. 133 | -> YLabel -- ^ Y-axis label. 134 | -> (unitx, unity) -- ^ Units for the x and y axes. 135 | -> [Item (DMV.Qu dimx l Double) (DMV.Qu dimy l Double)] 136 | -- ^ List of items to plot. 137 | -> IO () -- ^ IO action. 138 | xyChartUnits out t x y (ux, uy) items 139 | = xyChart out t x y [] (itemInUnits (ux, uy) <$> items) 140 | 141 | 142 | itemInUnits 143 | :: forall dimx dimy unitx unity l n. 144 | ( DMV.ValidDLU dimx l unitx 145 | , DMV.ValidDLU dimy l unity 146 | , VectorSpace n, Fractional (Scalar n) ) 147 | => (unitx, unity) 148 | -> Item (DMV.Qu dimx l n) (DMV.Qu dimy l n) 149 | -> Item n n 150 | itemInUnits (ux, uy) item = 151 | let 152 | inu (x, y) = (x DMV.# ux, y DMV.# uy) 153 | in 154 | case item of 155 | Line t xys -> Line t (inu <$> xys) 156 | Points t xys -> Points t (inu <$> xys) 157 | 158 | 159 | plotXYChartPNGBS :: XYChart -> IO LBS.ByteString 160 | plotXYChartPNGBS chart = do 161 | env <- Chart.defaultEnv Chart.vectorAlignmentFns 500 375 162 | let dia = fst $ Chart.runBackendR env (Chart.toRenderable (xyChartEC chart)) 163 | let img = BR.rasterRgb8 (D.mkWidth 1200) dia 164 | pure (Png.encodePng img) 165 | 166 | 167 | plotXYChartPGF :: FilePath -> XYChart -> IO () 168 | plotXYChartPGF filePath chart = do 169 | env <- Chart.defaultEnv Chart.vectorAlignmentFns 500 375 170 | let dia = fst $ Chart.runBackendR env (Chart.toRenderable (xyChartEC chart)) 171 | PGF.renderPGF filePath (D.mkWidth 500) dia 172 | 173 | 174 | xyChartEC :: XYChart -> Chart.Renderable () 175 | xyChartEC chart = Chart.toRenderable $ do 176 | Chart.layout_title .= (Text.unpack . unTitle . title $ chart) 177 | Chart.layout_x_axis . Chart.laxis_title .= 178 | (Text.unpack . unXLabel . xLabel $ chart) 179 | Chart.layout_y_axis . Chart.laxis_title .= 180 | (Text.unpack . unYLabel . yLabel $ chart) 181 | forM_ (chartItems chart) $ \case 182 | Line label pts -> Chart.plot (Chart.line (Text.unpack label) [pts]) 183 | Points label pts -> Chart.plot (Chart.points (Text.unpack label) pts) 184 | forM_ (chartOptions chart) $ \case 185 | XRange minX maxX -> Chart.layout_x_axis . Chart.laxis_generate .= 186 | Chart.scaledAxis Chart.def (minX, maxX) 187 | 188 | 189 | -- | Data for an orbital system plot. 190 | -- 191 | -- An orbital system plot typically contains a planet or moon and trajectories. 192 | data OrbitSystem 193 | = OrbitSystem 194 | { planet :: Planet 195 | , systemItems :: [OrbitSystemItem] 196 | } 197 | 198 | 199 | -- | Planet data. 200 | data Planet 201 | = Planet 202 | { name :: Text 203 | , radius :: Double 204 | , color :: Colour Double 205 | } 206 | 207 | 208 | -- | Items that can appear in an orbit system plot. 209 | data OrbitSystemItem 210 | = Trajectory 211 | { points :: [(Double, Double)] 212 | , color :: Colour Double 213 | } 214 | | AltitudeCircle 215 | { altitude :: Double 216 | , altLabel :: Text 217 | , color :: Colour Double 218 | } 219 | 220 | 221 | -- | Plots an orbit system. 222 | plotOrbitSystem 223 | :: Output -- ^ Output location. 224 | -> Double -- ^ Vertical scale factor for the altitude. 225 | -> OrbitSystem -- ^ OrbitSystem to plot. 226 | -> IO () -- ^ IO action. 227 | plotOrbitSystem output vScale system = 228 | case output of 229 | Screen -> do 230 | let pngBS = plotOrbitSystemPNGBS vScale system 231 | LBS.hPutStr stdout (ITermShow.displayImage pngBS) 232 | putStrLn "" 233 | 234 | PGF filePath -> do 235 | let 236 | dia = mconcat (plotSystemItem output vScale (planet system) <$> systemItems system) 237 | <> plotPlanet (PGF filePath) (planet system) 238 | PGF.renderPGF filePath (D.mkWidth 400) dia 239 | 240 | PNG filePath -> do 241 | let pngBS = plotOrbitSystemPNGBS vScale system 242 | LBS.writeFile filePath pngBS 243 | 244 | 245 | plotOrbitSystemPNGBS :: Double -> OrbitSystem -> LBS.ByteString 246 | plotOrbitSystemPNGBS vScale system = 247 | let 248 | dia = mconcat (plotSystemItem (PNG undefined) vScale (planet system) <$> systemItems system) 249 | <> plotPlanet (PNG undefined) (planet system) 250 | diaFramed = D.bgFrame 400 D.white dia 251 | img = BR.rasterRgb8 (D.dims2D 1000 1000) diaFramed 252 | in 253 | Png.encodePng img 254 | 255 | 256 | plotPlanet 257 | :: ( D.Renderable (D.Path D.V2 Double) b 258 | , D.Renderable (D.Text Double) b ) 259 | => Output 260 | -> Planet 261 | -> D.QDiagram b D.V2 Double D.Any 262 | plotPlanet output (Planet pName pRadius pColor) 263 | = D.text (Text.unpack pName) 264 | # D.fontSize (getFontSize output) 265 | # D.fc D.black 266 | <> D.circle pRadius 267 | # D.fc pColor 268 | 269 | 270 | plotSystemItem 271 | :: ( D.Renderable (D.Path D.V2 Double) b 272 | , D.Renderable (D.Text Double) b ) 273 | => Output 274 | -> Double 275 | -> Planet 276 | -> OrbitSystemItem 277 | -> D.QDiagram b D.V2 Double D.Any 278 | plotSystemItem _ vScale (Planet _ pRadius _) (Trajectory pts c) 279 | = D.fromVertices (D.p2 . altitudeScale vScale pRadius <$> pts) # D.lc c # D.pad 1.1 280 | plotSystemItem output vScale (Planet _ pRadius _) (AltitudeCircle alt lbl c) 281 | = D.circle (scalarAltitudeScale vScale pRadius alt) 282 | # D.lc c 283 | # D.lwO 1 284 | # D.dashingN [0.01, 0.01] 0 285 | <> D.alignedText 0.5 (-1.0) (Text.unpack lbl) 286 | # D.fontSize (0.6 * getFontSize output) 287 | # D.fc D.gray 288 | # D.translateY (scalarAltitudeScale vScale pRadius alt) 289 | 290 | 291 | -- | Get font size for an OrbitSystem plot. 292 | -- 293 | -- "Good" font sizes seem to depend on our output device, so here we can 294 | -- switch between different font sizes depending on the output device. 295 | getFontSize :: Output -> D.Measured Double Double 296 | getFontSize Screen = D.output 30.0 297 | getFontSize (PNG _) = D.output 30.0 298 | getFontSize (PGF _) = D.output 12.0 299 | 300 | 301 | -- | Perform vertical scaling of altitude distance on a vector. 302 | altitudeScale :: Double -> Double -> (Double, Double) -> (Double, Double) 303 | altitudeScale vScale rad (x, y) = 304 | let 305 | r = sqrt ((x*x) + (y*y)) 306 | theta = atan2 y x 307 | 308 | r' = scalarAltitudeScale vScale rad r 309 | x' = r' * cos theta 310 | y' = r' * sin theta 311 | in 312 | (x', y') 313 | 314 | 315 | -- | Perform vertical scaling of altitude distance. 316 | scalarAltitudeScale :: Double -> Double -> Double -> Double 317 | scalarAltitudeScale vScale planetRadius r 318 | = vScale * (r - planetRadius) + planetRadius 319 | 320 | -------------------------------------------------------------------------------- /src/LunarAscent/Types.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE DeriveAnyClass #-} 3 | {-# LANGUAGE DeriveGeneric #-} 4 | {-# LANGUAGE FlexibleContexts #-} 5 | {-# LANGUAGE QuasiQuotes #-} 6 | {-# LANGUAGE ScopedTypeVariables #-} 7 | {-# LANGUAGE TemplateHaskell #-} 8 | {-# LANGUAGE TypeFamilies #-} 9 | {-# LANGUAGE TypeOperators #-} 10 | {-# LANGUAGE UndecidableInstances #-} 11 | module LunarAscent.Types 12 | ( -- * Coordinate systems 13 | MFCS(MFCS) 14 | , LVCS(LVCS) 15 | -- * Vectors and Points 16 | , V2, v2, qv2, v2Tuple 17 | , P2, p2 18 | , qcsAssignV2 19 | -- * Ascent target 20 | , AscentTarget(..), targetVel, targetRadius 21 | -- * Ascent stage state 22 | , ThrustAngle(..) 23 | , Dynamics(..), pos, vel, mass, angle, angvel 24 | , DDynamics(..), dpos, dvel, dmass, dangle, dangvel 25 | , AGCState(..), tgo, prevThrustAngle 26 | , Sim(..), time, engineShutoff, agcState, dynamics, ddynamics, accel 27 | -- * ACG commands 28 | , EngineShutoff(..) 29 | , AGCCommand(..), commandedThrustAngle, commandedEngineShutoff 30 | -- * Simulation constants 31 | , Constants(..), moonSGP, apsExhaustVelocity, apsMassFlowRate 32 | , initialMass, rDotFLVPEnd, tEngineThreshold, t2_HoldAll, t3_PositionControl 33 | , bThreshold, constants 34 | ) where 35 | 36 | import Control.Lens (Contravariant, Optic', Profunctor, 37 | makeLenses, to, (^.)) 38 | import Data.AdditiveGroup (AdditiveGroup, negateV, zeroV, (^+^), 39 | (^-^)) 40 | import Data.AffineSpace (AffineSpace, Diff, (.+^), (.-.)) 41 | import Data.Basis (Basis, HasBasis) 42 | import Data.Coerce (coerce) 43 | import Data.LinearMap ((:-*), lapply) 44 | import Data.VectorSpace (InnerSpace, Scalar, VectorSpace, (*^), 45 | (<.>)) 46 | import GHC.Generics (Generic) 47 | 48 | import Orphans () 49 | import Units ((:/) ((:/)), si, (%), (|*|), (|+|), (|-|), 50 | (|.+^|), (|.-.|), (|^/|)) 51 | import qualified Units as U 52 | 53 | ------------------------------------------------------------------------------- 54 | -- Vectors, points and coordinate systems 55 | ------------------------------------------------------------------------------- 56 | 57 | -- | Moon-fixed coordinate system. 58 | -- 59 | -- 2D coordinate system centred at the moon COM. Similar to that 60 | -- described in section 5.1.4.5 of Levine (1971), but in 2D and 61 | -- without any consideration of rotational axes. 62 | data MFCS = MFCS 63 | 64 | 65 | -- | Local vertical coordinate system. 66 | -- 67 | -- Local vertical coordinate system of the launch vehicle. See section 68 | -- 5.3.5.2 of Levine (1971). 69 | data LVCS = LVCS 70 | 71 | 72 | -- | V2 is a 2D vector in coordinate system @c@ with numeric type @a@. 73 | -- 74 | -- The Apollo ascent stage algorithm as implemented here uses two main 75 | -- coordinate systems, so for safety, we distinguish between vectors 76 | -- expressed in each of them using the phantom type @c@. 77 | data V2 c a = V2 !a !a deriving (Show, Eq) 78 | 79 | instance (AdditiveGroup a) => AdditiveGroup (V2 c a) where 80 | zeroV = V2 zeroV zeroV 81 | (V2 x1 y1) ^+^ (V2 x2 y2) = V2 (x1 ^+^ x2) (y1 ^+^ y2) 82 | (V2 x1 y1) ^-^ (V2 x2 y2) = V2 (x1 ^-^ x2) (y1 ^-^ y2) 83 | negateV (V2 x y) = V2 (negateV x) (negateV y) 84 | 85 | instance (VectorSpace a) => VectorSpace (V2 c a) where 86 | type Scalar (V2 c a) = Scalar a 87 | k *^ (V2 x y) = V2 (k*^x) (k*^y) 88 | 89 | instance 90 | ( VectorSpace a 91 | , AdditiveGroup (Scalar a) 92 | , InnerSpace a 93 | ) => InnerSpace (V2 c a) where 94 | (V2 x1 y1) <.> (V2 x2 y2) = (x1<.>x2) ^+^ (y1<.>y2) 95 | 96 | 97 | -- | Assign the coordinate system of a V2 with units. 98 | qcsAssignV2 :: U.Qu d l (V2 c1 a) -> U.Qu d l (V2 c2 a) 99 | qcsAssignV2 = coerce 100 | 101 | 102 | -- | Construct a 'V2'. 103 | v2 :: forall c a. a -> a -> V2 c a 104 | v2 vx vy = V2 vx vy 105 | 106 | 107 | -- | Construct a 'V2' as a dimensionless quantity. 108 | qv2 :: forall c a l. a -> a -> U.Qu '[] l (V2 c a) 109 | qv2 vx vy = U.quantity (v2 vx vy) 110 | 111 | 112 | -- | Convert a 'V2' to a tuple. 113 | v2Tuple :: forall c a. V2 c a -> (a, a) 114 | v2Tuple (V2 vx vy) = (vx, vy) 115 | 116 | 117 | -- | P2 is a 2D point in coordinate system @c@ with numeric type @a@. 118 | type P2 c a = U.Point (V2 c a) 119 | 120 | 121 | -- | Construct a 'P2'. TODO: remove? 122 | p2 :: forall c a. a -> a -> P2 c a 123 | p2 px py = U.Point (V2 px py) 124 | 125 | 126 | ------------------------------------------------------------------------------- 127 | -- Ascent target 128 | ------------------------------------------------------------------------------- 129 | 130 | 131 | -- | Target of the lunar ascent control. 132 | data AscentTarget a 133 | = AscentTarget 134 | { -- | Target velocity in local vertical coordinate system. 135 | _targetVel :: !(U.Velocity (V2 LVCS a)) 136 | -- | Target radius. 137 | , _targetRadius :: !(U.Length a) 138 | } deriving (Show) 139 | makeLenses ''AscentTarget 140 | 141 | 142 | ------------------------------------------------------------------------------- 143 | -- State of the ascent stage 144 | ------------------------------------------------------------------------------- 145 | 146 | 147 | -- | Thrust angle. 148 | newtype ThrustAngle a 149 | = ThrustAngle { unThrustAngle :: a } 150 | deriving (Show, Generic, AdditiveGroup, VectorSpace) 151 | 152 | 153 | -- | Dynamical state of the ascent stage vehicle. 154 | data Dynamics a 155 | = Dynamics 156 | { _pos :: !(U.Length (P2 MFCS a)) 157 | , _vel :: !(U.Velocity (V2 MFCS a)) 158 | , _mass :: !(U.Mass a) 159 | , _angle :: !(U.Count a) 160 | , _angvel :: !(U.AngVelocity a) 161 | } deriving (Show) 162 | makeLenses ''Dynamics 163 | 164 | 165 | -- | Delta of the dynamical state. 166 | data DDynamics a 167 | = DDynamics 168 | { _dpos :: !(U.Length (V2 MFCS a)) 169 | , _dvel :: !(U.Velocity (V2 MFCS a)) 170 | , _dmass :: !(U.Mass a) 171 | , _dangle :: !(U.Count a) 172 | , _dangvel :: !(U.AngVelocity a) 173 | } deriving (Show, Generic, AdditiveGroup, VectorSpace) 174 | makeLenses ''DDynamics 175 | 176 | instance (VectorSpace a) => AffineSpace (Dynamics a) where 177 | type Diff (Dynamics a) = DDynamics a 178 | d1 .-. d2 = DDynamics 179 | { _dpos = d1^.pos |.-.| d2^.pos 180 | , _dvel = d1^.vel |-| d2^.vel 181 | , _dmass = d1^.mass |-| d2^.mass 182 | , _dangle = d1^.angle |-| d2^.angle 183 | , _dangvel = d1^.angvel |-| d2^.angvel 184 | } 185 | d .+^ dd = Dynamics 186 | { _pos = d^.pos |.+^| dd^.dpos 187 | , _vel = d^.vel |+| dd^.dvel 188 | , _mass = d^.mass |+| dd^.dmass 189 | , _angle = d^.angle |+| dd^.dangle 190 | , _angvel = d^.angvel |+| dd^.dangvel 191 | } 192 | 193 | 194 | -- | State of the guidance computer. 195 | data AGCState a 196 | = AGCState 197 | { -- | Previous time-to-go estimate (prior to engine cutoff). 198 | _tgo :: U.Time a 199 | -- | Previous thrust angle. 200 | , _prevThrustAngle :: ThrustAngle a 201 | } deriving (Show) 202 | makeLenses ''AGCState 203 | 204 | 205 | -- | Engine shutoff time. 206 | newtype EngineShutoff a 207 | = EngineShutoff { unEngineShutoff :: U.Time a } 208 | deriving (Show) 209 | 210 | instance Semigroup (EngineShutoff a) where 211 | x <> _ = x -- accept the first shutoff command 212 | 213 | 214 | -- | Total simulation state. 215 | data Sim a 216 | = Sim 217 | { -- | Simulation time. 218 | _time :: !(U.Time a) 219 | -- | Time for engine shutoff (if known yet). 220 | , _engineShutoff :: !(Maybe (EngineShutoff a)) 221 | -- | Relevant state of the Apollo Guidance Computer. 222 | , _agcState :: !(AGCState a) 223 | -- | Dynamical state of the vehicle. 224 | , _dynamics :: !(Dynamics a) 225 | -- | Gradient of the dynamical state of the vehicle. 226 | , _ddynamics :: !(U.Time a :-* DDynamics a) 227 | } 228 | makeLenses ''Sim 229 | 230 | instance (Show a, a ~ Scalar a, Basis a ~ (), InnerSpace a, HasBasis a, Floating a) => Show (Sim a) where 231 | show sim = "Sim { _time = " <> (show (sim^.time)) <> "\n" 232 | <> " , _engineShutoff = " <> (show (sim^.engineShutoff)) <> "\n" 233 | <> " , _agcState = " <> (show (sim^.agcState)) <> "\n" 234 | <> " , _dynamics = " <> (show (sim^.dynamics)) <> "\n" 235 | <> " , accel = " <> (show (sim^.accel)) <> "}\n" 236 | 237 | 238 | -- | Getter for the current acceleration from 'Sim'. 239 | accel 240 | :: forall a p f. 241 | ( Profunctor p, Contravariant f 242 | , InnerSpace a, a ~ Scalar a, HasBasis a, Basis a ~ (), Floating a ) 243 | => Optic' p f (Sim a) (U.Acceleration (V2 MFCS a)) 244 | accel = to $ \sim -> 245 | let 246 | tDelta = 1 % [si| s |] 247 | dd = lapply (sim^.ddynamics) tDelta 248 | dv = dd^.dvel 249 | in 250 | dv |^/| tDelta 251 | 252 | 253 | ------------------------------------------------------------------------------- 254 | -- Commands (from the guidance computer back to the simulation) 255 | ------------------------------------------------------------------------------- 256 | 257 | 258 | -- | Commands returned by the Apollo Guidance Computer. 259 | data AGCCommand a 260 | = AGCCommand 261 | { _commandedThrustAngle :: !(ThrustAngle a) 262 | , _commandedEngineShutoff :: !(Maybe (EngineShutoff a)) 263 | } deriving (Show) 264 | makeLenses ''AGCCommand 265 | 266 | 267 | ------------------------------------------------------------------------------- 268 | -- Simulation constants 269 | ------------------------------------------------------------------------------- 270 | 271 | 272 | data Constants a 273 | = Constants 274 | { -- | Specific gravitational parameter. 275 | _moonSGP :: !(U.SGPUnit a) 276 | -- | APS exhaust velocity. 277 | , _apsExhaustVelocity :: !(U.Velocity a) 278 | -- | Mass flow rate of APS fuel. 279 | , _apsMassFlowRate :: !(U.MassFlowRate a) 280 | -- | Initial total mass of the vehicle (wet mass). 281 | , _initialMass :: !(U.Mass a) 282 | -- | Radial velocity triggering end of vertical rise phase. 283 | , _rDotFLVPEnd :: !(U.Velocity a) 284 | -- | When time-to-go gets below this value, request the engine 285 | -- to cut-off. 286 | , _tEngineThreshold :: !(U.Time a) 287 | -- | t2 time parameter: after time-to-go is less than this, hold 288 | -- all control parameters. 289 | , _t2_HoldAll :: !(U.Time a) 290 | -- | t3 time parameter: after time-to-go is less than this, use 291 | -- position control only. 292 | , _t3_PositionControl :: !(U.Time a) 293 | -- | Minimum threshold for "B" rate constant. 294 | , _bThreshold :: !(U.BThresholdUnit a) 295 | } deriving (Show) 296 | makeLenses ''Constants 297 | 298 | 299 | constants 300 | :: forall a. 301 | ( Fractional a, VectorSpace a, a ~ Scalar a ) 302 | => Constants a 303 | constants = 304 | let 305 | -- mdot is the APS mass flow rate 306 | mdot = 11.32 % (U.Pound :/ U.Second) 307 | -- tau0 is the initial mass / mass flow rate 308 | tau0 = 919.02 % [si| s |] 309 | in 310 | Constants 311 | { _moonSGP = 0.4902778e13 % [si| m^3/s^2 |] 312 | , _apsExhaustVelocity = 3030 % [si| m/s |] 313 | , _apsMassFlowRate = mdot 314 | , _initialMass = tau0 |*| mdot 315 | , _rDotFLVPEnd = 40 % U.Foot :/ U.Second 316 | , _tEngineThreshold = 4 % [si| s |] 317 | , _t2_HoldAll = 2 % [si| s |] 318 | , _t3_PositionControl = 10 % [si| s |] 319 | , _bThreshold = -0.1 % U.Foot :/ U.Second :/ U.Second :/ U.Second 320 | } 321 | -------------------------------------------------------------------------------- /src/LunarAscent.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE NegativeLiterals #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE QuasiQuotes #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | {-# LANGUAGE TypeApplications #-} 6 | {-# LANGUAGE TypeFamilies #-} 7 | {-# LANGUAGE TypeOperators #-} 8 | module LunarAscent where 9 | 10 | import Control.Lens ((<&>), (^.)) 11 | import Data.Basis (Basis, HasBasis) 12 | import Data.LinearMap ((:-*), linear) 13 | import Data.List (unfoldr) 14 | import Data.List.NonEmpty (NonEmpty) 15 | import qualified Data.List.NonEmpty as NonEmpty 16 | import Data.VectorSpace (InnerSpace, Scalar, VectorSpace, zeroV) 17 | import qualified Diagrams.Prelude as D 18 | 19 | import LunarAscent.AGC (gravAccel, p12) 20 | import LunarAscent.Types (AGCState (AGCState, _prevThrustAngle, _tgo), 21 | AscentTarget (AscentTarget), Constants, 22 | DDynamics (DDynamics, _dangle, _dangvel, _dmass, _dpos, _dvel), 23 | Dynamics (Dynamics, _angle, _angvel, _mass, _pos, _vel), 24 | EngineShutoff (EngineShutoff), MFCS, P2, 25 | Sim (Sim, _agcState, _ddynamics, _dynamics, _engineShutoff, _time), 26 | ThrustAngle (ThrustAngle), agcState, angle, 27 | angvel, apsExhaustVelocity, 28 | apsMassFlowRate, commandedEngineShutoff, 29 | commandedThrustAngle, dynamics, 30 | engineShutoff, initialMass, mass, moonSGP, 31 | pos, qv2, time, v2, v2Tuple, vel) 32 | import qualified LunarAscent.Types as Types 33 | import qualified ODE 34 | import qualified Plot 35 | import Units (si, ( # ), (%), (%.), (*|), (.#), (|*^|), 36 | (|*|), (|+|), (|-|), (|.-.|), (|.|), (|/|), 37 | (|^*|)) 38 | import qualified Units as U 39 | 40 | 41 | defaultAscentTarget :: AscentTarget Double 42 | defaultAscentTarget 43 | = AscentTarget 44 | (v2 5.9436 1679.2956 % [si| m/s |]) 45 | ((1731.1 % [si| km |]) |+| (18288.0 % [si| m |])) 46 | 47 | 48 | plotLunarAscentMoonView :: Plot.Output -> IO () 49 | plotLunarAscentMoonView output = do 50 | let 51 | burnSteps = burn BurnLastOnly 10 (Types.constants) defaultAscentTarget 52 | coastSteps = coast (Types.constants) (last burnSteps) 53 | rBurn = (eachN 10 burnSteps) <&> (\sim -> sim^.dynamics^.pos .# [si| km |]) <&> v2Tuple 54 | rCoast = coastSteps <&> (\sim -> sim^.dynamics^.pos .# [si| km |]) <&> v2Tuple 55 | Plot.plotOrbitSystem output 20.0 56 | (Plot.OrbitSystem 57 | { Plot.planet = Plot.Planet "Moon" 1731.1 D.lightgray 58 | , Plot.systemItems = 59 | [ Plot.Trajectory rBurn D.red 60 | , Plot.Trajectory rCoast D.green 61 | , Plot.AltitudeCircle 1749.2 "" D.gray 62 | ] 63 | }) 64 | 65 | 66 | plotLunarAscentVerticalRise :: Plot.Output -> IO () 67 | plotLunarAscentVerticalRise output = do 68 | let 69 | burnSteps = burn BurnIncludeAll 10 (Types.constants) defaultAscentTarget 70 | initRise = takeWhile (\sim -> sim^.time <= 16.0 % [si| s |]) burnSteps 71 | rInitRise = initRise 72 | <&> (\sim -> sim^.dynamics.pos .# [si| km |]) 73 | <&> v2Tuple 74 | <&> toRangeCoords 75 | <&> (\(x, y) -> (x * 1000.0, y * 1000.0)) 76 | twoSecondFlags = eachN 10 rInitRise 77 | Plot.xyChart 78 | output 79 | "Vertical Rise Phase (16 seconds)" 80 | "Down Range (m)" 81 | "Altitude (m)" 82 | [Plot.XRange -200 200] 83 | [ Plot.Line "Ascent Trajectory" rInitRise 84 | , Plot.Points "2s markers (one every 2s)" twoSecondFlags 85 | ] 86 | 87 | 88 | plotLunarAscentBurnOnly :: Plot.Output -> IO () 89 | plotLunarAscentBurnOnly output = do 90 | let 91 | burnSteps = burn BurnLastOnly 10 (Types.constants) defaultAscentTarget 92 | rBurn = burnSteps <&> (\sim -> sim^.dynamics.pos .# [si| km |]) <&> v2Tuple <&> toRangeCoords 93 | Plot.xyChart 94 | output 95 | "Lunar Ascent Burn Trajectory" 96 | "Down Range (km)" 97 | "Altitude (km)" 98 | [] 99 | [ Plot.Line "Burn Trajectory" rBurn 100 | ] 101 | 102 | -- | Convert MFCS to range coordinates (Down-range, altitude); units of km. 103 | toRangeCoords :: (Double, Double) -> (Double, Double) 104 | toRangeCoords (xmfcs, ymfcs) = (downRange, altitude) 105 | where 106 | altitude = sqrt (xmfcs*xmfcs + ymfcs*ymfcs) - 1731.1 107 | downRange = 1731.1 * atan2 xmfcs ymfcs 108 | 109 | 110 | -- | Which points to keep from the burn phase. 111 | -- 112 | -- It turns out that `diagrams` just isn't plotting properly if all the points 113 | -- are kept, so this lets us switch depending on the detail required. 114 | data BurnInterp 115 | = BurnIncludeAll -- ^ Include all interpolated points during the burn phase 116 | | BurnLastOnly -- ^ Include only the last point from each 2s burn phase 117 | 118 | 119 | burn 120 | :: forall a. 121 | ( InnerSpace a, a ~ Scalar a, RealFloat a, Basis a ~ (), HasBasis a ) 122 | => BurnInterp 123 | -> Int 124 | -> Constants a 125 | -> AscentTarget a 126 | -> [Sim a] 127 | burn burnInterp nSteps constants target = 128 | let 129 | step :: Sim a -> Maybe (NonEmpty (Sim a), Sim a) 130 | step s = 131 | let 132 | rs = burnStep nSteps constants target s 133 | in 134 | rs <&> \ne -> (ne, NonEmpty.last ne) 135 | in 136 | case burnInterp of 137 | BurnLastOnly -> NonEmpty.head <$> unfoldr step (initSim constants) 138 | BurnIncludeAll -> concat $ NonEmpty.toList <$> unfoldr step (initSim constants) 139 | 140 | 141 | -- | Take every nth item from a list, starting from the first. 142 | eachN 143 | :: Int -- ^ how many to drop 144 | -> [a] -- ^ input list of items 145 | -> [a] -- ^ output list of items 146 | eachN _ [] = [] 147 | eachN n (x:xs) = x : eachN n (drop (n - 1) xs) 148 | 149 | 150 | coast 151 | :: forall a. 152 | ( InnerSpace a, a ~ Scalar a, RealFloat a, Basis a ~ (), HasBasis a ) 153 | => Constants a 154 | -> Sim a 155 | -> [Sim a] 156 | coast constants sim = 157 | let 158 | f = gradFnNoThrust constants 159 | times = NonEmpty.fromList 160 | $ ODE.linspace 200 (sim^.time) (sim^.time |+| (10000 % [si| s |])) 161 | dynStates = NonEmpty.toList 162 | $ ODE.integrateWithDiff ODE.rk4Step (sim^.dynamics) times f 163 | 164 | mkSim (t, dyn, ddyn) 165 | = Sim 166 | { _time = t 167 | , _engineShutoff = sim^.engineShutoff 168 | , _agcState = sim^.agcState 169 | , _dynamics = dyn 170 | , _ddynamics = ddyn 171 | } 172 | in 173 | mkSim <$> dynStates 174 | 175 | 176 | burnStep 177 | :: forall a. 178 | ( InnerSpace a, a ~ Scalar a, RealFloat a, Basis a ~ (), HasBasis a ) 179 | => Int -- number of time steps in the 2 second period 180 | -> Constants a -- simulation constants 181 | -> AscentTarget a -- ascent target 182 | -> Sim a -- starting simulation state 183 | -> Maybe (NonEmpty (Sim a)) -- produced simulation time steps 184 | burnStep nSteps constants target sim = 185 | let 186 | -- consult the guidance computer 187 | (command, state) = p12 constants target sim 188 | 189 | -- figure out the ODE to solve for our current state 190 | cmdAngle = thrustAngleToGlobal (command^.commandedThrustAngle) (sim^.dynamics^.pos) 191 | angAccel = constAngAccel 192 | (2 % [si| s |]) 193 | (sim^.dynamics^.angle) 194 | cmdAngle 195 | (sim^.dynamics^.angvel) 196 | f = gradFn constants angAccel (command^.commandedEngineShutoff) 197 | 198 | -- integrate the ODE over the 2-second period with constant control 199 | times = NonEmpty.fromList 200 | $ ODE.linspace nSteps (sim^.time) (sim^.time |+| (2 % [si| s |])) 201 | dynStates = ODE.integrateWithDiff ODE.rk4Step (sim^.dynamics) times f 202 | 203 | -- mkSim converts the output of each integration step to a Sim 204 | mkSim 205 | :: (U.Time a, Dynamics a, U.Time a :-* DDynamics a) 206 | -> Sim a 207 | mkSim (t, dyn, ddyn) 208 | = Sim 209 | { _time = t 210 | , _engineShutoff = sim^.engineShutoff 211 | <> command^.commandedEngineShutoff 212 | , _agcState = state 213 | , _dynamics = dyn 214 | , _ddynamics = ddyn 215 | } 216 | 217 | -- check if we're actually after the engine shutoff time 218 | afterShutoff 219 | = maybe 220 | False 221 | (\(EngineShutoff tshut) -> tshut <= sim^.time) 222 | (sim^.engineShutoff) 223 | in 224 | if afterShutoff 225 | then Nothing 226 | else Just (mkSim <$> dynStates) 227 | 228 | 229 | gradFn 230 | :: ( InnerSpace a, a ~ Scalar a, HasBasis a, Basis a ~ (), Floating a, Ord a ) 231 | => Constants a 232 | -> U.AngAcceleration a -- ^ commanded angular acceleration 233 | -> Maybe (EngineShutoff a) -- ^ possible engine shutoff time 234 | -> (U.Time a, Dynamics a) -- ^ time and system dynamic state 235 | -> U.Time a :-* DDynamics a -- ^ gradient of dynamic state 236 | gradFn constants angAccel shutoff (t, dyn) = 237 | let 238 | phi = dyn^.angle # [si| |] 239 | vhat = qv2 (cos phi) (sin phi) 240 | thrustMag = ( (constants^.apsExhaustVelocity # [si| m/s |]) 241 | * (constants^.apsMassFlowRate # [si| kg/s |]) 242 | ) % [si| N |] -- the type checker just fails on this one 243 | 244 | -- only apply thrust if the time is prior to an engine shutoff command 245 | thrust = if (maybe True (\(EngineShutoff tshut) -> t < tshut) shutoff) 246 | then thrustMag |/| dyn^.mass |*^| vhat 247 | else zeroV % [si| m/s^2 |] 248 | gravity = gravAccel (constants^.moonSGP) (dyn^.pos) 249 | in 250 | linear 251 | $ \dt -> 252 | DDynamics 253 | { _dpos = dyn^.vel |^*| dt 254 | , _dvel = (thrust |+| gravity) |^*| dt 255 | , _dmass = -1 *| constants^.apsMassFlowRate |*| dt 256 | , _dangle = dyn^.angvel |*| dt 257 | , _dangvel = angAccel |*| dt 258 | } 259 | 260 | 261 | gradFnNoThrust 262 | :: ( InnerSpace a, a ~ Scalar a, HasBasis a, Basis a ~ (), Floating a ) 263 | => Constants a 264 | -> (U.Time a, Dynamics a) -- ^ time and system dynamic state 265 | -> U.Time a :-* DDynamics a -- ^ gradient of dynamic state 266 | gradFnNoThrust constants (_, dyn) = 267 | let 268 | gravity = gravAccel (constants^.moonSGP) (dyn^.pos) 269 | in 270 | linear 271 | $ \dt -> 272 | DDynamics 273 | { _dpos = dyn^.vel |^*| dt 274 | , _dvel = gravity |^*| dt 275 | , _dmass = 0 % [si| kg |] 276 | , _dangle = dyn^.angvel |*| dt 277 | , _dangvel = 0 % [si| 1/s |] 278 | } 279 | 280 | 281 | thrustAngleToGlobal 282 | :: ( InnerSpace a, a ~ Scalar a, RealFloat a ) 283 | => ThrustAngle a 284 | -> U.Length (P2 MFCS a) 285 | -> U.Count a 286 | thrustAngleToGlobal (ThrustAngle x) p = 287 | let 288 | pv = p |.-.| (zeroV %. [si| m |]) 289 | px = (pv |.| qv2 1 0) # [si| m |] 290 | py = (pv |.| qv2 0 1) # [si| m |] 291 | pangle = atan2 py px 292 | in 293 | U.quantity (pangle - x) 294 | 295 | 296 | constAngAccel 297 | :: ( VectorSpace a, a ~ Scalar a, Fractional a ) 298 | => U.Time a -- ^ time delta 299 | -> U.Count a -- ^ current angle 300 | -> U.Count a -- ^ desired angle 301 | -> U.AngVelocity a -- ^ current angular velocity 302 | -> U.AngAcceleration a -- ^ required (constant) angular acceleration 303 | constAngAccel dt theta_i theta_f omega 304 | = 2 *| ((theta_f |-| theta_i) |/| dt |-| omega) |/| dt 305 | 306 | 307 | -- | Initial simulation state. 308 | initSim 309 | :: forall a. 310 | ( HasBasis a, Basis a ~ (), Floating a, InnerSpace a, a ~ Scalar a ) 311 | => Constants a 312 | -> Sim a 313 | initSim constants 314 | = Sim 315 | { _time = 0 % [si| s |] 316 | , _engineShutoff = Nothing 317 | , _agcState = AGCState 318 | { _tgo = 370 % [si| s |] 319 | , _prevThrustAngle = ThrustAngle 0 320 | } 321 | , _dynamics = Dynamics 322 | { _pos = (v2 0 1731.1) %. [si| km |] 323 | , _vel = zeroV % [si| m/s |] 324 | , _mass = constants^.initialMass 325 | , _angle = U.quantity (pi/2) 326 | , _angvel = 0 % [si| 1/s |] 327 | } 328 | , _ddynamics = linear 329 | $ \(dt :: U.Time a) -> 330 | DDynamics 331 | { _dpos = (zeroV % [si| m/s |]) |^*| dt 332 | , _dvel = (zeroV % [si| m/s^2 |]) |^*| dt 333 | , _dmass = -1 *| constants^.apsMassFlowRate |*| dt 334 | , _dangle = U.quantity 0 335 | , _dangvel = 0 % [si| 1/s |] 336 | } 337 | } 338 | --------------------------------------------------------------------------------