source: branches/2886_SymRegGrammarEnumeration/HeuristicLab.Algorithms.DataAnalysis.SymRegGrammarEnumeration/GrammarEnumeration/GrammarEnumerationAlgorithm.cs @ 16022

Last change on this file since 16022 was 16022, checked in by bburlacu, 21 months ago

#2886: Remove MaxSentenceLength from priority calculation for the time being.

File size: 23.1 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Threading;
5using HeuristicLab.Algorithms.DataAnalysis.SymRegGrammarEnumeration.GrammarEnumeration;
6using HeuristicLab.Collections;
7using HeuristicLab.Common;
8using HeuristicLab.Core;
9using HeuristicLab.Data;
10using HeuristicLab.Optimization;
11using HeuristicLab.Parameters;
12using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
13using HeuristicLab.Problems.DataAnalysis;
14using HeuristicLab.Problems.DataAnalysis.Symbolic;
15using HeuristicLab.Problems.DataAnalysis.Symbolic.Regression;
16
17namespace HeuristicLab.Algorithms.DataAnalysis.SymRegGrammarEnumeration {
18  [Item("Grammar Enumeration Symbolic Regression", "Iterates all possible model structures for a fixed grammar.")]
19  [StorableClass]
20  [Creatable(CreatableAttribute.Categories.DataAnalysisRegression, Priority = 250)]
21  public class GrammarEnumerationAlgorithm : FixedDataAnalysisAlgorithm<IRegressionProblem> {
22    #region properties and result names
23    private readonly string SearchStructureSizeName = "Search Structure Size";
24    private readonly string GeneratedPhrasesName = "Generated/Archived Phrases";
25    private readonly string GeneratedSentencesName = "Generated Sentences";
26    private readonly string DistinctSentencesName = "Distinct Sentences";
27    private readonly string PhraseExpansionsName = "Phrase Expansions";
28    private readonly string AverageSentenceComplexityName = "Avg. Sentence Complexity among Distinct";
29    private readonly string OverwrittenSentencesName = "Sentences overwritten";
30    private readonly string AnalyzersParameterName = "Analyzers";
31    private readonly string ExpansionsPerSecondName = "Expansions per second";
32
33    private readonly string OptimizeConstantsParameterName = "Optimize Constants";
34    private readonly string ErrorWeightParameterName = "Error Weight";
35    private readonly string SearchDataStructureParameterName = "Search Data Structure";
36    private readonly string MaxComplexityParameterName = "Max. Complexity";
37    private readonly string GuiUpdateIntervalParameterName = "GUI Update Interval";
38    private readonly string GrammarSymbolsParameterName = "Grammar Symbols";
39    private readonly string SearchDataStructureSizeParameterName = "Search Data Structure Size";
40
41    // result names
42    public static readonly string BestTrainingModelResultName = "Best model (Training)";
43    public static readonly string BestTrainingSolutionResultName = "Best solution (Training)";
44    public static readonly string BestComplexityResultName = "Best solution complexity";
45
46    public override bool SupportsPause { get { return true; } }
47
48    protected IFixedValueParameter<BoolValue> OptimizeConstantsParameter {
49      get { return (IFixedValueParameter<BoolValue>)Parameters[OptimizeConstantsParameterName]; }
50    }
51
52    public bool OptimizeConstants {
53      get { return OptimizeConstantsParameter.Value.Value; }
54      set { OptimizeConstantsParameter.Value.Value = value; }
55    }
56
57    protected IFixedValueParameter<IntValue> MaxComplexityParameter {
58      get { return (IFixedValueParameter<IntValue>)Parameters[MaxComplexityParameterName]; }
59    }
60
61    public int MaxComplexity {
62      get { return MaxComplexityParameter.Value.Value; }
63      set { MaxComplexityParameter.Value.Value = value; }
64    }
65
66    protected IFixedValueParameter<DoubleValue> ErrorWeightParameter {
67      get { return (IFixedValueParameter<DoubleValue>)Parameters[ErrorWeightParameterName]; }
68    }
69
70    public double ErrorWeight {
71      get { return ErrorWeightParameter.Value.Value; }
72      set { ErrorWeightParameter.Value.Value = value; }
73    }
74
75    protected IFixedValueParameter<IntValue> GuiUpdateIntervalParameter {
76      get { return (IFixedValueParameter<IntValue>)Parameters[GuiUpdateIntervalParameterName]; }
77    }
78
79    public int GuiUpdateInterval {
80      get { return GuiUpdateIntervalParameter.Value.Value; }
81      set { GuiUpdateIntervalParameter.Value.Value = value; }
82    }
83
84    protected IFixedValueParameter<EnumValue<StorageType>> SearchDataStructureParameter {
85      get { return (IFixedValueParameter<EnumValue<StorageType>>)Parameters[SearchDataStructureParameterName]; }
86    }
87
88    public IFixedValueParameter<IntValue> SearchDataStructureSizeParameter {
89      get { return (IFixedValueParameter<IntValue>)Parameters[SearchDataStructureSizeParameterName]; }
90    }
91
92    public int SearchDataStructureSize {
93      get { return SearchDataStructureSizeParameter.Value.Value; }
94    }
95
96    public StorageType SearchDataStructure {
97      get { return SearchDataStructureParameter.Value.Value; }
98      set { SearchDataStructureParameter.Value.Value = value; }
99    }
100
101    public IFixedValueParameter<ReadOnlyCheckedItemCollection<IGrammarEnumerationAnalyzer>> AnalyzersParameter {
102      get { return (IFixedValueParameter<ReadOnlyCheckedItemCollection<IGrammarEnumerationAnalyzer>>)Parameters[AnalyzersParameterName]; }
103    }
104
105    public ICheckedItemCollection<IGrammarEnumerationAnalyzer> Analyzers {
106      get { return AnalyzersParameter.Value; }
107    }
108
109    public IFixedValueParameter<ReadOnlyCheckedItemCollection<EnumValue<GrammarRule>>> GrammarSymbolsParameter {
110      get { return (IFixedValueParameter<ReadOnlyCheckedItemCollection<EnumValue<GrammarRule>>>)Parameters[GrammarSymbolsParameterName]; }
111    }
112
113    public ReadOnlyCheckedItemCollection<EnumValue<GrammarRule>> GrammarSymbols {
114      get { return GrammarSymbolsParameter.Value; }
115    }
116
117    [Storable]
118    public SymbolString BestTrainingSentence { get; set; }     // Currently set in RSquaredEvaluator: quite hacky, but makes testing much easier for now...
119    #endregion
120
121    [Storable]
122    public Dictionary<int, int> DistinctSentencesComplexity { get; private set; }  // Semantically distinct sentences and their length in a run.
123
124    [Storable]
125    public HashSet<int> ArchivedPhrases { get; private set; }
126
127    [Storable]
128    internal SearchDataStore OpenPhrases { get; private set; }           // Stack/Queue/etc. for fetching the next node in the search tree. 
129
130    [Storable]
131    public int MaxSentenceLength { get; private set; }
132
133    #region execution stats
134    [Storable]
135    public int AllGeneratedSentencesCount { get; private set; }
136
137    [Storable]
138    public int OverwrittenSentencesCount { get; private set; } // It is not guaranteed that shorter solutions are found first.
139                                                               // When longer solutions are overwritten with shorter ones,
140                                                               // this counter is increased.
141    [Storable]
142    public int PhraseExpansionCount { get; private set; }      // Number, how many times a nonterminal symbol is replaced with a production rule.
143    #endregion
144
145    [Storable]
146    public Grammar Grammar { get; private set; }
147
148    #region ctors
149    public override IDeepCloneable Clone(Cloner cloner) {
150      return new GrammarEnumerationAlgorithm(this, cloner);
151    }
152
153    [StorableConstructor]
154    protected GrammarEnumerationAlgorithm(bool deserializing) : base(deserializing) { }
155
156    private void RegisterEvents() {
157      // re-wire analyzer events
158      foreach (var analyzer in Analyzers.CheckedItems)
159        analyzer.Register(this);
160      Analyzers.CheckedItemsChanged += Analyzers_CheckedItemsChanged;
161
162      SearchDataStructureParameter.Value.ValueChanged += (o, e) => Prepare();
163      SearchDataStructureSizeParameter.Value.ValueChanged += (o, e) => Prepare();
164    }
165
166    private void DeregisterEvents() {
167      foreach (var analyzer in Analyzers.CheckedItems)
168        analyzer.Register(this);
169      Analyzers.CheckedItemsChanged -= Analyzers_CheckedItemsChanged;
170
171      SearchDataStructureParameter.Value.ValueChanged -= (o, e) => Prepare();
172      SearchDataStructureSizeParameter.Value.ValueChanged -= (o, e) => Prepare();
173    }
174
175    [StorableHook(HookType.AfterDeserialization)]
176    private void AfterDeserialization() {
177      RegisterEvents();
178    }
179
180    public GrammarEnumerationAlgorithm() {
181      Parameters.Add(new FixedValueParameter<BoolValue>(OptimizeConstantsParameterName, "Run constant optimization in sentence evaluation.", new BoolValue(false)));
182      Parameters.Add(new FixedValueParameter<DoubleValue>(ErrorWeightParameterName, "Defines, how much weight is put on a phrase's r² value when priorizing phrases during search.", new DoubleValue(0.8)));
183      Parameters.Add(new FixedValueParameter<IntValue>(MaxComplexityParameterName, "The maximum number of variable symbols in a sentence.", new IntValue(12)));
184      Parameters.Add(new FixedValueParameter<IntValue>(GuiUpdateIntervalParameterName, "Number of generated sentences, until GUI is refreshed.", new IntValue(5000)));
185      Parameters.Add(new FixedValueParameter<IntValue>(SearchDataStructureSizeParameterName, "The size of the search data structure.", new IntValue((int)1e5)));
186      Parameters.Add(new FixedValueParameter<EnumValue<StorageType>>(SearchDataStructureParameterName, new EnumValue<StorageType>(StorageType.SortedSet)));
187
188      SearchDataStructureParameter.Value.ValueChanged += (o, e) => Prepare();
189      SearchDataStructureSizeParameter.Value.ValueChanged += (o, e) => Prepare();
190
191      var availableAnalyzers = new IGrammarEnumerationAnalyzer[] {
192        new SearchGraphVisualizer(),
193        new SentenceLogger(),
194        new RSquaredEvaluator()
195      };
196
197      Parameters.Add(new FixedValueParameter<ReadOnlyCheckedItemCollection<IGrammarEnumerationAnalyzer>>(
198        AnalyzersParameterName,
199        new CheckedItemCollection<IGrammarEnumerationAnalyzer>(availableAnalyzers).AsReadOnly()));
200
201      Analyzers.CheckedItemsChanged += Analyzers_CheckedItemsChanged;
202
203      foreach (var analyzer in Analyzers) {
204        Analyzers.SetItemCheckedState(analyzer, false);
205      }
206      Analyzers.SetItemCheckedState(Analyzers.First(analyzer => analyzer is RSquaredEvaluator), true);
207
208      var grammarSymbols = Enum.GetValues(typeof(GrammarRule))
209        .Cast<GrammarRule>()
210        .Select(v => new EnumValue<GrammarRule>(v));
211
212      Parameters.Add(new FixedValueParameter<ReadOnlyCheckedItemCollection<EnumValue<GrammarRule>>>(
213        GrammarSymbolsParameterName,
214        new ReadOnlyCheckedItemCollection<EnumValue<GrammarRule>>(new CheckedItemCollection<EnumValue<GrammarRule>>(grammarSymbols))
215      ));
216      foreach (EnumValue<GrammarRule> grammarSymbol in GrammarSymbols) {
217        GrammarSymbols.SetItemCheckedState(grammarSymbol, true);
218      }
219
220      // set a default problem
221      Problem = new RegressionProblem() {
222        ProblemData = new Problems.Instances.DataAnalysis.PolyTen(seed: 1234).GenerateRegressionData()
223      };
224    }
225
226    public GrammarEnumerationAlgorithm(GrammarEnumerationAlgorithm original, Cloner cloner) : base(original, cloner) {
227      foreach (var analyzer in Analyzers.CheckedItems)
228        analyzer.Register(this);
229      Analyzers.CheckedItemsChanged += Analyzers_CheckedItemsChanged;
230
231      DistinctSentencesComplexity = new Dictionary<int, int>(original.DistinctSentencesComplexity);
232      ArchivedPhrases = new HashSet<int>(original.ArchivedPhrases);
233      OpenPhrases = cloner.Clone(original.OpenPhrases);
234      Grammar = cloner.Clone(original.Grammar);
235
236      AllGeneratedSentencesCount = original.AllGeneratedSentencesCount;
237      OverwrittenSentencesCount = original.OverwrittenSentencesCount;
238      PhraseExpansionCount = original.PhraseExpansionCount;
239    }
240    #endregion
241
242    [Storable]
243    private Dictionary<VariableTerminalSymbol, double> variableImportance;
244
245    public override void Prepare() {
246      DistinctSentencesComplexity = new Dictionary<int, int>();
247      ArchivedPhrases = new HashSet<int>();
248      AllGeneratedSentencesCount = 0;
249      OverwrittenSentencesCount = 0;
250      PhraseExpansionCount = 0;
251
252      Grammar = new Grammar(Problem.ProblemData.AllowedInputVariables.ToArray(), GrammarSymbols.CheckedItems.Select(v => v.Value));
253      OpenPhrases = new SearchDataStore(SearchDataStructure, SearchDataStructureSize); // Select search strategy
254      base.Prepare(); // this actually clears the results which will get reinitialized on Run()
255    }
256
257    protected override void Run(CancellationToken cancellationToken) {
258      // do not reinitialize the algorithm if we're resuming from pause
259      if (previousExecutionState != ExecutionState.Paused) {
260        InitResults();
261        var phrase0 = new SymbolString(new[] { Grammar.StartSymbol });
262        var phrase0Hash = Grammar.Hasher.CalcHashCode(phrase0);
263
264        OpenPhrases.Store(new SearchNode(phrase0Hash, 0.0, 0.0, phrase0));
265      }
266
267      MaxSentenceLength = Grammar.GetMaxSentenceLength(MaxComplexity);
268      var errorWeight = ErrorWeight;
269      var optimizeConstants = OptimizeConstants; // cache value to avoid parameter lookup
270      // main search loop
271      while (OpenPhrases.Count > 0) {
272        if (cancellationToken.IsCancellationRequested)
273          break;
274
275        SearchNode fetchedSearchNode = OpenPhrases.GetNext();
276
277        if (fetchedSearchNode == null)
278          continue;
279
280        SymbolString currPhrase = fetchedSearchNode.SymbolString;
281
282        OnPhraseFetched(fetchedSearchNode.Hash, currPhrase);
283
284        ArchivedPhrases.Add(fetchedSearchNode.Hash);
285
286        // expand next nonterminal symbols
287        int nonterminalSymbolIndex = currPhrase.NextNonterminalIndex();
288        NonterminalSymbol expandedSymbol = (NonterminalSymbol)currPhrase[nonterminalSymbolIndex];
289        var appliedProductions = Grammar.Productions[expandedSymbol];
290
291        for (int i = 0; i < appliedProductions.Count; i++) {
292          PhraseExpansionCount++;
293
294          SymbolString newPhrase = currPhrase.DerivePhrase(nonterminalSymbolIndex, appliedProductions[i]);
295          int newPhraseComplexity = Grammar.GetComplexity(newPhrase);
296
297          if (newPhraseComplexity > MaxComplexity)
298            continue;
299
300          var phraseHash = Grammar.Hasher.CalcHashCode(newPhrase);
301
302          OnPhraseDerived(fetchedSearchNode.Hash, fetchedSearchNode.SymbolString, phraseHash, newPhrase, expandedSymbol, appliedProductions[i]);
303
304          if (newPhrase.IsSentence()) {
305            AllGeneratedSentencesCount++;
306
307            OnSentenceGenerated(fetchedSearchNode.Hash, fetchedSearchNode.SymbolString, phraseHash, newPhrase, expandedSymbol, appliedProductions[i]);
308
309            // Is the best solution found? (only if RSquaredEvaluator is activated)
310            if (Results.ContainsKey(RSquaredEvaluator.BestTrainingQualityResultName)) {
311              double r2 = ((DoubleValue)Results[RSquaredEvaluator.BestTrainingQualityResultName].Value).Value;
312              if (r2.IsAlmost(1.0)) {
313                UpdateView(force: true);
314                return;
315              }
316            }
317
318            if (!DistinctSentencesComplexity.ContainsKey(phraseHash) || DistinctSentencesComplexity[phraseHash] > newPhraseComplexity) {
319              if (DistinctSentencesComplexity.ContainsKey(phraseHash)) OverwrittenSentencesCount++; // for analysis only
320
321              DistinctSentencesComplexity[phraseHash] = newPhraseComplexity;
322              OnDistinctSentenceGenerated(fetchedSearchNode.Hash, fetchedSearchNode.SymbolString, phraseHash, newPhrase, expandedSymbol, appliedProductions[i]);
323            }
324            UpdateView();
325
326          } else if (!OpenPhrases.Contains(phraseHash) && !ArchivedPhrases.Contains(phraseHash)) {
327
328            bool isCompleteSentence = IsCompleteSentence(newPhrase);
329            double r2 = isCompleteSentence ? Grammar.EvaluatePhrase(newPhrase, Problem.ProblemData, optimizeConstants) : fetchedSearchNode.R2;
330            double phrasePriority = GetPriority(newPhrase, r2, MaxSentenceLength);
331
332            SearchNode newSearchNode = new SearchNode(phraseHash, phrasePriority, r2, newPhrase);
333            OpenPhrases.Store(newSearchNode);
334          }
335        }
336      }
337      UpdateView(force: true);
338    }
339
340    protected static double GetPriority(SymbolString phrase, double r2, int maxSentenceLength) {
341      return (1 - r2) * phrase.Count();
342    }
343
344    private bool IsCompleteSentence(SymbolString phrase) {
345      return !phrase.Any(x => x is NonterminalSymbol && x != Grammar.Expr);
346    }
347
348    #region pause support
349    private ExecutionState previousExecutionState;
350
351    protected override void OnPaused() {
352      previousExecutionState = this.ExecutionState;
353      base.OnPaused();
354    }
355
356    protected override void OnPrepared() {
357      previousExecutionState = this.ExecutionState;
358      base.OnPrepared();
359    }
360
361    protected override void OnStarted() {
362      previousExecutionState = this.ExecutionState;
363      base.OnStarted();
364    }
365
366    protected override void OnStopped() {
367      previousExecutionState = this.ExecutionState;
368      // free memory at the end of the run (this saves a lot of memory)
369      ArchivedPhrases.Clear();
370      OpenPhrases.Clear();
371      DistinctSentencesComplexity.Clear();
372
373      if (BestTrainingSentence == null) {
374        base.OnStopped();
375        return;
376      }
377
378      var interpreter = new SymbolicDataAnalysisExpressionTreeLinearInterpreter();
379      var tree = Grammar.ParseSymbolicExpressionTree(BestTrainingSentence);
380      var model = new SymbolicRegressionModel(Problem.ProblemData.TargetVariable, tree, interpreter);
381
382      SymbolicRegressionConstantOptimizationEvaluator.OptimizeConstants(
383        interpreter,
384        model.SymbolicExpressionTree,
385        Problem.ProblemData,
386        Problem.ProblemData.TrainingIndices,
387        applyLinearScaling: true,
388        maxIterations: 10,
389        updateVariableWeights: false,
390        updateConstantsInTree: true);
391
392      model.Scale(Problem.ProblemData);
393      var bestTrainingSolution = new SymbolicRegressionSolution(model, Problem.ProblemData);
394
395      Results.AddOrUpdateResult(BestTrainingModelResultName, model);
396      Results.AddOrUpdateResult(BestTrainingSolutionResultName, bestTrainingSolution);
397      Results.AddOrUpdateResult(BestComplexityResultName, new IntValue(Grammar.GetComplexity(BestTrainingSentence)));
398      base.OnStopped();
399    }
400    #endregion
401
402    #region Visualization in HL
403    // Initialize entries in result set.
404    private void InitResults() {
405      Results.Clear();
406      Results.Add(new Result(GeneratedPhrasesName, new IntValue(0)));
407      Results.Add(new Result(SearchStructureSizeName, new IntValue(0)));
408      Results.Add(new Result(GeneratedSentencesName, new IntValue(0)));
409      Results.Add(new Result(DistinctSentencesName, new IntValue(0)));
410      Results.Add(new Result(PhraseExpansionsName, new IntValue(0)));
411      Results.Add(new Result(OverwrittenSentencesName, new IntValue(0)));
412      Results.Add(new Result(AverageSentenceComplexityName, new DoubleValue(1.0)));
413      Results.Add(new Result(ExpansionsPerSecondName, "In Thousand expansions per second", new IntValue(0)));
414    }
415
416    // Update the view for intermediate results in an algorithm run.
417    private int updates;
418    private void UpdateView(bool force = false) {
419      updates++;
420
421      if (force || updates % GuiUpdateInterval == 1) {
422        ((IntValue)Results[GeneratedPhrasesName].Value).Value = ArchivedPhrases.Count;
423        ((IntValue)Results[SearchStructureSizeName].Value).Value = OpenPhrases.Count;
424        ((IntValue)Results[GeneratedSentencesName].Value).Value = AllGeneratedSentencesCount;
425        ((IntValue)Results[DistinctSentencesName].Value).Value = DistinctSentencesComplexity.Count;
426        ((IntValue)Results[PhraseExpansionsName].Value).Value = PhraseExpansionCount;
427        ((DoubleValue)Results[AverageSentenceComplexityName].Value).Value = DistinctSentencesComplexity.Count > 0
428          ? DistinctSentencesComplexity.Select(pair => pair.Value).Average()
429          : 0;
430        ((IntValue)Results[OverwrittenSentencesName].Value).Value = OverwrittenSentencesCount;
431        ((IntValue)Results[ExpansionsPerSecondName].Value).Value = (int)((PhraseExpansionCount /
432                                                                          ExecutionTime.TotalSeconds) / 1000.0);
433      }
434    }
435    #endregion
436
437    #region events
438
439    // private event handlers for analyzers
440    private void Analyzers_CheckedItemsChanged(object sender, CollectionItemsChangedEventArgs<IGrammarEnumerationAnalyzer> args) {
441      // newly added items
442      foreach (var item in args.Items.Except(args.OldItems).Union(args.OldItems.Except(args.Items))) {
443        if (Analyzers.ItemChecked(item)) {
444          item.Register(this);
445        } else {
446          item.Deregister(this);
447        }
448      }
449    }
450
451    public event EventHandler<PhraseEventArgs> PhraseFetched;
452    private void OnPhraseFetched(int hash, SymbolString symbolString) {
453      if (PhraseFetched != null) {
454        PhraseFetched(this, new PhraseEventArgs(hash, symbolString));
455      }
456    }
457
458    public event EventHandler<PhraseAddedEventArgs> PhraseDerived;
459    private void OnPhraseDerived(int parentHash, SymbolString parentSymbolString, int addedHash, SymbolString addedSymbolString, Symbol expandedSymbol, Production expandedProduction) {
460      if (PhraseDerived != null) {
461        PhraseDerived(this, new PhraseAddedEventArgs(parentHash, parentSymbolString, addedHash, addedSymbolString, expandedSymbol, expandedProduction));
462      }
463    }
464
465    public event EventHandler<PhraseAddedEventArgs> SentenceGenerated;
466    private void OnSentenceGenerated(int parentHash, SymbolString parentSymbolString, int addedHash, SymbolString addedSymbolString, Symbol expandedSymbol, Production expandedProduction) {
467      if (SentenceGenerated != null) {
468        SentenceGenerated(this, new PhraseAddedEventArgs(parentHash, parentSymbolString, addedHash, addedSymbolString, expandedSymbol, expandedProduction));
469      }
470    }
471
472    public event EventHandler<PhraseAddedEventArgs> DistinctSentenceGenerated;
473    private void OnDistinctSentenceGenerated(int parentHash, SymbolString parentSymbolString, int addedHash, SymbolString addedSymbolString, Symbol expandedSymbol, Production expandedProduction) {
474      if (DistinctSentenceGenerated != null) {
475        DistinctSentenceGenerated(this, new PhraseAddedEventArgs(parentHash, parentSymbolString, addedHash, addedSymbolString, expandedSymbol, expandedProduction));
476      }
477    }
478
479    #endregion
480
481  }
482
483  #region events for analysis
484
485  public class PhraseEventArgs : EventArgs {
486    public int Hash { get; }
487
488    public SymbolString Phrase { get; }
489
490    public PhraseEventArgs(int hash, SymbolString phrase) {
491      Hash = hash;
492      Phrase = phrase;
493    }
494  }
495
496  public class PhraseAddedEventArgs : EventArgs {
497    public int ParentHash { get; }
498    public int NewHash { get; }
499
500    public SymbolString ParentPhrase { get; }
501    public SymbolString NewPhrase { get; }
502
503    public Symbol ExpandedSymbol { get; }
504
505    public Production ExpandedProduction { get; }
506
507    public PhraseAddedEventArgs(int parentHash, SymbolString parentPhrase, int newHash, SymbolString newPhrase, Symbol expandedSymbol, Production expandedProduction) {
508      ParentHash = parentHash;
509      ParentPhrase = parentPhrase;
510      NewHash = newHash;
511      NewPhrase = newPhrase;
512      ExpandedSymbol = expandedSymbol;
513      ExpandedProduction = expandedProduction;
514    }
515  }
516
517  #endregion
518}
Note: See TracBrowser for help on using the repository browser.