Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 15963 was 15960, checked in by bburlacu, 6 years ago

#2886: Fix serialization and cloning and plugin properties.

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