How to extract something from DelaunayMesh and similar family of functions
I'm going to take this as a general question, referring to all atomic objects, not just DelaunayMesh
.
By design, atomic objects like DelaunayMesh
, SparseArray
, Graph
, etc. or even Association
and Rational
are not meant to be accessed directly as a Mathematica expression. There are various reasons why an object was made atomic, typically related to performance (think of the change from v8 to v9 when Image
became atomic).
These objects usually have some sort of interface to allow extracting information from them. This is what we should use, as this is the only supported (i.e. guaranteed to be robust and compatible) way. For your example, you can extract the desired information as MeshCells[r, 3]
. For a sparse array, we can extract the components of the objects with sa["NonzeroPositions"]
, sa["NonzeroValues"]
, etc. For a Graph
object, we can use VertexList
and EdgeList
.
Usually, the standard interface works well. But unfortunately, occasionally it happens that a use case was not anticipated by Wolfram. This happened recently to me when I had a need to extract an edge list of the graph in terms of indices, with good performance. I know the information is there, and I know that it can be extracted quickly, as e.g. AdjacencyMatrix
seems to do it, but there's no documented way for me to get access to the raw information. These really made me want to poke around the internal structure of Graph
... but doing such things would be a very bad idea if we need any sort of robustness, especially inside a production package.
However, to do it at all, we need to get access it the expression's "full form". You noticed that virtually all atomic expressions have a full form, even though it is mostly inaccessible. Why is this so, if they are atomic? I believe that the answer is that often there is a need to serialize Mathematica expressions, either to write them into an .m
file, save them in a notebook (when possible), or to send them through a MathLink connection. This is done by first representing them as a compound expression, which might not map directly to the internal structure of the atomic object, but should represent it fully.
How well this "full form" integrates into the rest of the language varies from case to case. E.g. SparseArray
and Rational
can be accessed using pattern matching:
sa = SparseArray[{5, 7} -> 1];
Replace[sa, HoldPattern@SparseArray[guts___] :> {guts}]
(* {Automatic, {5, 7}, 0, {1, {{0, 0, 0, 0, 0, 1}, {{7}}}, {1}}} *)
Graph
cannot:
g = RandomGraph[{5,10}];
MatchQ[g, HoldPattern@Graph[___]]
We know though that it does have a full form ...
In[]:= InputForm[g]
Out[]//InputForm=
Graph[{1, 2, 3, 4, 5}, {Null, SparseArray[Automatic, {5, 5}, 0,
{1, {{0, 4, 8, 12, 16, 20}, {{2}, {3}, {4}, {5}, {1}, {3}, {4}, {5}, {1}, {2}, {4},
{5}, {1}, {2}, {3}, {5}, {1}, {2}, {3}, {4}}}, Pattern}]}]
I think that the only way to get to it is to first convert the atomic object to another representation. We could convert it to a string and back, e.g.
ToExpression[ToString[g, InputForm], InputForm, Hold]
Hold[Graph[{1, 2, 3, 4, 5}, {Null,
SparseArray[Automatic, {5, 5},
0, {1, {{0, 4, 8, 12, 16,
20}, {{2}, {3}, {4}, {5}, {1}, {3}, {4}, {5}, {1}, {2}, {4}, \
{5}, {1}, {2}, {3}, {5}, {1}, {2}, {3}, {4}}}, Pattern}]}]]
What's inside the Hold
is not an atom, it's just a compound expression with head Graph
that will immediately evaluate to an atomic graph once we remove the Hold
.
We could also use Compress
:
Uncompress[Compress[g], Hold]
Or possibly export to WDX and import back (haven't tested).
If we wanted better performance, we might send the expression through a MathLink connection and wrap it in Hold
in C code ...
These are good techniques for doing some spelunking on atoms. But doing this should really really be avoided in favour of using the standard, type-specific way of extracting information. Remember that this full form used for serialization is not meant to be used directly, it's only for serialization. It may change between versions, and it may not work the way you thought it did. Graph
for example can have several different internal representations.
I think this is the simplest fast way to convert an atomic expression to an equivalent compound form, to be able to inspect and manipulate its "apparent" full form:
g = RandomGraph[{5,8}]; (* this is our atomic expression *)
ml = LinkCreate[LinkMode -> Loopback];
LinkWrite[ml, With[{e = g}, Hold[e]]]
LinkRead[ml]
LinkClose[ml]
(* Hold[Graph[{1, 2, 3, 4, 5}, {Null,
SparseArray[Automatic, {5, 5},
0, {1, {{0, 4, 6, 9, 12,
16}, {{2}, {3}, {4}, {5}, {1}, {5}, {1}, {4}, {5}, {1}, {3}, {5}, {1}, {2}, {3}, {4}}}, Pattern}]}]] *)
Motivation
Recently, I wanted to extract parts of an atomic expression, and my first thought was to use a ToExpression
/ToString
roundtrip where I inactivate the atomic head. I then decided that it would be worthwhile to have a function to convert an atomic object into an inactive version where the head is wrapped in Inactive
. Then, I thought such a function would be worthwhile posting here, and a search led me to this question, where I learned of @Szabolcs' clever idea of using LinkWrite
/LinkRead
. I really like this approach better than using ToExpression
/ToString
because I expect it to be quicker and more robust. For example, using ToString[expr, InputForm]
is not robust because it is possible to write Format
statements that will break the ToExpression
/ToString
round trip. So, one would have to use ToString[FullForm[expr]]
, but this is much slower.
Design
My first thought was to overload Inactivate
so that it works with atomic objects. I decided against it for 2 reasons. One, people may not feel comfortable changing System`
functions. And two, the design of Inactivate
accepts patterns in the second argument, and I really wanted to restrict the second argument to a symbol, or to a list of symbols.
This meant that I needed to come up with a name. One possibility is AtomInactivate
, but that seems too cumbersome. So, I decided on Nucleus
(I also considered Ionize
). The idea is that the nucleus is the most important part of an atom. If you think this is too cute, you may change the name to something else.
Nucleus
Here is the definition of Nucleus
:
Nucleus[input_, head_:Automatic] := With[
{
atoms = Replace[head,
{
Automatic :> If[AtomQ[input], {Head[input]}, Message[Nucleus::atom]; $Failed],
h_Symbol :> {h},
h:{__Symbol} :> h,
_ :> (Message[Nucleus::syms,head,2];$Failed)
}
]
},
(
If[!MemberQ[Links[], $AtomLink] || LinkReadyQ[$AtomLink],
Quiet @ LinkClose[$AtomLink];
$AtomLink = LinkCreate[LinkMode -> Loopback]
];
LinkWrite[$AtomLink, input];
inactiveBlock[atoms, LinkRead[$AtomLink]]
) /; atoms =!= $Failed
]
SetAttributes[inactiveBlock, HoldAll]
inactiveBlock[h_List, body_] := Block @@ Join[
Apply[Set, Hold @ Evaluate @ Thread[{h,Inactive/@h}], {2}],
Hold[body]
]
Nucleus::syms = "Argument `1` at position `2` is expected to be a symbol or a list of symbols";
Nucleus::atom = "Unable to determine atomic symbol";
The basic idea is to figure out what heads need to be inactivated, call LinkWrite
on the input, followed by calling LinkRead
after blocking the heads to inactive versions of themselves. If only a single argument is given, and the input is atomic, then the head of the input will be inactivated. Here is an example:
g = RandomGraph[{5,10}];
Nucleus[g]
First @ %
Inactive[Graph][{1, 2, 3, 4, 5}, {Null, SparseArray[ Automatic, {5, 5}, 0, { 1, {{0, 4, 8, 12, 16, 20}, {{2}, {3}, {4}, {5}, {1}, {3}, {4}, {5}, {1}, { 2}, {4}, {5}, {1}, {2}, {3}, {5}, {1}, {2}, {3}, {4}}}, Pattern}]}]
{1, 2, 3, 4, 5}
Notice that Nucleus
was able to automatically determine that the head to be inactivated is Graph
. And another example with the same graph:
Nucleus[g, SparseArray]
Graph[{1, 2, 3, 4, 5}, {Null, Inactive[SparseArray][Automatic, {5, 5}, 0, {1, {{0, 4, 8, 12, 16, 20}, {{2}, {3}, {4}, {5}, {1}, {3}, {4}, {5}, {1}, {2}, {4}, {5}, {1}, {2}, {3}, {5}, {1}, {2}, {3}, {4}}}, Pattern}]}]
This time Graph
is not inactivated, but it is not atomic because an "invalid" SparseArray
argument is used (since the SparseArray
head is wrapped inside of Inactive
).
Note that the object need not be atomic, as Nucleus
will inactivate the requisite atomic parts. For example:
Nucleus[{Graph[{1->2,2->3}], Graph[{1->3,2->3}]}, Graph]
{Inactive[Graph][{1, 2, 3}, {{{1, 2}, {2, 3}}, Null}], Inactive[Graph][{1, 3, 2}, {{{1, 2}, {3, 2}}, Null}]}
Once part extraction or transformations of the inactive object are performed, one simply uses Activate
to recreate the atomic object. For example, suppose you wanted to rename the vertices, and then have them displayed:
Append[Nucleus[g] /. {1,2,3,4,5}->{a,b,c,d,e}, VertexLabels->"Name"]
Activate[%]
Inactive[Graph][{a, b, c, d, e}, {Null, SparseArray[ Automatic, {5, 5}, 0, { 1, {{0, 4, 8, 12, 16, 20}, {{2}, {3}, {4}, {5}, {1}, {3}, {4}, {5}, {1}, { 2}, {4}, {5}, {1}, {2}, {3}, {5}, {1}, {2}, {3}, {4}}}, Pattern}]}, VertexLabels -> "Name"]
Of course, for this example, one can simply use VertexReplace
instead:
Graph[VertexReplace[g, Thread[{1,2,3,4,5}->{a,b,c,d,e}]], VertexLabels->"Name"]
Nucleus
is particularly useful when needed accessor functions are not available.