-
Notifications
You must be signed in to change notification settings - Fork 17
/
Common.m
267 lines (201 loc) · 9.89 KB
/
Common.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
(* Mathematica Package *)
(* Created by Mathematica Plugin for IntelliJ IDEA, see http://wlplugin.halirutan.de/ *)
(* :Author: szhorvat *)
(* :Date: 2016-06-12 *)
(* :Copyright: (c) 2016-2020 Szabolcs Horvát *)
Package["IGraphM`"]
(********************************************)
(***** Common (package scope) functions *****)
(********************************************)
PackageScope["igGraphQ"]
igGraphQ::usage = "igGraphQ[g] checks if g is an igraph-compatible graph.";
igGraphQ = GraphQ[#] && If[MixedGraphQ[#], Message[IGraphM::mixed]; False, True] &;
PackageScope["igDirectedQ"]
igDirectedQ::usage = "igDirectedQ[g] checks if g is a directed graph. Empty graphs are considered undirected.";
igDirectedQ[graph_] := DirectedGraphQ[graph] && Not@EmptyGraphQ[graph]
PackageScope["applyGraphOpt"]
applyGraphOpt::usage = "applyGraphOpt[options][graph] applies the given options to graph.";
applyGraphOpt[opt___][graph_] := Graph[graph, Sequence@@FilterRules[{opt}, Options[Graph]]]
PackageScope["applyGraphOpt3D"]
applyGraphOpt3D::usage = "applyGraphOpt3D[options][graph] applies the given options to graph using Graph3D.";
applyGraphOpt3D[opt___][graph_] := Graph3D[graph, Sequence@@FilterRules[{opt}, Options[Graph3D]]]
PackageScope["zeroDiagonal"]
zeroDiagonal::usage = "zeroDiagonal[mat] replaces the diagonal of a matrix with zeros.";
zeroDiagonal[mat_] := UpperTriangularize[mat, 1] + LowerTriangularize[mat, -1]
PackageScope["adjacencyGraph"]
(* Fast version of directed or undirected adjacency matrix -> graph conversion.
The matrix is not checked to be symmetric in the undirected case. *)
adjacencyGraph::usage = "adjacencyGraph[vertices, sparseAM, directed]";
adjacencyGraph[vs_, sa_, True] := Graph[vs, {sa, Null}]
adjacencyGraph[vs_, sa_, False] := Graph[vs, {Null, sa}]
PackageScope["removeSelfLoops"]
removeSelfLoops::usage = "removeSelfLoops[graph] removes any self loops from graph.";
removeSelfLoops[g_?LoopFreeGraphQ] := g (* catches empty case *)
removeSelfLoops[g_] := adjacencyGraph[VertexList[g], zeroDiagonal@AdjacencyMatrix[g], DirectedGraphQ[g]]
PackageScope["removeMultiEdges"]
removeMultiEdges::usage = "removeMultiEdges[graph] removes any multi-edges from graph.";
removeMultiEdges[g_?MultigraphQ] := adjacencyGraph[VertexList[g], Unitize@AdjacencyMatrix[g], DirectedGraphQ[g]]
removeMultiEdges[g_] := g
PackageScope["transformGraphOptions"]
(* Use 'dummy' instead of 'usage' to prevent IntelliJ from making the symbol known across files *)
$graphLink::dummy = "$graphLink is a loopback link used to convert atomic graphs to a compound form.";
transformGraphOptions::usage = "transformGraphOptions[fun][graph] applies fun to the list of options stored in graph.";
transformGraphOptions[fun_][g_?GraphQ] :=
(
If[Not@MemberQ[Links[], $graphLink],
$graphLink = LinkCreate[LinkMode -> Loopback];
];
With[
{
expr = AbortProtect[
LinkWrite[$graphLink, g];
LinkRead[$graphLink, Hold]
]
},
Replace[expr, Hold@Graph[v_, e_, opt : _ : {}, rest___] :> Graph[v, e, fun[opt], rest]]
]
)
PackageScope["ruleQ"]
ruleQ::usage = "ruleQ[expr] gives True if expr is a Rule or RuleDelayed, False otherwise.";
ruleQ[_Rule | _RuleDelayed] = True;
ruleQ[_] = False;
PackageScope["partitionRagged"]
partitionRagged::usage = "partitionRagged[list, lengths] partitions list into parts of the given lengths.";
If[$VersionNumber >= 11.2,
partitionRagged = TakeList,
partitionRagged[v_List, l_?VectorQ] := MapThread[Take[v, {#1, #2}] &, With[{a = Accumulate[l]}, {a - l + 1, a}]]
]
(* Retrieving edge or vertex weights this way is much faster than using PropertyValue *)
PackageScope["igEdgeWeights"]
igEdgeWeights::usage = "igEdgeWeights[graph] returns the edge weight vector of graph.";
igEdgeWeights = GraphComputation`WeightValues;
PackageScope["igVertexWeights"]
igVertexWeights ::usage = "igVertexWeights[graph] returns the vertex weight vector of graph.";
igVertexWeights = GraphComputation`WeightVector;
(***** Tools for error handling *****)
(* TODO: we need a better error handling framework.
* The message tag should be encapsulated in the thrown object.
* catch[] should have a version that takes a head and reports the message under that head (head::tag)
*)
(* Use 'dummy' instead of 'usage' to prevent IntelliJ from making the symbol known across files *)
igTag::dummy = "igTag is a private tag for Throw/Catch within IGraphM.";
PackageScope["throw"]
throw::usage = "throw[val]";
throw[val_] := Throw[val, igTag]
PackageScope["catch"]
catch::usage = "catch[expr]";
SetAttributes[catch, HoldFirst]
catch[expr_] := Catch[expr, igTag]
PackageScope["check"]
check::usage = "check[val]";
check[val_LibraryFunctionError] := throw[$Failed] (* this was originally throw[val] *)
check[$Failed] := throw[$Failed]
check[HoldPattern[LibraryFunction[___][___]]] := throw[$Failed]
check[val_] := val
PackageScope["sck"]
sck::usage = "sck[val]";
sck[HoldPattern[LibraryFunction[___][___]]] := $Failed
sck[val_LibraryFunctionError] := $Failed (* this was originally val *)
sck[val_] := val
(***** Functions for argument checking *****)
(* Note: VectorQ, MatrixQ, etc. are optimized with certain second arguments. For example,
* VectorQ[#, NumericQ]& will immediately return True for a packed array without evaluating
* NumericQ for each array argument separately. Below it is noted which of these second arguments
* the check is fast for. *)
PackageScope["nonNegIntVecQ"]
nonNegIntVecQ::usage = "nonNegIntVecQ[vec]";
nonNegIntVecQ = VectorQ[#, Internal`NonNegativeMachineIntegerQ]&; (* verified fast M11.1+ *)
PackageScope["posIntVecQ"]
posIntVecQ::usage = "posIntVecQ[vec]";
posIntVecQ = VectorQ[#, Internal`PositiveMachineIntegerQ]&; (* verified fast M11.1+ *)
PackageScope["intVecQ"]
intVecQ::usage = "intVecQ[]";
intVecQ = VectorQ[#, Developer`MachineIntegerQ]&; (* verified fast *)
PackageScope["intMatQ"]
intMatQ::usage = "intMatQ[mat]";
intMatQ = MatrixQ[#, Developer`MachineIntegerQ]&; (* verified fast *)
PackageScope["positiveNumericQ"]
positiveNumericQ::usage = "positiveNumericQ[num]";
positiveNumericQ = NumericQ[#] && TrueQ@Positive[#]&;
PackageScope["nonNegativeNumericQ"]
nonNegativeNumericQ::usage = "nonNegativeNumericQ[num]";
nonNegativeNumericQ = NumericQ[#] && TrueQ@NonNegative[#]&;
PackageScope["positiveVecQ"]
positiveVecQ::usage = "positiveVecQ[vec]";
positiveVecQ = VectorQ[#, Positive]&; (* NOT fast *)
PackageScope["nonNegVecQ"]
nonNegVecQ::usage = "nonNegVecQ[vec]";
nonNegVecQ = VectorQ[#, NonNegative]&; (* NOT fast *)
PackageScope["emptyArrayQ"]
emptyArrayQ::usage = "emptyArrayQ[arr]";
emptyArrayQ[arr_] := MemberQ[Dimensions[arr], 0]
PackageScope["positiveOrInfQ"]
positiveOrInfQ::usage = "positiveOrInfQ[val]";
positiveOrInfQ = TrueQ@Positive[#]&;
(* Replace Infinity by 0 *)
PackageScope["infToZero"]
infToZero::usage = "infToZero[arg] returns 0 if arg === Infinity.";
infToZero[arg_] := Replace[arg, Infinity -> 0]
(* Replace Infinity by -1 *)
PackageScope["infToNeg"]
infToNeg::usage = "infToNeg[arg] returns 0 if arg === Infinity.";
infToNeg[arg_] := Replace[arg, Infinity -> -1]
(* Temporarily disable floating point exception checking in LibraryLink *)
PackageScope["expectInfNaN"]
expectInfNaN::usage = "expectInfNaN[expr] evaluates expr with settings that allow LibraryLink functions to return Infinity or Indeterminate.";
If[$VersionNumber >= 12.2,
SetAttributes[expectInfNaN, HoldAll];
expectInfNaN[expr_] :=
With[{llo = SystemOptions["LibraryLinkOptions" -> "TestFloatingPointExceptions"]},
Internal`WithLocalSettings[
SetSystemOptions["LibraryLinkOptions" -> "TestFloatingPointExceptions" -> False],
expr,
SetSystemOptions[llo]
]
]
,
expectInfNaN[expr_] := expr
]
(* Unpack array containing infinities or indeterminates *)
(* TODO: Test on all platforms that unpacking such arrays produces usable Infinity and Indeterminate *)
PackageScope["fixInfNaN"]
fixInfNaN::usage = "fixInfNaN[array] unpacks array if it contains Inf or NaN.";
fixInfNaN[arr_?Developer`PackedArrayQ] := If[igraphGlobal@"infOrNanQ"[arr], Developer`FromPackedArray[arr], arr];
fixInfNaN[arr_] := arr
(***** Workarounds for old versions missing some functions *****)
PackageScope["circularEmbedding"]
circularEmbedding::usage = "circularEmbedding is a GraphLayout.";
If[$VersionNumber < 12.0,
circularEmbedding = "CircularEmbedding",
circularEmbedding = {"CircularEmbedding", "OptimalOrder" -> False}
]
PackageScope["canonicalEdgeBlock"]
canonicalEdgeBlock::usage = "canonicalEdgeBlock[expr] evaluates expression while making sure that all UndirectedEdge expressions inside are ordered canonically.";
SetAttributes[canonicalEdgeBlock, HoldAll]
(* In M12.1 and later, UndirectedEdge can have 3 arguments, so we cannot canonicalize simply with Orderless. *)
(* TODO The workaround /; Not@OrderedQ[{a, b}] is 10x slower than Orderless! *)
If[$VersionNumber >= 12.1,
canonicalEdgeBlock[expr_] :=
Internal`InheritedBlock[{UndirectedEdge},
Unprotect[UndirectedEdge];
UndirectedEdge[a_, b_, rest___] /; Not@OrderedQ[{a, b}] := UndirectedEdge[b, a, rest];
expr
]
,
canonicalEdgeBlock[expr_] :=
Internal`InheritedBlock[{UndirectedEdge},
SetAttributes[UndirectedEdge, Orderless];
expr
]
]
(* In M12.1 and later, custom graph properties are stored in the AnnotationRules option, not in the Properties option.
PropOptName is defined to be the correct property-holding option name in each version. *)
PackageScope["PropOptName"]
PropOptName::usage = "PropOptName is AnnotationRules in M >= 12.1 and Properties in M < 12.0.";
If[$VersionNumber >= 12.1,
PropOptName = AnnotationRules,
PropOptName = Properties
]
PackageScope["encodeNeighborMode"]
encodeNeighborMode::usage = "encodeNeighborMode[mode] translates Out, In, All to 1, 2, 3. To be used with the igNeighborMode() C++ function.";
encodeNeighborMode[mode_] := Lookup[<|"Out" -> 1, "In" ->2, "All" -> 3 (*, All -> 3 *)|>, mode, -1]