-
Notifications
You must be signed in to change notification settings - Fork 68
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
Edge labels #17
Comments
Perhaps an implementation based on line graphs (where the nodes are representative of edges and cliques uniquely represent nodes in the underlying edge-to-vertex dual graph). You might use multiparameter typeclass instances of the nodes to represent valid connection types overloading the type hierarchy for validity checking and label specification -- out of band, so speak. You could then treat the concrete linegraph as an edge 'schema' connected with a surjective relation of cliques to concrete edge-to-vertex dual graph nodes. Still, I'm just brainstorming. |
Thanks @justinlynn! Line graphs are cool, but so far I couldn't come up with a good way to represent them algebraically (without allowing some of their forbidden subgraphs). I also couldn't figure out how to efficiently (and algebraically) obtain the line graph of a given graph. So, I am a bit stuck in this direction, but I agree, this is a potential option to consider when dealing with edge labels. |
@snowleopard Excellent point -- now that you say it, I can't yet see that either. I suppose I've been coming at it from more of a data structure perspective and looking at how to cram the construction into the haskell typechecker while still maintaining invariants. Thanks heaps for taking a look and taking the time to provide some excellent feedback. Cheers! |
Hello. I'm loving alga so far and am looking into ways to make this labelled edge story nice. Here are a few thoughts:
More thoughts might come later. |
@alpmestan Hello, and thank you for sharing your thoughts! Let me answer point-by-point.
|
Tagged graphs as you introduce them in your last comment could work well indeed. I can certainly imagine this construct being useful. I however do think I need another solution for graphs with labelled edges, as I basically want a representation that'll only allow me to "tag" Regarding your suggestion of constructing the matrix "at the last minute", once we have described the entire graph and right before we run some algorithm on it, this would definitely work, I think. That's one possible approach. I have been thinking, since my last comment, about another route that doesn't go through the deep embedding. It is probably less efficient, but it is quite cute IMO. So, having an adjacency matrix and growing it all the time is a no-go. And I want something that doesn't restrict me to Most likely, this is all fun but terribly inefficient and we'll just go with your suggestion of going through Re: the algebra, it's interesting that this |
@alpmestan Yes, I see. Thinking about this led me to the following type class: class (Graph g, Monoid (Label g)) => LabelledGraph g where
type Label g
labelledEdge :: Label g -> Vertex g -> Vertex g -> g With the following two laws:
It doesn't look very nice though.
That's very cool, I like this idea! I'm very curious to see what you come up with -- please share the code when/if you manage to make this approach work :)
Same here! |
Late to the thread... putting labels on connect is what I was thinking about, too. Does it have to be a monoid though? Shouldn't a semiring be enough? Or do you think unlabeled edges are important? With "SET" as the semiring, this directly gives labeled multigraphs which is what I would want most of the time. |
@boggle You're not late, I think we're still looking for the right approach.
Good point, a semiring should be sufficient for combining edges. My previous attempts to extend the theory with labelled edges used monoid labels, but it's interesting to see what happens with a semiring.
Could you clarify what you mean by "SET" here? Plain sets do have the identity element (the empty set), so I guess you mean something else here. |
I meant the the normal "Set a" monoid. Except the monoid constraint isn't needed here. Actually the kind of graphs I'm interested in right now would only need "NonEmptySet a" which of course is just a semiring. Layering is possible, too, if the type of edge labels is a monoid, then "connect" can be used without specifying a label. |
@boggle I see, thanks for clarifying! Just to make sure I fully understand your use-case: you are looking for a graph type Interesting! |
Yes, that's what I had in mind. And by the way you've put it, I'm realizing if one actually works with a monoid bound, one could get a function "connected a b g" this gives mempty if a b are not connected in a b, the label otherwise. That might turn out to feel really natural and wouldn't require moving to NonEmptySet. It raisese the question though what "connect a b mempty" would mean - perhaps this is just a no-op. |
I think |
Yes, I agree. This made me now wonder for a moment if that would allow to not even have |
@boggle That's true! We've been experimenting with so-called 'conditional connect' which allows us to have the following encoding of data Graph a = Empty | Vertex a | ConditionalConnect Bool (Graph a) (Graph a)
overlay :: Graph a -> Graph a -> Graph a
overlay = ConditionalConnect False
connect :: Graph a -> Graph a -> Graph a
connect = ConditionalConnect True This case is not particularly interesting, since it's isomorphic to the datatype with two separate constructors data Graph b a = Empty | Vertex a | LabelledConnect b (Graph a) (Graph a)
overlay :: Monoid b => Graph b a -> Graph b a -> Graph b a
overlay = LabelledConnect mempty But there is more! If data Graph b a = Empty | Vertex a | LabelledConnect b (Graph a) (Graph a)
overlay :: BoundedLattice b => Graph b a -> Graph b a -> Graph b a
overlay = LabelledConnect bottom
connect :: BoundedLattice b => Graph b a -> Graph b a -> Graph b a
connect = LabelledConnect top This brings us back to the 'conditional connect' since Boolean algebra is a bounded lattice with Could this be the encoding we've been looking for? |
You could probably play the same trick with |
@alpmestan I used to think we need semirings, but I'm now leaning towards something lattice-like. Let's try to list all requirements. Below I use
This looks like a join-semilattice (https://ncatlab.org/nlab/show/semilattice) with two bounds ( I guess we don't need the meet operator |
My semiring obsession is almost cured, don't worry. So with your formulation, |
This is interesting! On the one hand it is really elegant to have neutral elements for both My use case would still be representing multigraphs by labeling edges with sets of tags. The empty set/lower bound would serve to represent Using a full lattice certainly works (and I think the commutativity of join is a good requirement for edge labeling). For representing multigraphs using tag sets as labels this would require to artificially introduce an upper bound Regular |
@alpmestan Yes, that's right. The idea is that This duality between
@boggle Yes, I agree. Ideally we'd like a better justification (than just elegance) for having a full lattice. Here are just a couple of random thoughts on this:
Alas, not convincing enough.
This was my intention, but now that you mentioned
You may be right. I'm a bit uncomfortable about picking a default label, which feels rather arbitrary, but I guess one can argue that all possible choices lead to isomorphic results... which seems to bring us to initial and final objects: for sets, the empty set is the initial object (and we'll use it for For Hask category, we would have chosen |
I've been thinking a bit more about the full lattice encoding for the Set case. What I like about this is that it leads to a information accumulation vs consolidation point of view:
This seems a very useful picture and it reveals overlay to just be union/join and connect to just be intersect/meet. |
@boggle We can avoid making the choice and simply require the user to provide a semi-lattice, plus a default label that will be used for -- We require additional laws for the underlying monoid: <> must be commutative and idempotent
class Monoid a => Label a where
defaultLabel :: a
overlay :: Label b => LabelledGraph b a -> LabelledGraph b a -> LabelledGraph b a
overlay = labelledConnect mempty
connect :: Label b => LabelledGraph b a -> LabelledGraph b a -> LabelledGraph b a
connect = labelledConnect defaultLabel This may be a temporary solution while we are still looking for the right existing abstraction. I believe this permits both interpretations (full lattice and initial-final). The accumulation vs consolidation point of view is indeed useful for many applications, but I think the basic abstraction should be as general as possible. One can of course build the accumulation vs consolidation machinery on top of the basic one (another graph-like typeclass with an additional connect-like method?). |
Ah! That is of course possible and indeed allows exploring this space more gradually. Might also be insightful to try to define how to encode an edge labeled graph into a node labeled graph which should be possible with a newtype, using either to distinguish between nodes used for nodes in the labeled graph, and nodes used for edges in the labeled graph, and defining connect accordingly. |
@boggle Indeed. We can do |
Hey all, I am currently considering whether to participate in the next GSoC and so I am trying to familiarize myself with this project. There are a couple of things in this thread I didn't understand, maybe you could help my out?
Thank you for your time! |
newtype Distinct a = Distinct a
instance Eq Distinct where _ == _ = False
instance Monoid a => Monoid (Distinct a) where
... Then |
@anfelor Hey, thanks for joining the conversation!
This thread has become quite confusing, because so many approaches have been intertwined in discussions. My current understanding is as follows (I am planning to push some code with this soon): We label edges with elements from a semiring data LG e a = E | V a | C e (LG e a) (LG e a)
overlay :: Semiring e => LG e a -> LG e a -> LG e a
overlay x y = C 0 x y
connect :: Semiring e => LG e a -> LG e a -> LG e a
connect x y = C 1 x y Then my understanding is that for instance Tropical e => Graph (LG e a) where
Vertex (LG e a) = a
... Here is a cool paper about semirings and tropical semirings and labelled graphs: http://stedolan.net/research/semirings.pdf @anfelor Now, that we've fixed a concrete vocabulary, do you still have the same questions? Or are some of them resolved? For example, I think your point (1) is no longer a concern, since |
I meant it as |
I see! Yes, without distinguishing |
I keep coming back to think about graphs with edge weights in terms of information loss/gain (where disconnected means "no information") and what that interpretation would require from connect/overlay. This leads me to believe that its perhaps not good to try to unify two pieces of information here:
As an experiment, let's assume the algebra tracks both overlay and connect but uses only a commutative monoid for weights like
This leads to a definition of
So rather than having two dual monoids (like in a semiring), the option monoid and the edge label monoid are nested.
This was written with the monoid |
@boggle As far as I understand, you want Can you give an example where the difference between |
@boggle Furthermore, in a semiring 0 doesn't need to be 0 and 1 doesn't need to be 1 (of the underlying structure). Take for example the longest distance semiring in the excellent "Fun with semirings" paper: data LongestDistance = LDistance Int | LUnreachable | LInfinite
instance Semiring LongestDistance where
zero = Unreachable
one = LDistance 0
x @+ LUnreachable = x
LUnreachable @+ x = x
LDistance x @+ LDistance y = LDistance (max x y)
... We would then define |
@boggle The set of laws @snowleopard and I agreed on so far (I believe) are as follows:
These imply:
That means that by using the bool semiring (0=False, 1=True, +=or, *=and) we can always recover the original algebra of graphs. We can extend the above set of laws by demanding that every element in our semiring except 0 has a multiplicative inverse (^-1) and extend the 2. law to:
This fits well with some semirings like the above mentioned instance Semiring LongestDistance where
zero = LUnreachable
one = LDistance 0
x @+ LUnreachable = x
LUnreachable @+ x = x
LDistance x @+ LDistance y = LDistance (max x y) -- or alternatively
| (abs x) < (abs y) = LDistance y
| otherwise = LDistance x
...
x @. LUnreachable = LUnreachable
LUnreachable @. x = LUnreachable
LDistance x @. LDistance y = LDistance (x + y)
inv LUnreachable = LUnreachable -- we don't want partial functions here => 0 has an inverse
inv (LDistance x) = LDistance (-x) Do you think that could be a useful addition? I think it could possibly help with augmented graphs; I will comment here when I have something presentable. I wish we could extend that to a ring, but unfortunately there are no idempotent rings so that is impossible without changing the algebra of graphs. |
@anfelor Yes, this sounds good. Have you formally proved the theorems? I haven't yet, but plan to do this in the Agda repository: https://github.com/algebraic-graphs/agda. By the way, I haven't thought about multiplicative inverses, that's pretty cool! To have a fixed common vocabulary, I pushed a branch with a draft implementation of edge-labelled graphs: https://github.com/snowleopard/alga/blob/edge-labels/src/Algebra/Graph/Labelled.hs. Note that I chose the class name |
No, I haven't yet, but I could do it if you want.
Sounds good! Related: |
@anfelor Sure, if you are familiar with Agda, feel free to send a PR to the Agda repository. Alternatively, if you'd like to prove it using your favourite prover, just share the link to the proof. A pen-and-paper proof is fine too, although it's harder to check :-) Your proof sketch for Graph laws looks correct.
By This notation worked pretty well. In Latex, it is For ASCII, your |
I will do that tomorrow or friday.
I prefer the shorter |
@anfelor I've been reviewing the definition and theorems of labelled algebraic graphs and realised that we never use distributivity of dioids (in fact, I don't think we even use the dioid multiplication at all). Perhaps, we should relax the constraint and talk about a semilattice with both least and greatest bounds? It feels a bit awkward, but is most general. It's interesting that in order to introduce transitively-closed graphs, we will need the multiplication in order to define the following axiom:
We'll also need multiplication and distributivity to talk about the notions of reachability, paths, etc. |
@snowleopard Do you mean something like this? data LabelledGraph {S eq} (d : Semilattice S eq) (A : Set) : Set where ...
connect, overlay, ... on LabelledGraph
data StructuredGraph {D eq} (d : Dioid D eq) (A : Set) : Set where ...
reachable, transitive-closure, ... on StructuredGraph
embed :: ∀ {A D S eqd eqs} {d : Dioid D eqd} {s : Semilattice S eqs } -> StructuredGraph d A -> LabelledGraph s A But I don't see yet how to embed a semilattice into a dioid. In Haskell: class Semilattice a where
high :: a -- For all x: x `join` high = high
low :: a -- For all x: x `join` low = x
join :: a -> a -> a -- associative, idempotent, commutative
class Dioid a where
zero :: a
one :: a
(+) :: a -> a -> a
(*) :: a -> a -> a
instance (Dioid a) => Semilattice a where
join = (+)
low = zero
high = ?? Now, PS: I like your implementation of |
Sorry, messed up the link at first; I was referring to http://stedolan.net/research/semirings.pdf |
@anfelor Yes:
Aha, interesting! If we define We could do the following, assuming overlay :: Semilattice e => Graph e a -> Graph e a -> Graph e a
overlay = LConnect low
connect :: Dioid e => Graph e a -> Graph e a -> Graph e a
connect = LConnect one It's a bit less symmetric, but perhaps most general. Does this sound better to you?
Cool :) It does look nice, especially with the Hasklig font I'm using. The only (minor) issue is that |
It sounds like a relatively easy change to make with potential benefits to users and much better than changing the dioid laws. It would also improve the usefulness of alga as a general graph representation.
Unfortunately, the semilattice class above has a name clash with monadic join and for meet-semilattices the relationship is reversed by convention, eg |
@anfelor What about using the following class hierarchy? -- Associative, commutative and idempotent |+| with identity 'zero'
class Semilattice a where
zero :: a
(|+|) :: a -> a -> a
-- Associative |*| that distributes over |+| with identity 'one'
class Semilattice a => Dioid a where
one :: a
(|*|) :: a -> a -> a |
Hi. I'm jumping into the conversation late but I wanted clarification on what is wrong with the naive labelled connect operations. I don't see how it makes sense to talk about associativity of two different operations. What makes sense to me is asking whether they commute, and they don't:
However, they still seem to satisfy some decomposition laws:
These laws seem intuitive to me and I think they hold for labelled graphs in general although I haven't proved it. In particular, the two ways of decomposing the following expression agree:
|
Hi @danharaj, welcome! I agree with you: different labels lead to different operations, so associativity makes little sense (but when the labels are the same, associativity does hold). Also, your 4-way decomposition is very nice, I'll use it in the examples/tests, thank you! But I don't understand this remark:
What do you mean by "naive labelled connect operations"? As far as I'm concerned, there is nothing wrong with our current design, and it does look rather straightforward to me, one could even call it naive :-) Essentially, we just add labelled connect operations and describe how they work by postulating a few axioms. We also establish a link with unlabelled graphs by noting that |
Sorry, I got tripped up reading this thread; it seemed like the approach where you have one connect operator per edge label was inadequate and an alternative formulation was sought. I call it naive because it's the very first thing one would try and hope it works :) I see now that this is a special case of the formulation you all have been discussing via the lattice of subsets of edge labels. |
Aha, I see :) This thread did get pretty long and confusing. I guess I better open a new one, describing the chosen design, just to figure out the remaining bits. Will do that in a few days. |
I gave a Haskell eXchange 2018 talk about edge-labelled algebraic graphs: https://skillsmatter.com/skillscasts/12361-labelled-algebraic-graphs It mostly builds on the abstractions we discussed in this issue, but in the end I decided to go for a different hierarchy of type classes for edge labels: I intend to write a blog post about this, providing further details. @anfelor Any comments? Do we need to modify your Agda proofs? I guess all results you proved for dioids still hold, but we might have some other interesting theorems for the case when the type of labels is not a dioid, but something weaker -- a monoid or a semiring. |
This completes most of the work on adding edge labels #17.
I think we can finally close this 🎉 I'm quite happy with the final design. There are a few unpolished spots, but I'll be opening separate issues for them if need be. Many thanks to everyone for an amazing discussion! |
The current implementation has no support for edge labels and this is a serious limitation. Let's use this issue to discuss possible designs.
As an example, consider a graph with vertices
1
,2
,3
and labelled edges1 -{a}-> 2
and2 -{b}-> 3
.The paper mentions this approach:
Here
b
stands for the type of labels that can be attached not only to edges, but also vertices and arbitrary subgraphs. Function applicationg x
acts as a projection: it extracts the subgraph labelled withx
.A similar, but somewhat simpler approach is to use lists of pairs instead of functions:
To obtain a projection, we filter the list by the edge label and overlay the graphs. The advantage of this approach compared to the above is that it's more explicit, and we can easily implement equality checking.
A very different approach is to add a new labelling operator
[b]
to the algebra. Here to obtain a nice algebraic structure the type of labelsb
need to form an idempotent semiring. For example, this works very well for Boolean labels:[0]a = empty
[1]a = a
[x]a + [y]a = [x | y]a
[x][y]a = [x & y]a
[x](a + b) = [x]a + [x]b
[x](a * b) = [x]a * [x]b
This should also work well for numeric labels, e.g. usually a distance label
x
on an edge corresponds to the interval[x; infinity)
in terms of possible time it takes to traverse the edge, and such intervals, I believe form an idempotent semiring.The cost is a new graph construction primitive:
I haven't yet tried any of these approaches on a serious example.
I'm pretty sure there are other approaches and the design space is quite large. Do you have any suggestions?
The text was updated successfully, but these errors were encountered: