Skip to content

Commit 811551c

Browse files
authored
Multiset system: auto rule input counting (#656)
## Changes * `MultisetSubstitutionSystem` will automatically figure out the number of inputs in some classes of rules and adjust `"MinEventInputs"` and `"MaxEventInputs"` accordingly. * This was one of the bottlenecks in `MultisetSubstitutionSystem` performance. ## Comments * It cannot recognize all cases (in particular there is a condition in which `Length` is used). However, it does go inside conditions in simple cases, so something like `{a_, b_} /; a < b` is recognized. * This will be disabled in the new WL versions in case new pattern symbols appear. * @taliesinb, can you think of a better way to do this? ## Examples * This now works without a `"MaxEventInputs"` spec: ```wl In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] &@ SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}]@ GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} /; a < b :> {a + b}], {"MaxDestroyerEvents" -> 1}, None, EventOrderingFunctions[MultisetSubstitutionSystem], {}]@range[10] ``` <img width="399" alt="image" src="https://user-images.githubusercontent.com/1479325/122101996-91589b80-cdda-11eb-8ea4-5fa5f6d525e0.png">
1 parent aeb7a1d commit 811551c

8 files changed

Lines changed: 162 additions & 50 deletions

Documentation/Generators/EventOrderingFunctions.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ match any pair of numbers, many of which overlap:
77
In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] & @
88
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] @
99
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} /; a < b :> {a + b}],
10-
{"MaxGeneration" -> 1, "MaxEventInputs" -> 2},
10+
"MaxGeneration" -> 1,
1111
None,
1212
EventOrderingFunctions[MultisetSubstitutionSystem],
1313
{}] @
@@ -32,7 +32,7 @@ function at the moment):
3232
In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] & /@
3333
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] /@
3434
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} /; a < b :> {a + b}],
35-
{"MaxDestroyerEvents" -> 1},
35+
"MaxDestroyerEvents" -> 1,
3636
None,
3737
EventOrderingFunctions[MultisetSubstitutionSystem],
3838
{}] /@
@@ -50,7 +50,7 @@ However, this is not the case for all systems. For example, see what happens if
5050
In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] & /@
5151
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] /@
5252
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} /; a < b :> {a - b}],
53-
{"MaxDestroyerEvents" -> 1},
53+
"MaxDestroyerEvents" -> 1,
5454
None,
5555
EventOrderingFunctions[MultisetSubstitutionSystem],
5656
{}] /@

Documentation/Generators/EventSelectionParameters.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ another event is created instead:
4444
```wl
4545
In[] := #["ExpressionsEventsGraph"] & @ SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] @
4646
GenerateMultihistory[MultisetSubstitutionSystem[{a__} /; Total[{a}] == 5 :> {Total[{a}] - 1, Total[{a}] + 1}],
47-
{"MaxGeneration" -> 1},
47+
"MaxGeneration" -> 1,
4848
None,
4949
EventOrderingFunctions[MultisetSubstitutionSystem],
5050
"MaxEvents" -> 3] @ {1, 2, 3}
@@ -61,7 +61,7 @@ parameters) does not have a corresponding termination reason.
6161
```wl
6262
In[] := #[[2, "TerminationReason"]] & @
6363
GenerateMultihistory[MultisetSubstitutionSystem[{a__} /; Total[{a}] == 5 :> {Total[{a}] - 1, Total[{a}] + 1}],
64-
{"MaxGeneration" -> 1},
64+
"MaxGeneration" -> 1,
6565
None,
6666
EventOrderingFunctions[MultisetSubstitutionSystem],
6767
"MaxEvents" -> 3] @ {1, 2, 3}
@@ -92,7 +92,7 @@ multihistory (similar to `GenerateFullMultihistory`) subject to other selection
9292
In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] & @
9393
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] @
9494
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} :> {a + b}],
95-
{"MaxDestroyerEvents" -> Infinity, "MaxGeneration" -> 1, "MaxEventInputs" -> 2},
95+
{"MaxDestroyerEvents" -> Infinity, "MaxGeneration" -> 1},
9696
None,
9797
EventOrderingFunctions[MultisetSubstitutionSystem],
9898
{}] @ {1, 2, 3}
@@ -106,7 +106,7 @@ If set to a finite number, it will generate a partial multihistory:
106106
In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] & @
107107
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] @
108108
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} :> {a + b}],
109-
{"MaxDestroyerEvents" -> 5, "MaxEventInputs" -> 2},
109+
"MaxDestroyerEvents" -> 5,
110110
None,
111111
EventOrderingFunctions[MultisetSubstitutionSystem],
112112
{}] @ {1, 2, 3}

Documentation/Generators/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ a single history (no nondeterministic branching):
2222
In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] & @
2323
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] @
2424
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} /; a < b :> {a + b}],
25-
{"MaxDestroyerEvents" -> 1, "MaxEventInputs" -> 2},
25+
"MaxDestroyerEvents" -> 1,
2626
None,
2727
EventOrderingFunctions[MultisetSubstitutionSystem],
2828
{}] @ {1, 2, 3, 4}
@@ -36,7 +36,7 @@ or multiple histories. Note different events (orange) using the same tokens (lig
3636
In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] & @
3737
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] @
3838
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} /; a < b :> {a + b}],
39-
{"MaxDestroyerEvents" -> 2, "MaxEventInputs" -> 2},
39+
"MaxDestroyerEvents" -> 2,
4040
None,
4141
EventOrderingFunctions[MultisetSubstitutionSystem],
4242
{}] @ {1, 2, 3, 4}

