7
$\begingroup$

How can I force Graph to evaluate all its options without actually displaying the graph itself?

Evaluating the following code ignores the Print statements in VertexShapeFunction, EdgeShapeFunction and possibly in other options too meaning that these options are not evaluated and I assume that the graph is not built at all, until displayed (notice the ; at the end):

g = Graph[{Print["entered Graph"]; 1 -> 2, 2 -> 3, 3 -> 1}, 
  VertexShapeFunction -> (Disk[Print[#]; #, .1] &), 
  EdgeShapeFunction -> (Line[Print[#]; #] &), 
  ImageSize -> {Print["imagesize"]; 100, 100}];

entered Graph

imagesize

I assume this is because of some kind of optimization going on in the background, like when dynamic objects are only updated when they are onscreen. However, this delayed behaviour is inconsistent with other functions' policy on options (see later) and is rather dangerous, as now calling for the presumed static object g, I do not expect extra evaluations:

g

{-0.866025,-0.5}

{0.866025,-0.5}

{1.83697*10^-16,1.}

enter image description here

For comparison, a similar (partly dynamic) object is a chart, which behaves as expected (again, note ;):

BubbleChart[{{1, 0, 1}, {0, 1, 2}, {1, 2, 3}},
    ChartElementFunction -> (Disk[Print[#]; First@#, .1] &)];

{{0.942265,1.05774},{-0.11547,0.11547}}

{{-0.0816497,0.0816497},{0.836701,1.1633}}

{{0.9,1.1},{1.8,2.2}}

In general, I had the impression, that functions work like BubbleChart (i.e. Plot, ListPlot, etc.) so Graph is the odd one out. Is this delayed evaluation a feature or a bug?

$\endgroup$

2 Answers 2

5
$\begingroup$

It seems that it was chosen to have "graphs" have as FullForm an expression that has head Graph. A plot made by Plot has a FullForm whose head is not Plot and an "expression" made by BubbleChart does not have head BubbleChart. Expressions generated by BubbleChart and Plot have Head Graphics. It seems that the "graph" is indeed only "generated" when it has to be displayed. The following code "defines a graph"/ties the symbol g to an expression with Head Graph. It shows that whenever we evaluate g (and therefore the expression with Head Graph), Mathematica indeed goes once through all the rules, but that is just normal evaluation as the attribute NHoldAll is not like HoldAll in this sense. Now consider the code

With[
 {
  unevGraph =
   Unevaluated @@ Hold[
      Graph[
       graphBody,
       VertexShapeFunction -> optVal1,
       EdgeShapeFunction -> optVal2,
       ImageSize ->  optVal3
       ]
      ] /. 
    {
     graphBody :> {Print["entered Graph"]; 1 -> 2, 2 -> 3, 3 -> 1},
     optVal1 :> (Print[
        "vSFOption Evaluated"]; (Disk[
          Print["printed by vertex shape"]; Print[#]; #, .1] &)),
     optVal2 :> (Print[
        "eSFOption Evaluated"]; (Line[Print["printed by edge shape"]; 
          Print[#]; #] &)),
     optVal3 :> (Print["iSOption Evaluated"]; {100, 100})

     }
  }
 ,
 g := unevGraph;
 ]
 g//FullForm

The code prints that the Options are evaluated and then shows the FullForm of g. It does not print the other messages. A little bit more explanation about the code above can be found in a section below.

We can print the messages without displaying the graph by calling

h = ToBoxes[g];

--prints--> 'all the messages'

We can then set

g2 = ToExpression[h];

So that g2 really refers to an expression with head Graphics. Indeed it does not print any messages!

For those interested, note that even

HoldForm @@ Hold[g2] /. OwnValues[g2]

yields the image of the graph, but I guess that was to be expected.

We seem to have effectively created an expression with head Graphics corresponding to our graph g, in a (at least somewhat) analogous manner as Plot and BubbleChart create such expressions. It now seems clear that the reason Graph does not automatically make such conversions is that expressions with Graph head can also be used in calculations. I.e. we want to be able to use an expression with head Graph as an argument of for example Subgraph, rather than some expression with head Graphics.

Things that do not work

Something that looks similar can be achieved by evaluating a graph (for example g) and then setting g4 = %. However, g4 then refers to an object with head Graph. Note that copying and pasting the graph that was displayed by evaluating g will also not work.

Remarks about the first code block above

The first code block may seem a bit complicated. Especially the fact that an intermediate expression g:=Unevaluated[stuff] occurs may be a bit confusing. Note that g:=stuff is equivalent to g:=Unevaluated[stuff], so there is nothing special there. In fact I use that these expressions are equivalent, to replace the Hold with Unevaluated, which will be automatically stripped by SetDelayed. The only reason I use the Hold and RuleDelayed is because I wanted to insert the "option evaluated" messages. All of this is really not necessary to convert a Graph to its graphical representation. This can be seen in the section below.

Compact example of making the expresion with Head Graphics

Consider the following code,

g3 =
 ToExpression@ToBoxes@Graph[{1 -> 2, 2 -> 3, 3 -> 1},
    VertexShapeFunction -> (Disk[Print["printed by vertex shape"]; 
        Print[#]; #, .1] &),
    EdgeShapeFunction -> (Line[Print["printed by edge shape"];
        jPrint[#]; #] &),
    ImageSize -> {100, 100}
    ];

After evaluation of the code, g3 refers to an expression with head Graphics. Evaluating g3 in a new cell (and indeed in many other places, everything is as expected) shows the graph.

I think we must conclude this is a feature, not a bug :)

Less important: Non graphical options

Note that non graphical options are not "evaluated" when we display the graph. There is actually pretty strange behavior here. If we set

graph =
  Graph[{1 -> 2, 2 -> 3, 3 -> 1},
   VertexShapeFunction -> (Disk[Print["printed by vertex shape"]; 
       Print[#]; #, .1] &),
   EdgeShapeFunction -> (Line[Print["printed by edge shape"];
       Print[#]; #] &),
   ImageSize -> {100, 100},
   VertexWeight :> {4, Print["vertexWeight"]; 5, 6}
   ];

We have

graph

--print--> messages

--shows--> a graph

But "vertexWeight" is not among the messages. Furthermore, we have

PropertyValue[{graph, 1}, VertexWeight]

-> 4

Whereas

PropertyValue[{graph, 2}, VertexWeight]

--prints--> "vertexWeight"

-> 5

The point here is that options are "evaluated" as needed. Graphical options are used when ToBoxes is used/an output cell is made. I suppose that is also the efficient way of doing things if you also want to be able to do fast calculations with graphs.

Less important: Storage and query of options

We define g7 as follows,

mmm = 8000;
nnn = 1000;
With[
 {mmm = mmm,
  nnn = nnn},
 g7 = Graph[Append[Array[# -> # + 1 &, mmm], mmm -> 1],
    EdgeShapeFunction -> Function[c++; If[
       (*Mod[c,nnn]==0*)False, Print[#]]; Line[#]]
    ];

 ]

We see that defining g7 in this way takes hardly any time at all. But we have

Timing[PropertyValue[{g, 2555}, VertexCoordinates]]

--> {3.701786, {1707.87, 2453.73}} (and prints nothing)

And

Timing[ToExpression[ToBoxes[g]]][[1]]

--prints--> 10 messages

-> 6.282605

This suggests to me Mathematica has a way of building the VertexCoordinates without making the entire graphics of the graph. However, this calculation of the VertexCoordinates is not done when setting g7=Graph[...]. This makes sense, as it has to do only with graphics, so that if you are interested in subgraphs, you really don't want this to be calculated. Calculating the same PropertyValue twice does not take shorter, so I really don't think anything is stored, unless you store the graph graphics in the way I described. But then we can probably not find the values as easily as with PropertyValue.

Less important remarks

Throughout this answer my use of language may have been a bit awkward. Of course, I just want to be precise. For example it is obviously not the case that the evaluation of g3 causes a graph to be displayed, as we can simply use CompoundExpression to not display the graph. It seems that Mathematica does two things in evaluating input (or at least this helps me to talk about things). First, it evaluates an "input" to some intermediate expression that does not change if you evaluate it again. Then it builds the output cell. It seems that whenever the expression that g3 is tied to appears in the FullForm of such an intermediate expression, the graph is displayed. The example with HoldForm above is an example of this. The same is of course not true for g (defined in the first code block), evaluating a cell with the content

Hold[g] /. OwnValues[g]

Does not display a graph. As another example, note that even

OwnValues[g3]

displays a graph.

I hope somebody will have something more to say about this :).

$\endgroup$
12
  • $\begingroup$ Thanks Jacob, this should be the baseline. Though I was looking for something less hands-on as I don't want to manually set up each option. $\endgroup$ Commented Apr 12, 2013 at 14:46
  • $\begingroup$ @IstvánZachar sorry, I wrote this in a rush, I will edit again. One moment please. $\endgroup$ Commented Apr 12, 2013 at 20:40
  • $\begingroup$ @IstvánZachar Ok, I think the story is quite complete now :) $\endgroup$ Commented Apr 17, 2013 at 13:59
  • $\begingroup$ Thanks Jacob for the details! "the reason Graph does not automatically make such conversions is that expressions with Graph head can also be used in calculations". Now that is precisely what I wanted to do by Reap[Graph[..., VertexShapeFunction -> (Disk[Sow@#, .1] &)]. So if what you say was WRI's intention, it only works partially. $\endgroup$ Commented Apr 17, 2013 at 15:08
  • 1
    $\begingroup$ @IstvánZachar note that it may take very long for the PropertyValue to be calculated. In fact, this may take longer than making the Graphics expression from the graph. And calculating the same PropertyValue twice does not reduce the time that takes. I think that MMA has a way of calculating the VertexCoordinates without building the entire expression with head Graphics. Quite ingenious. $\endgroup$ Commented Apr 18, 2013 at 9:57
4
$\begingroup$

One interesting aspect of this is that the option appears to evaluate only on the first display of the graph. To emphasis the point I use a random colouring as well as a Print:

g = Graph[{1 -> 2, 2 -> 3, 3 -> 1}, 
   VertexShapeFunction -> ({Random[] // Hue, Disk[Print["VSF"]; #, 0.05]} &)];

enter image description here

Notice how the second display of g has not re-evaluated the function (there is no printing and the colours are the same). Those colours must be stored somewhere, but they're not in g (as evidenced by the unchanged ByteCount)

If we Set one of the VertexCoordinates using PropertyValue, the option will be re-evaluated. Note also that explicitly setting the value of a vertex coordinate causes the whole vertex coordinate list to be stored in g:

PropertyValue[{g, 1}, VertexCoordinates] = PropertyValue[{g, 1}, VertexCoordinates];

enter image description here

I'm not sure what to make of this. It seems reasonable that the VertexShapeFunction is only re-evaluated after a change to the VertexCoordinates, but Jacob showed that if the Graph is created with VertexCoordinates -> Automatic, the VertexCoordinates don't actually appear to be stored anywhere (e.g. they are recomputed each time PropertyValue is called). So it would seem that:

  • Vertex coordinates are either stored in the Graph expression, or nowhere.
  • The vertex shape information is stored somewhere, but not in the Graph expression.
  • Mathematica knows when the vertex coordinates haven't changed, even when it doesn't know what they actually are.

It would be nice to understand this better, in particular to know where the vertex shape information is being held.

Anyway, to literally answer the question "How can I force Graph to evaluate all its options without actually displaying the graph itself?", I did find the function GraphComputation`GraphConvertToGraphics which appears to do the job:

g2 = Graph[{1 -> 2, 2 -> 3, 3 -> 1}, 
   VertexShapeFunction -> ({Random[] // Hue, Disk[Print["VSF"]; #, 0.05]} &)];

GraphComputation`GraphConvertToGraphics[g2];

enter image description here

So the vertex shape function is evaluated without displaying the graph, and we can now display the graph without re-evaluating the shape function:

enter image description here

$\endgroup$
6
  • $\begingroup$ Nice answer! I encountered some of this behavior, i.e. I realized that the graph would not print messages again, but I missed your observations. However, it seems that this behavior is caused by Caching, rather than by something fundamental about Graph. It frustrates me that I still do not know how to turn off all Caching (setting all SystemOptions ending in Cache-> True to Cache-> False does not do it). However, after evaluating ClearSystemCache[], g will print messages again and will have differently colored vertices. $\endgroup$ Commented Apr 18, 2013 at 14:05
  • $\begingroup$ Also note that copy pasting a graph you make into a new input cell and evaluating it will yield a newly colored graph each time. The caching appears to be linked to the symbol G, rather than to the graph expression. $\endgroup$ Commented Apr 18, 2013 at 14:08
  • $\begingroup$ Simon, you remarked that it would be nice to understand where the graph information was stored. I hoped for a reply (possibly you can alleviate my trouble with disabling caching)! @IstvánZachar the comments above may also interest you... $\endgroup$ Commented Apr 22, 2013 at 18:39
  • $\begingroup$ @JacobAkkerboom, I think you must be right about the caching, but I find it troubling! Normally caching doesn't affect the result of evaluating an expression, it just speeds things up. I had a look through the ``GraphComputation``` context but I couldn't find any way to alter this behaviour. I can't help you with turning off all caching I'm afraid. $\endgroup$ Commented Apr 23, 2013 at 9:29
  • $\begingroup$ Then I will ask a question about this soon, and keep you updated! $\endgroup$ Commented Apr 23, 2013 at 10:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.