Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 25 additions & 13 deletions src/SIL.Machine.Morphology.HermitCrab/AnalysisAffixTemplateRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,34 @@ public AnalysisAffixTemplateRule(Morpher morpher, AffixTemplate template)

public IEnumerable<Word> Apply(Word input)
{
if (!_morpher.RuleSelector(_template))
if (!CanApply(input))
return Enumerable.Empty<Word>();

Word inWord = input.Clone();
inWord.Freeze();

var output = new HashSet<Word>(FreezableEqualityComparer<Word>.Default);
if (_morpher.MaxDegreeOfParallelism == 1)
ApplySlots(inWord, _rules.Count - 1, output);
else
ParallelApplySlots(inWord, output);

return output;
}

internal AffixTemplate Template => _template;

internal IReadOnlyList<IRule<Word, ShapeNode>> SlotRules => _rules;

internal bool CanApply(Word input)
{
if (!_morpher.RuleSelector(_template))
return false;

// A check only: adding the template's required features here would make SyntacticFeatureStruct
// depend on whether a template was un-applied, which Word.ValueEquals assumes cannot happen.
if (!input.SyntacticFeatureStruct.IsUnifiable(_template.RequiredSyntacticFeatureStruct))
return Enumerable.Empty<Word>();
return false;

if (_morpher.TraceManager.IsTracing)
_morpher.TraceManager.BeginUnapplyTemplate(_template, input);
Expand All @@ -58,19 +79,10 @@ public IEnumerable<Word> Apply(Word input)
FailureReason.NonPartialRuleProhibitedAfterFinalTemplate
);
}
return Enumerable.Empty<Word>();
return false;
}

Word inWord = input.Clone();
inWord.Freeze();

var output = new HashSet<Word>(FreezableEqualityComparer<Word>.Default);
if (_morpher.MaxDegreeOfParallelism == 1)
ApplySlots(inWord, _rules.Count - 1, output);
else
ParallelApplySlots(inWord, output);

return output;
return true;
}

private void ApplySlots(Word inWord, int index, HashSet<Word> output)
Expand Down
154 changes: 154 additions & 0 deletions src/SIL.Machine.Morphology.HermitCrab/AnalysisAffixTemplatesRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System.Collections.Generic;
using System.Linq;
using SIL.Machine.Annotations;
using SIL.Machine.Rules;
using SIL.ObjectModel;