Documentation/Generators/StoppingConditionParameters.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This is the most basic stopping condition. It stops the evaluation once the give
2424
In[] := #["ExpressionsEventsGraph"] & @
2525
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] @
2626
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} /; a < b :> {a + b}],
27-
{"MaxEventInputs" -> 2},
27+
{},
2828
None,
2929
EventOrderingFunctions[MultisetSubstitutionSystem],
3030
{"MaxEvents" -> 9}] @ {1, 2, 3, 4}

Documentation/Systems/MultisetSubstitutionSystem.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ For example, to make a system that adds pairs of numbers:
2121
In[] := #["ExpressionsEventsGraph", VertexLabels -> Placed[Automatic, After]] & @
2222
SetReplaceTypeConvert[{WolframModelEvolutionObject, 2}] @
2323
GenerateMultihistory[MultisetSubstitutionSystem[{a_, b_} /; a < b :> {a + b}],
24-
{"MaxDestroyerEvents" -> 1, "MaxEventInputs" -> 2},
24+
"MaxDestroyerEvents" -> 1,
2525
None,
2626
EventOrderingFunctions[MultisetSubstitutionSystem],
2727
{}] @ {1, 2, 3, 4}

Kernel/MultisetSubstitutionSystem.m

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
expressionDestroyerEventCounts, destroyerChoices, instantiationCounts, instantiations},
3434
Module[{rules, maxGeneration, maxDestroyerEvents, minEventInputs, maxEventInputs, maxEvents, init, terminationReason},
3535
rules = parseRules[rawRules];
36+
ruleInputCountRanges = inputCountRange /@ rules;
3637
{maxGeneration, maxDestroyerEvents, minEventInputs, maxEventInputs} = Values @ rawEventSelection;
38+
minEventInputs = Max[minEventInputs, Min[ruleInputCountRanges[[All, 1]]]];
39+
maxEventInputs = Min[maxEventInputs, Max[ruleInputCountRanges[[All, 2]]]];
3740
parseTokenDeduplication[rawTokenDeduplication]; (* Token deduplication is not implemented at the moment *)
3841
parseEventOrdering[rawEventOrdering]; (* Event ordering is not implemented at the moment *)
3942
{maxEvents} = Values @ rawStoppingCondition;
@@ -95,6 +98,7 @@
9598
(* Matching *)
9699

97100
findMatch[rules_, maxGeneration_, maxDestroyerEvents_, minEventInputs_, maxEventInputs_] := ModuleScope[
101+
If[minEventInputs === Infinity || minEventInputs > maxEventInputs, Throw["Complete", $$terminationReason]];
98102
eventInputsCountRange = {minEventInputs, Min[maxEventInputs, expressions["Length"]]};
99103
subsetCount = With[{n = expressions["Length"], a = eventInputsCountRange[[1]], b = eventInputsCountRange[[2]]},
100104
(* Sum[Binomial[n, k], {k, a, b}] *)
@@ -236,6 +240,85 @@ according to some (but not all) ordering functions. This new data structure will
236240
parseRules[rawRules_] := throw[Failure["invalidMultisetRules", <|"rules" -> rawRules|>]];
237241
parseRules[rawRules___] /; !CheckArguments[MultisetSubstitutionSystem[rawRules], 1] := throw[Failure[None, <||>]];
238242

243+
inputCountRange[(input_ :> _) | (input_ -> _)] := inputCountRange[input];
244+
inputCountRange[Verbatim[HoldPattern][input_List]] :=
245+
Total[Append[ReleaseHold @ Map[sequencePatternLengthRange, Hold[input], {2}], {0, 0}]];
246+
inputCountRange[input_List] := inputCountRange[HoldPattern[input]];
247+
inputCountRange[Verbatim[Alternatives][patterns__]] := MinMax[inputCountRange /@ {patterns}];
248+
inputCountRange[(Verbatim[Condition] | Verbatim[PatternTest])[input_, _]] := inputCountRange[input];
249+
inputCountRange[Verbatim[Pattern][_, obj_]] := inputCountRange[obj];
250+
inputCountRange[Verbatim[Except][_, p_]] := inputCountRange[p];
251+
inputCountRange[Verbatim[Verbatim][p_List]] := ConstantArray[Length[p], 2];
252+
inputCountRange[_] := {0, Infinity};
253+
254+
(* We need to hold the pattern from now on because sequencePatternLengthRange may be called from inside HoldPattern. *)
255+
256+
Attributes[sequencePatternLengthRange] := {HoldFirst};
257+
258+
(* Here we enumerate all possible WL pattern constructs from https://reference.wolfram.com/language/guide/Patterns.html.
259+
If a pattern construct has incorrect syntax, we return {Infinity, 0}, which means nothing can be matched. *)
260+
261+
sequencePatternLengthRange[Verbatim[Pattern][_, obj_]] := sequencePatternLengthRange[obj];
262+
sequencePatternLengthRange[Verbatim[Pattern][___]] := {Infinity, 0};
263+
264+
sequencePatternLengthRange[_Blank] := {1, 1};
265+
sequencePatternLengthRange[_BlankSequence] := {1, Infinity};
266+
sequencePatternLengthRange[_BlankNullSequence] := {0, Infinity};
267+
sequencePatternLengthRange[Verbatim[Alternatives][p__]] :=
268+
MinMax[ReleaseHold @ Map[sequencePatternLengthRange, Hold[{p}], {2}]];
269+
sequencePatternLengthRange[Verbatim[Alternatives][]] := {Infinity, 0}; (* this does not match to anything *)
270+
271+
zeroPreferenceProduct[0, _] := 0;
272+
zeroPreferenceProduct[_, 0] := 0;
273+
zeroPreferenceProduct[a_, b_] := a * b;
274+
zeroPreferenceProduct[{a_, b_}, {c_, d_}] := {zeroPreferenceProduct[a, c], zeroPreferenceProduct[b, d]};
275+
276+
sequencePatternLengthRange[Verbatim[Repeated][p_]] := {Min @ sequencePatternLengthRange[p], Infinity};
277+
sequencePatternLengthRange[Verbatim[RepeatedNull][_]] := {0, Infinity};
278+
sequencePatternLengthRange[Verbatim[Repeated][p_, max_]] :=
279+
zeroPreferenceProduct[{1, max}, sequencePatternLengthRange[p]];
280+
sequencePatternLengthRange[Verbatim[RepeatedNull][p_, max_]] :=
281+
{0, zeroPreferenceProduct[max, Max @ sequencePatternLengthRange[p]]};
282+
sequencePatternLengthRange[(Verbatim[Repeated] | Verbatim[RepeatedNull])[p_, {min_, max_}]] :=
283+
zeroPreferenceProduct[{min, max}, sequencePatternLengthRange[p]];
284+
sequencePatternLengthRange[(Verbatim[Repeated] | Verbatim[RepeatedNull])[p_, {n_}]] :=
285+
zeroPreferenceProduct[{n, n}, sequencePatternLengthRange[p]];
286+
sequencePatternLengthRange[Verbatim[Repeated][___]] := {Infinity, 0};
287+
sequencePatternLengthRange[Verbatim[RepeatedNull][___]] := {Infinity, 0};
288+
289+
(* Variable-length sequences are not allowed as a second argument to Except. *)
290+
sequencePatternLengthRange[_Except] := {1, 1};
291+
292+
sequencePatternLengthRange[Verbatim[Longest][p_]] := sequencePatternLengthRange[p];
293+
sequencePatternLengthRange[Verbatim[Longest][___]] := {Infinity, 0};
294+
sequencePatternLengthRange[Verbatim[Shortest][p_]] := sequencePatternLengthRange[p];
295+
sequencePatternLengthRange[Verbatim[Shortest][___]] := {Infinity, 0};
296+
297+
sequencePatternLengthRange[_OptionsPattern] := {0, Infinity};
298+
299+
sequencePatternLengthRange[(Verbatim[PatternSequence] | Verbatim[OrderlessPatternSequence])[ps___]] :=
300+
Total[Append[ReleaseHold @ Map[sequencePatternLengthRange, Hold[{ps}], {2}], {0, 0}]];
301+
302+
sequencePatternLengthRange[_Verbatim] := {1, 1};
303+
304+
sequencePatternLengthRange[Verbatim[HoldPattern][p_]] := sequencePatternLengthRange[p];
305+
sequencePatternLengthRange[Verbatim[HoldPattern][___]] := {Infinity, 0};
306+
307+
sequencePatternLengthRange[_KeyValuePattern] := {1, 1};
308+
309+
sequencePatternLengthRange[Verbatim[Condition][p_, _]] := sequencePatternLengthRange[p];
310+
sequencePatternLengthRange[Verbatim[Condition][___]] := {Infinity, 0};
311+
312+
sequencePatternLengthRange[Verbatim[PatternTest][p_, _]] := sequencePatternLengthRange[p];
313+
sequencePatternLengthRange[Verbatim[PatternTest][___]] := {Infinity, 0};
314+
315+
sequencePatternLengthRange[Verbatim[Optional][p_, _]] := {0, Max @ sequencePatternLengthRange[p]};
316+
sequencePatternLengthRange[Verbatim[Optional][___]] := {Infinity, 0};
317+
318+
(* Since we have enumerated all pattern constructs above, this case does not correspond to a pattern.
319+
However, the completeness of checks above needs to be checked for every new WL version. *)
320+
sequencePatternLengthRange[_] := If[$VersionNumber <= 12.3, {1, 1}, {0, Infinity}];
321+
239322
parseTokenDeduplication[None] := None;
240323
declareMessage[General::multisetTokenDeduplicationNotImplemented,
241324
"Token deduplication is not implemented for Multiset Substitution System."];

0 commit comments

Comments
 (0)