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 algorithms for finding strong components and cycles. #65

Open
wants to merge 29 commits into
base: master
Choose a base branch
from

Conversation

gablin
Copy link

@gablin gablin commented May 3, 2017

No description provided.

Copy link
Contributor

@ivan-m ivan-m left a comment

Choose a reason for hiding this comment

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

Please also add some tests for these functions.

Module : Data.Graph.Inductive.Query.Cycles
Description : Algorithms for finding all cycles.
Copyright : (c) Gabriel Hjort Blindell 2017
License : 2-Clause BSD
Copy link
Contributor

Choose a reason for hiding this comment

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

FGL is licensed under 3-Clause BSD; in interests of using the same license for all, would you mind changing this?

(And some code from here is from Graphalyze, which isn't under your Copyright.)

Copy link
Author

Choose a reason for hiding this comment

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

Addressed as part of the reply to your comment below.

Copy link
Author

Choose a reason for hiding this comment

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

I decided to include a header, with the BSD3 license and copyright held by both me and you (due to copied functions from Graphalize package).

Description : Algorithms for finding all cycles.
Copyright : (c) Gabriel Hjort Blindell 2017
License : 2-Clause BSD
Maintainer : gabriel.hjort.blindell@gmail.com
Copy link
Contributor

Choose a reason for hiding this comment

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

Will you be maintaining this module?

(Actually, if you look at all the current FGL modules they tend not to bother with the header block at all, so you could just remove the entire thing.)

Copy link
Author

Choose a reason for hiding this comment

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

Ah, I didn't read the header carefully. =)

I've removed the header entirely.

Copy link
Author

Choose a reason for hiding this comment

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

Added header, but removed maintainer information.


import Data.Graph.Inductive.Graph

import Data.List
Copy link
Contributor

Choose a reason for hiding this comment

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

Please put import blocks on the same line, e.g.

import Data.List ((\\), delete, tails)

Copy link
Author

Choose a reason for hiding this comment

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

Fixed.

-- index, and its low link).
, sccGraph :: g a b
-- ^ The input graph.
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why no instances? At least Eq, Show and Read plaese.

Copy link
Author

Choose a reason for hiding this comment

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

Show, Read, and Eq added.

