Skip to content

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

Merged
merged 6 commits into from
Jul 8, 2018

Conversation

Thurii
Copy link
Contributor

@Thurii Thurii commented Jul 4, 2018

No description provided.

@june128 june128 added the Implementation This provides an implementation for an algorithm. (Code and maybe md files are edited.) label Jul 4, 2018
Copy link
Contributor

@lulucca12 lulucca12 left a 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
Copy link
Contributor

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 =
Copy link
Contributor

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;
Copy link
Contributor

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() 

@Thurii
Copy link
Contributor Author

Thurii commented Jul 4, 2018

Hindent is pretty cool!
Set the types to Doubles, didn't realize how generalized checkResult had become.

solveEuler :: Double -> Int -> [Double]
solveEuler timestep n = take n $ iterate f 1
where
f x = x - 3 * x * timestep
Copy link
Contributor

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second one

@jiegillet
Copy link
Member

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 solveEuler. This way, the Euler algorithm is general, and we can choose the Newton equations along with the time steps and other parameters down in main . I submitted code for the Verlet algorithm (way at the bottom, not in the text, I should fix that) if you want to see what I mean.

@jiegillet jiegillet self-assigned this Jul 5, 2018
@lulucca12
Copy link
Contributor

@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.

Copy link
Contributor

@lulucca12 lulucca12 left a 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]
Copy link
Contributor

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)

Copy link
Member

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.

Copy link
Contributor

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

Copy link
Member

@jiegillet jiegillet Jul 5, 2018

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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

Copy link
Contributor

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
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Contributor Author

@Thurii Thurii Jul 5, 2018

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.

Copy link
Contributor

@lulucca12 lulucca12 left a 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
Copy link
Member

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]
Copy link
Member

@jiegillet jiegillet Jul 6, 2018

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

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

@jiegillet jiegillet left a 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)
Copy link
Member

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)

@jiegillet jiegillet merged commit 73f5060 into algorithm-archivists:master Jul 8, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Implementation This provides an implementation for an algorithm. (Code and maybe md files are edited.)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants