-
-
Notifications
You must be signed in to change notification settings - Fork 360
Added Forward Euler to Haskell #215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also beautify the code with hindent, it becomes so better readable 😄
@@ -0,0 +1,13 @@ | |||
solve_euler timestep n = take n $ iterate f 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not camelCase and explicit type signatures are better:
solveEuler :: Num a => a -> Int -> [a]
solveEuler timestep n = take n $ iterate f 1
(type signature suggested by ghmod)
solve_euler timestep n = take n $ iterate f 1 | ||
where f x = x - 3 * x * timestep | ||
|
||
check_result results threshold timestep = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same here:
checkResult :: [Double] -> Double -> Double -> Bool
checkResult results threshold timestep =
and $ zipWith check' results [exp $ -3 * i * timestep | i <- [0..]] | ||
where check' result solution = abs (result - solution) < threshold | ||
|
||
main = let timestep = 0.01; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is highly recommended to explicit the main type signature, even when it's just
main :: IO()
Hindent is pretty cool! |
solveEuler :: Double -> Int -> [Double] | ||
solveEuler timestep n = take n $ iterate f 1 | ||
where | ||
f x = x - 3 * x * timestep |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure whats more readable,
solveEuler :: Double -> Int -> [Double]
solveEuler timestep n = take n $ iterate f 1
where
f x = x - 3 * x * timestep
or
solveEuler :: Double -> Int -> [Double]
solveEuler timestep n = take n $ iterate (\x -> x - 3 * x * timestep) 1
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second one
This is just a personal preference, but Haskel being a functional programming, I think we should emphasize the functions. I believe that the function that calculates the acceleration should be a parameter in |
@jiegillet I see what you mean, but I asked about implementing the code as a lambda expression or a 'where' expression. Either way I think it could be simpler implementing the function as a parameter, like you said. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A possible solution for using general acceleration functions. As the code gets more dense it could be a great idea creating comments.
@@ -0,0 +1,20 @@ | |||
solveEuler :: Double -> Int -> [Double] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For implementing a general acceleration function it could be:
solveEuler :: (Double -> Double) -> Int -> [Double]
solveEuler accFun n = take n $ iterate accFun 1
(note that we don't need the 'timestep' anymore because we can infer it will be implemented in the acceleration function)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm maybe overdoing it, but if you want to be more general, something like
solveEuler :: (Particle -> Acceleration) -> Time -> Particle -> [Particle]
would be good, where Particle
represents a particle in space-time, something like like
type Particle = (Position, Speed, Acceleration, Time)
the first parameter is the physical model, the second parameter is the time step, because it shouldn't be part of the physical model, and the third parameter is the initial condition.
I'll admit that this kind of general thinking it not represented in any of the other implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using Doubles for now is good, but how would you implement the timestep given to the solveEuler function? I think it is unnecessary if we implement it in the acceleration function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, in my Verlet implementation (see link in my other comment), everything is either double or list of doubles (to use in more than 1 dimension). Giving them a name only makes it easy to reason about them.
We can't use the tilmestep in the acceleration function, the acceleration function is the f in: dy(t)/dt=f(t,y(t)). You give f any time, place and speed, it tells you what the acceleration is. Once you know the acceleration, you calculate the new position using the tilmestep and whichever method you like, in this case Euler. So these are used in different places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is what you meant:
solveEuler :: (Double -> Double) -> Double -> Int -> [Double]
solveEuler accFun timestep n =
take n $ iterate (\x -> x + accFun x * timestep) 1
as now you can pass the timestep and the acceleration function and it calculates the solution. the function (\x -> x + accFun x * timestep) should calculate the change in x for a given time and acceleration generated by the acceleration function.
Is this correct or I misunderstood what you are trying to say with the acceleration function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I have right now:
solveEuler :: Num a => (a -> a) -> a -> Int -> [a]
solveEuler func initVal n = take n $ iterate func initVal
Just pure forward euler and func
can be whatever you need.
main = | ||
let timestep = 0.01 | ||
n = 1 | ||
threshold = 0.01 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding a value for accFun:
accFun x = x - 3 * x * timestep
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sould the acceleration function be:
accFun x = -3 * x
n = 1 | ||
threshold = 0.01 | ||
in putStrLn $ | ||
if checkResult (solveEuler timestep n) threshold timestep |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And then adding the function as a parameter (we do not need timestep anymore):
if checkResult (solveEuler accFun n) threshold timestep
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So now with the timestep:
if checkResult (solveEuler accFun timestep n) threshold timestep
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it's better to leave timestep as a parameter in solveEuler, or have it be a part of accFun?
I'm thinking timestep is more closely related to solveEuler and should be a parameter so that you can try different timesteps without having to modify accFunc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't make the code smaller, but now I see what was the point of extracting the applied function because you can easily play with exiting the kinematics and checkFun functions.
solveEuler :: Num a => (a -> a) -> a -> Int -> [a] | ||
solveEuler func initVal n = take n $ iterate func initVal | ||
|
||
checkResult :: (Num a, Ord a, Enum a) => [a] -> (a -> a) -> a -> Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can simplify this
checkResult :: (Ord a, Num a, Num t, Enum t) => a -> (t -> a) -> [a] -> Bool
checkResult thresh check = and . zipWith (\i k -> abs (check i - k) < thresh) [0..]
@@ -0,0 +1,23 @@ | |||
solveEuler :: Num a => (a -> a) -> a -> Int -> [a] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that the Julia code returns n
values, but this is Haskell, have solveEuler
return an infinite list, we can limit ourselves to n
values down in main
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. Though now the whole function is feeling kinda off because it's (now more obviously) just solveEuler = iterate
. At that point why even define it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good idea to define it anyway because giving it a name gives it meaning.
In a more general setting, it's really supposed to do more than that, it would have another parameter, the time step, but you included it in kinematics
. It's OK like this, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After watching Leios's video I think I have a somewhat better idea of what this should look like.
The timestep should definitely be a solveEuler parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you definitely got it! This is much better structured.
There is one last thing that I think it a typo for you to fix and I can merge.
let timestep = 0.01 | ||
n = 100 | ||
threshold = 0.01 | ||
checkResult' = checkResult threshold $ exp . (\x -> x - 3 * x * timestep) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think here it should be (\x -> - 3 * x * timestep)
No description provided.