Repository navigation
Add bounded batch parsing to HermitCrab #582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,7 +32,11 @@ public class Morpher : IMorphologicalAnalyzer, IMorphologicalGenerator | |
| private readonly IList<RootAllomorph> _lexicalPatterns = new List<RootAllomorph>(); | ||
|
|
||
| public Morpher(ITraceManager traceManager, Language lang, int maxDegreeOfParallelism = 0) | ||
| : this(traceManager, lang, maxDegreeOfParallelism, 0) { } | ||
|
|
||
| public Morpher(ITraceManager traceManager, Language lang, int maxDegreeOfParallelism, int maxAlternatives) | ||
| { | ||
| MaxAlternatives = maxAlternatives; | ||
| _lang = lang; | ||
| _traceManager = traceManager; | ||
| // Must be set before CompileAnalysisRule: AnalysisStratumRule picks a sequential vs. parallel | ||
|
|
@@ -60,7 +64,6 @@ public Morpher(ITraceManager traceManager, Language lang, int maxDegreeOfParalle | |
| _analysisRule = lang.CompileAnalysisRule(this); | ||
| _synthesisRule = lang.CompileSynthesisRule(this); | ||
| MaxStemCount = 2; | ||
| MaxAlternatives = 0; | ||
| MergeEquivalentAnalyses = true; | ||
| PruneDisagreeingCopies = true; | ||
| LexEntrySelector = entry => true; | ||
|
|
@@ -93,8 +96,9 @@ public ITraceManager TraceManager | |
| /// <summary> | ||
| /// MaxAlternatives limits the number of alternatives considered. | ||
| /// If the limit is exceeded, then MaxAlternativesExceededException is thrown. | ||
| /// Set this value with the constructor. | ||
| /// </summary> | ||
| public int MaxAlternatives { get; set; } | ||
| public int MaxAlternatives { get; } | ||
|
|
||
| /// <summary> | ||
| /// Merge analyses with equivalent state for every analysis-side rule. | ||
|
|
@@ -128,6 +132,7 @@ public ITraceManager TraceManager | |
| /// (a value of 1 compiles the sequential cascade for unordered strata). It must remain a performance | ||
| /// knob only: nothing that changes which analyses a parse returns, or whether | ||
| /// <see cref="MaxAlternatives"/> is exceeded, may depend on it. | ||
| /// <see cref="ParseWords"/> throws unless this value is 1. | ||
| /// </summary> | ||
| public int MaxDegreeOfParallelism { get; } | ||
|
|
||
|
|
@@ -225,6 +230,88 @@ public IEnumerable<Word> ParseWord(string word, out object trace, bool guessRoot | |
| return syntheses; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Eagerly parses surface forms in input order, capturing each parse's exception and trace in its result. | ||
| /// Requires a Morpher constructed with maxDegreeOfParallelism: 1. Tracing runs the batch serially. | ||
| /// Keep the Language, settings, and tracing fixed until completion; selectors and custom rules must | ||
| /// support concurrent calls when maxWordConcurrency is greater than 1 and tracing is disabled. | ||
| /// Do not overlap traced calls sharing a trace manager. Failed parses have empty analyses. | ||
| /// Progress receives the input index and surface form, with a null result at the start and the | ||
| /// completed result at the finish. It runs on parsing threads (the calling thread for serial batches), | ||
| /// possibly concurrently, and must be fast and thread-safe. Callback exceptions are collected and | ||
| /// propagated as an AggregateException after all words and both notifications have been processed. | ||
| /// Batch argument validation and input enumeration errors propagate to the caller. | ||
| /// </summary> | ||
| public IReadOnlyList<WordParseResult> ParseWords( | ||
| IEnumerable<string> words, | ||
| int maxWordConcurrency = 4, | ||
| bool guessRoot = false, | ||
| Action<int, string, WordParseResult> progress = null | ||
| ) | ||
| { | ||
| if (words == null) | ||
| throw new ArgumentNullException(nameof(words)); | ||
| if (maxWordConcurrency < 1) | ||
|
claude[bot] marked this conversation as resolved.
Resolved
|
||
| throw new ArgumentOutOfRangeException(nameof(maxWordConcurrency)); | ||
| if (MaxDegreeOfParallelism != 1) | ||
|
claude[bot] marked this conversation as resolved.
Resolved
|
||
| { | ||
| throw new InvalidOperationException( | ||
| "ParseWords requires a Morpher constructed with maxDegreeOfParallelism: 1." | ||
| ); | ||
| } | ||
|
|
||
| string[] inputs = words.ToArray(); | ||
| var results = new WordParseResult[inputs.Length]; | ||
| var callbackErrors = new ConcurrentQueue<Exception>(); | ||
| void Notify(int index, WordParseResult result) | ||
| { | ||
| try | ||
| { | ||
| progress?.Invoke(index, inputs[index], result); | ||
| } | ||
| catch (Exception error) | ||
| { | ||
| callbackErrors.Enqueue(error); | ||
| } | ||
| } | ||
|
|
||
| void Parse(int index) | ||
| { | ||
| object trace = null; | ||
| Notify(index, null); | ||
| try | ||
| { | ||
| var analyses = ParseWord(inputs[index], out trace, guessRoot).ToList().AsReadOnly(); | ||
| results[index] = new WordParseResult(inputs[index], analyses, trace, null); | ||
| } | ||
| catch (Exception error) | ||
| { | ||
| results[index] = new WordParseResult(inputs[index], Array.Empty<Word>(), trace, error); | ||
| } | ||
| Notify(index, results[index]); | ||
| } | ||
|
|
||
| if (_traceManager.IsTracing || maxWordConcurrency == 1) | ||
| { | ||
| for (int i = 0; i < inputs.Length; i++) | ||
| Parse(i); | ||
| } | ||
| else | ||
| { | ||
| Parallel.For( | ||
| 0, | ||
| inputs.Length, | ||
| new ParallelOptions { MaxDegreeOfParallelism = maxWordConcurrency }, | ||
| Parse | ||
| ); | ||
| } | ||
|
|
||
| if (!callbackErrors.IsEmpty) | ||
| throw new AggregateException("One or more progress callbacks failed.", callbackErrors); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI: F4. One failing progress callback throws away the whole batch: every word is parsed, but the |
||
|
|
||
| return Array.AsReadOnly(results); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Generates surface forms from the specified word synthesis information. | ||
| /// </summary> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace SIL.Machine.Morphology.HermitCrab | ||
| { | ||
| public sealed class WordParseResult | ||
| { | ||
| internal WordParseResult(string surfaceForm, IReadOnlyList<Word> analyses, object trace, Exception error) | ||
| { | ||
| SurfaceForm = surfaceForm; | ||
| Analyses = analyses; | ||
| Trace = trace; | ||
| Error = error; | ||
| } | ||
|
|
||
| public string SurfaceForm { get; } | ||
|
|
||
| public IReadOnlyList<Word> Analyses { get; } | ||
|
|
||
| public object Trace { get; } | ||
|
|
||
| public Exception Error { get; } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.