Skip to content
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

Added Gaussian Elimination in Haskell #193

Merged
merged 35 commits into from
Jul 31, 2018

Conversation

jiegillet
Copy link
Member

  • Added implementation
  • Corrected "Eschelon" to "Echelon" (which comes from a French word meaning step of a ladder)
  • Minor typos
  • Removed "Reduced Row Eschelon Form is diagonal" since it's not true in general
  • Removed "You can do basically anything you want" since if you start from the upper left you'll have to do several passes. While technically you can always do what you want, maybe it's best to suggest the most efficient route.
  • James refers to his Julia code as "pseudocode", I didn't want to change the text so my crazy haskell code is now "pseudocode" as well (it works for crazy FP people maybe)

@jiegillet jiegillet added Implementation This provides an implementation for an algorithm. (Code and maybe md files are edited.) Chapter Edit This changes the archive's chapters. (md files are edited.) labels Jul 1, 2018
@leios
Copy link
Member

leios commented Jul 2, 2018

I just realized that the text for this chapter was written way back when I wanted to use pseudocode for this project, as such, line 264 can probably be deleted. If we are doing chapter edits anyway, maybe we can do this here? (sorry for the extra work)

@jiegillet
Copy link
Member Author

Happy to do it.

@leios
Copy link
Member

leios commented Jul 2, 2018

Is there any way to get lulucca12 (#152 -- I cannot address him for some reason) to help with haskell review?

@jiegillet
Copy link
Member Author

That'd be great. (I cannot address him either, maybe because he never submitted a PR).
I got a couple of hints from hlint on this.

@jiegillet jiegillet mentioned this pull request Jul 2, 2018
@jiegillet jiegillet force-pushed the gaussianElimination branch from 6479bd2 to dbde5c2 Compare July 2, 2018 06:24
@jiegillet jiegillet force-pushed the gaussianElimination branch from dbde5c2 to 57b8e7a Compare July 2, 2018 06:28
@jiegillet
Copy link
Member Author

jiegillet commented Jul 2, 2018

===> Changed pseudocode to code
Sorry for the mess of commits everyone, I messed up some things. Should be fixed now :(

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.

It can be beautified automatically with "hindent" and an extension to atom (atom-beautify) or vscode.

go (r, c) m = foldr (combRows (r, c) 1) (multRow r (1/(m!(r, c))) m) [r1..r-1]


printM :: (Show a) => Matrix a -> String
Copy link
Contributor

Choose a reason for hiding this comment

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

ghc-mod suggested that this could be defaulted to Double to add a constraint:

printM :: Matrix Double -> String
printM m =
  let ((r1, c1), (rn, cn)) = bounds m
   in unlines
        [ intercalate "\t" [take 7 $ show $ m ! (r, c) | c <- [c1 .. cn]]
        | r <- [r1 .. rn]
        ]

@jiegillet jiegillet force-pushed the gaussianElimination branch from 7ceb7bd to 05639f3 Compare July 20, 2018 08:32
@jiegillet
Copy link
Member Author

Updated my code, it's cleaner now, also added back substitution. Got rid of chapter edits.

@jiegillet jiegillet removed the Chapter Edit This changes the archive's chapters. (md files are edited.) label Jul 20, 2018
@lulucca12
Copy link
Contributor

I think before merging this PR(now that #219 is merged) we could format and factor out the code, passing it through a linter (and maybe a beautifier) to verify that all the added code it's all optimized and good.

@jiegillet
Copy link
Member Author

I ran it through hlint and got no hints. I tried a beautifier (hindent I think) but it made it worse :)
Do you have suggestions for factoring out? Each function is pretty short already.

@lulucca12
Copy link
Contributor

lulucca12 commented Jul 26, 2018

We should do some refactoring and make the code as clean and declarative as possible, so, when possible, we could substitute one letter parameters to the functions and make there purpose clear. Also, I saw that you created two print functions (printM for matrix and printV for vectors) if you made the type synonyms type declarations we could derive them (using instance) of the Show type class.

@jiegillet
Copy link
Member Author

jiegillet commented Jul 27, 2018

Single letter parameters are mostly matrix indices, r for row and c for column. Changing the name will make the code less readable I think. I also use m for matrix, I suppose I could change that.

Do you have concrete suggestions for making the code clean and declarative? Honestly I've done my best here.

I'll look into the Show instances, I actually started doing it that way, but it got complicated I don't remember exactly why, maybe because I need a newtype rather than type? I'll look into it.
EDIT: since Array a b is already an instance of Show, you can't make Matrix a an instance as well. Well, you can if you use two pragmas: {-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} but even then the print function still refuses to work since it doesn't know which to chose from. On top of that, I have Vector a which is yet another type synonym. Overall not worth it, I think, using printM is fine.

@lulucca12
Copy link
Contributor

I think there is just a little issue on how this code is written, and from the fact that you created type synonyms to make the code more understandable: There is no way of someone with just the type declaration of any function rewrite that function with given context. To see what I mean with that take an example: the function compose

compose :: (b -> c) -> (a -> b) -> a -> c

it's really clear(even if you don't give an explicit name) that you can use techniques for programming with context (like using Category Theory, or in a much simpler way Hole-driven Haskell) and generate the function

compose f g x = f (g x)

and I understand that this is not always possible with complex algorithms like this one, but making polymorphic types and explicit notations can make this way of Haskell programming better.

@lulucca12
Copy link
Contributor

And for the Show instances, could you write your approach in code for me to understand it?

@jiegillet
Copy link
Member Author

jiegillet commented Jul 28, 2018

It's absolutely impossible to write algorithms with the intent of letting people be able to rewrite the code from the signature alone. For example if you're computing convex hulls, there are many methods, but all have the same signature. In the case of code like this, the signature is there to explain how the code is used. For example swapRows :: Int -> Int -> Matrix a -> Matrix a. This is a function that swaps rows, it takes two Int, those must be the rows to flip and that's that.

About polymorphism, the only non-polymorphic type I have is Int the index to the array, and I could make it Ix i => i instead, but it wouldn't change anything on the level you're talking about. Not all code can be polymorphic, here we have a set a functions that transform matrices is other matrices, there is only one type.

for Show, I suggest you try yourself to make Matrix a an instance of Show. Basically, making an new instance for a type won't work because the compiler can't tell the difference between a type synonym and the original type. For the instance to work, you have to use a newtype like newtype Matrix a = Matrix { fromMat :: Array (Int, Int) a }. I got that to work, the problem is it makes the code more verbose because any time you use a function, you have to unwrap the array (using fromMat), re-wrap it (using Matrix) whenever you need another function like swapRows, unwrap it to keep using it and wrap it to export it. I don't think it's worth it just to avoid using printM instead of print.

While I appreciate your criticisms, they are a bit vague and disconnected from the actual algorithm. I'd like to have some concrete ideas on what I can do better.

@lulucca12
Copy link
Contributor

I said that because it's a common use and necessity for giving your code context (creating new type classes and deriving from existing ones), but as I said not all code is possible to be created that way, it's just a common practice when refactoring code trying to make the context of a function like the other functions.
I think the code is fine, it's just not so readable as the other language implementations, even in functional style.

@jiegillet
Copy link
Member Author

I know it's not very readable, haskell is not super suited for matrix operations (my FFT code is horrendous). I'm very happy to try and make it better, do you have concrete suggestions?
I tried making Matrix an instance of Show, but that add verbosity everywhere, not worth it.

@lulucca12
Copy link
Contributor

lulucca12 commented Jul 28, 2018

Have you seen Data.Matrix? I think we could rewrite the code using its functions and make it more clear. For example, they have a prettyMatrix function for displaying matrices. It could make it more clear.

@lulucca12
Copy link
Contributor

lulucca12 commented Jul 28, 2018

You could use Data.Vector with it (but it can cause ambiguity)

@jiegillet
Copy link
Member Author

Data.Matrix is not in the standard distribution, I don't think I should use that, even though it has some functions that I could use (switchRows, combineRows). Even if I did use these functions, the core algorithm (gaussJordan, backSubstitution) wouldn't change at all. All really, if you look at the source course for switchRows for example, it's not much more beautiful than my code.

Data.Vector is in the standard distribution, I think, but it's based on Data.Array to begin with, and it doesn't have many function I could use.

All in all, none of my functions are longer than 11 lines. They may be dense and a bit hard to read, but it's the reality of the algorithm.

@lulucca12
Copy link
Contributor

Being on the standard distribution or not is not a problem if you say clearly this information in the code and I really think that you could shorten your code without defining again what was already defined and can be standard. And it's good to use Haskell libraries because it gives your code more readability. Do you prefer to use "\f g -> (\x -> f(g x))" or "."?

@lulucca12
Copy link
Contributor

Have you seen the functions

inverse :: (Fractional a, Eq a) => Matrix a -> Either String (Matrix a) 

and

rref :: (Fractional a, Eq a) => Matrix a -> Either String (Matrix a) 

the last one converts a matrix to reduced row echelon form using Gaussian elimination and thus solving a system of linear equations.
If you don't want to use the built-in algorithm you can implement it using it's source (I think it's worse in readability, though). You can take a look at all of them and notice that you can shorten your code by (at least) 3 functions implemented (switchRows, prettyMatrix and yours printV that's not necessary because of Vector in Data.Vector being an instance of Show)

@jiegillet
Copy link
Member Author

Of course I can't use a built-in algorithm, that defeats the purpose. Overall I'm pretty reluctant to rely too much on outside source, even if it makes my code shorter. I like the idea of having everything you need in one file. And so far, that's been the philosophy of the AAA for all languages.

@leios
Copy link
Member

leios commented Jul 31, 2018

I would like to say that the point of this chapter is to show how the algorithm could be implemented in haskell, so using an in-built rref or inverse function kinda defeats the purpose.

I don't know how many people use the Data.Matrix, but if it's standard enough (like numpy or scipy), then I suppose we can use it. I don't mind using external libraries (like fftw) if they are common enough and necessary to perform an action.

That said, using a vector/matrix library it might be overkill for this case.

@lulucca12
Copy link
Contributor

lulucca12 commented Jul 31, 2018

The purpose was just to shorten the code, I mentioned this functions for you to look at their source and how they are implemented and to get an idea, but for factoring the code I think you can use the library just for their print and prettier functions but other ones that can help too, like the switchRows and switchCols that you don't have to implement them by yourself.

@jiegillet
Copy link
Member Author

Yeah I understand your purpose. I also love short, point-free style functions. It's the beauty of Haskell. But I can't refactor a 10 lines function down to 2 even with other people's build in functions. I've already said that I prefer using my own code, especially that my functions are not especially worse than the ones in Data.Matrix.

You haven't criticized a single specific line of code, just the general look of it. You gave me no reason to believe that you actually read my code at all past the signatures. If it all comes down to esthetics and personal preference, I ask that you respect my choices as the author of the code. If you read the code and have no specific improvements in mind, provide a positive review. If you haven't, leave it to someone else to do the review.

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.

For me, the problem isn't specific to your code, but to the fact that you step back when using other's code. If you really want to use your code and make the file bigger, do so, if not implement it with the library. I don't see how I can improve the code apart from that.

Copy link
Member

@leios leios left a comment

Choose a reason for hiding this comment

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

If @lulucca12 is happy, I'm happy. I understand that there is still discussion to be had here, but it seems like that discussion boils down to personal preference.

@jiegillet jiegillet merged commit 113e6d4 into algorithm-archivists:master Jul 31, 2018
@jiegillet jiegillet deleted the gaussianElimination branch August 4, 2018 12:28
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.

3 participants