namespace SIL.Machine.Morphology.HermitCrab
{
internal class AnalysisAffixTemplatesRule : RuleBatch<Word, ShapeNode>
{
private readonly Morpher _morpher;
private readonly AnalysisAffixTemplateRule[] _templates;
private readonly bool[][] _slotOptionality;
private readonly SlotNode _root = new SlotNode();
private readonly bool _hasSharedSlots;

public AnalysisAffixTemplatesRule(Morpher morpher, IEnumerable<IRule<Word, ShapeNode>> rules)
: base(rules, false, FreezableEqualityComparer<Word>.Default)
{
_morpher = morpher;
_templates = Rules.OfType<AnalysisAffixTemplateRule>().ToArray();
if (_templates.Length != Rules.Count)
return;

_slotOptionality = _templates.Select(t => t.Template.Slots.Select(s => s.Optional).ToArray()).ToArray();
for (int t = 0; t < _templates.Length; t++)
{
AnalysisAffixTemplateRule template = _templates[t];
SlotNode node = _root;
for (int s = template.Template.Slots.Count - 1; s >= 0; s--)
{
AffixTemplateSlot slot = template.Template.Slots[s];
SlotNode child = node.Children.FirstOrDefault(n => n.Matches(slot));
if (child == null)
{
child = new SlotNode
{
Slot = slot,
Optional = slot.Optional,
Rule = template.SlotRules[s],
};
node.Children.Add(child);
}
else
{
_hasSharedSlots = true;
}
child.Templates.Add(t);
node = child;
}
node.Terminals.Add(t);
}
}

public override IEnumerable<Word> Apply(Word input)
{
// Use the original walk when no tail is shared, when tracing (per-template Begin/End events), for
// parallel slots, under an alternative limit, or if slot optionality changed after the trie was built.
if (
!_hasSharedSlots
|| _morpher.TraceManager.IsTracing
|| _morpher.MaxDegreeOfParallelism != 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: F4. A default Morpher never takes the shared walk: maxDegreeOfParallelism defaults to 0 (Morpher.cs:29), so this guard sends every call to base.Apply and only callers who pass degree 1 get fewer batches. The PR body defers parallel sharing but presents the speedup as general; say in the description that it needs degree 1, or say why the default should not be covered.

|| MaxAlternatives > 0
|| !SlotsUnchanged()
)
return base.Apply(input);

var outputs = new HashSet<Word>[_templates.Length];
for (int t = 0; t < _templates.Length; t++)
{
if (_templates[t].CanApply(input))
outputs[t] = new HashSet<Word>(Comparer);
}

if (outputs.Any(o => o != null))
{
Word inWord = input.Clone();
inWord.Freeze();
Walk(_root, inWord, outputs);
}

// Template order determines which equal word survives and the order seen by analysis folding.
var output = new HashSet<Word>(Comparer);
foreach (HashSet<Word> templateOutput in outputs)
{
if (templateOutput != null)
output.UnionWith(templateOutput);
}
return output;
}

private bool SlotsUnchanged()
{
for (int t = 0; t < _templates.Length; t++)
{
IList<AffixTemplateSlot> slots = _templates[t].Template.Slots;
bool[] optionality = _slotOptionality[t];
if (slots.Count != optionality.Length)
return false;
for (int s = 0; s < slots.Count; s++)
{
if (slots[s].Optional != optionality[s])
return false;
}
}
return true;
}

private static void Walk(SlotNode node, Word input, HashSet<Word>[] outputs)
{
foreach (int t in node.Terminals)
outputs[t]?.Add(input);

foreach (SlotNode child in node.Children)
{
if (!AnyAdmitted(child.Templates, outputs))
continue;

foreach (Word word in child.Rule.Apply(input))
Walk(child, word, outputs);
if (child.Optional)
Walk(child, input, outputs);
}
}

private static bool AnyAdmitted(List<int> templates, HashSet<Word>[] outputs)
{
for (int i = 0; i < templates.Count; i++)
{
if (outputs[templates[i]] != null)
return true;
}
return false;
}

private sealed class SlotNode
{
public AffixTemplateSlot Slot { get; set; }
public bool Optional { get; set; }
public IRule<Word, ShapeNode> Rule { get; set; }
public List<SlotNode> Children { get; } = new List<SlotNode>();
public List<int> Templates { get; } = new List<int>();
public List<int> Terminals { get; } = new List<int>();

public bool Matches(AffixTemplateSlot slot)
{
if (Optional != slot.Optional || Slot.Rules.Count != slot.Rules.Count)
return false;

return Slot.Rules.Zip(slot.Rules, (a, b) => ReferenceEquals(a, b)).All(equal => equal);
}
}
}
}
7 changes: 3 additions & 4 deletions src/SIL.Machine.Morphology.HermitCrab/AnalysisStratumRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ public AnalysisStratumRule(Morpher morpher, Stratum stratum)
_prulesRule = new LinearRuleCascade<Word, ShapeNode>(
stratum.PhonologicalRules.Select(prule => CompilePhonologicalRule(prule, morpher)).Reverse()
);
_templatesRule = new RuleBatch<Word, ShapeNode>(
stratum.AffixTemplates.Select(template => CompileAffixTemplate(template, morpher)),
false,
FreezableEqualityComparer<Word>.Default
_templatesRule = new AnalysisAffixTemplatesRule(
morpher,
stratum.AffixTemplates.Select(template => CompileAffixTemplate(template, morpher))
);
_mrulesRule = null;
IEnumerable<IRule<Word, ShapeNode>> mrules = stratum
Expand Down
Loading
Loading