= SCCState
{ sccComponents :: [g a b]
-- ^ The components found so far.
, sccCurrentIndex :: Int
Copy link
Contributor

Choose a reason for hiding this comment

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

This (and others) should probably be banged.

Copy link
Author

Choose a reason for hiding this comment

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

I've not used bangs before, so I'm not sure of whether they apply here or not. I'll try looking into it.

, sccNodeInfo = M.insert n (True, i, i) (sccNodeInfo st0)
}
g = sccGraph st1
st2 = foldr ( \m st ->
Copy link
Contributor

Choose a reason for hiding this comment

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

This is large enough that I think a new function - even if it's in a where block - is warranted for documentation, etc.

That way, you can also use guards rather than nested if/else.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed.

}
g = sccGraph st1
st2 = foldr ( \m st ->
let st_ni = sccNodeInfo st
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason for snake_case here?

Copy link
Author

Choose a reason for hiding this comment

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

Did some refactoring, and the only use of snake_case here is to distinguish the low link of the n node from the low link of the m node.

-- ^ The current index.
, sccStack :: [Node]
-- ^ The node stack.
, sccNodeInfo :: M.Map Node (Bool, Int, Int)
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 a specific data structure with record labels would work better than a triple here; it would not only provide documentation as to what each field means, but you can then use them as selectors rather than pattern matching on the entire triple to get values out.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed.

( fromJust )
import Control.Monad
( ap )
import qualified Data.Map as M
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 an IntMap?

Copy link
Author

Choose a reason for hiding this comment

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

Ah, wasn't aware of that module. =)
Fixed.

-- ^ The components of the input graph.
, cisGraph :: g a b
-- ^ The input graph.
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Instances please

Copy link
Author

Choose a reason for hiding this comment

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

Show, Read, and Eq added.

Copy link
Contributor

Choose a reason for hiding this comment

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

Still needs bangs: ![[Node]], !(M.IntMap Bool), etc.

Copy link
Author

Choose a reason for hiding this comment

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

Bang patterns added.

@gablin
Copy link
Author

gablin commented May 4, 2017

I've addressed all requests now, except for the one regarding bang patterns.

@JeffreyBenjaminBrown
Copy link

Hi! I would like to detect cycles. Is this held up for lack of tests? I could write some ...

@gablin
Copy link
Author

gablin commented Sep 29, 2017

Don't know. I thought the tests as part of the change request were sufficient, but maybe it's due to the fact that the bang patterns are missing, but I don't know how to fix that.

@ivan-m Is there anything else I need to do in order to make the pull request go through?

c = fromJust $ cisCurrentComp st1
n_suc = suc c n
(st2, f) =
foldr ( \m (st, f') ->
Copy link
Contributor

Choose a reason for hiding this comment

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

This is big enough that it should be split into a separate function.

Copy link
Author

Choose a reason for hiding this comment

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

Now broken into several, smaller functions.

test_strongComponentsOf :: (Graph gr) => Proxy (gr a b) -> gr a b -> Bool
test_strongComponentsOf _ g =
sort (concatMap nodes (strongComponentsOf g)) == sort (nodes g)

Copy link
Contributor

Choose a reason for hiding this comment

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

Are you able to add a property as to the behaviour of the results of strongComponentsOf (e.g. disjoint)?

Copy link
Author

Choose a reason for hiding this comment

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

Test case has been expanded with additional property checks.

cycles :: (Graph g) => g a b -> [[LNode a]]
cycles g = map (addLabels g) (cycles' g)

-- | Same as 'cycles' but for unlabeled graphs.
Copy link
Contributor

Choose a reason for hiding this comment

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

Not for unlabelled graphs, just doesn't return the labels.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed.

Gabriel Hjort Blindell added 2 commits October 3, 2017 10:12
…between any two nodes belonging to separate components.
@gablin
Copy link
Author

gablin commented Oct 3, 2017

@ivan-m Requested changes have now been addressed.

then let (st1, f1) = cCircuits n st0
in (st1, f0 || f1)
else (st0, f0)

Copy link
Contributor

Choose a reason for hiding this comment

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

Could have been in a where clause rather than top level.

Maybe consider guards rather than nested if/then/else?

The fromJust can also potentially throw an error. Using PatternGuards can make that neater.

cCircuitsVisit n (st0, f0)
  | maybe False (n==) (cisS st0) = ...
  | fromMaybe False (M.lookup n (cisBlocked st0)) = ...
  | otherwise = ...

(Or else use case (cisS st0, M.lookup n (cisBlocked st0)) of (Just n', _) | n == n' -> ...; (_, Just True) ...).

Same goes with the other functions.

Copy link
Author

Choose a reason for hiding this comment

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

Now using guards and removed need for fromJust.

Gabriel Hjort Blindell added 3 commits October 3, 2017 10:38
…function parameter instead (this removes the need for using Maybe, which could fail).
… value as function parameter instead (this removes the need for using Maybe, which could fail).
@@ -100,45 +96,41 @@ cyclesFor n st0 =
filter (\c -> n `gelem` c) $
cisComponents st0
in if noNodes n_comp > 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that noNodes is O(|V|) for PatriciaTree (but O(1) for Tree); it would probably be better to do something like not . null . drop 1 . nodes $ n_comp which should be O(1).

Copy link
Author

Choose a reason for hiding this comment

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

Ah, didn't know that. Fixed.

@georgefst
Copy link

georgefst commented Dec 15, 2020

Any reason this never got merged? I could really do with it.

(I came here trying to work out whether the outputs of scc are guaranteed to be paths (EDIT: this can't be true in general - I'm not sure what I was getting at here), but this PR would solve my immediate problems and many more)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants