Free cookie consent management tool by TermsFeed Policy Generator

source: branches/ParallelExperiment/HeuristicLab.Optimization/3.3/MetaOptimizers/Experiment.cs @ 15337

Last change on this file since 15337 was 15337, checked in by pfleck, 7 years ago

#2822 Added parallel execution support for Experiment (similar to CrossValidation).

  • Fixed race-condition in BasicAlgorithm (potential NullReference of CancellationTokenSource).
  • Fixed race-condition in BatchRun (calling .Stop potentially fires both OnStopped and OnPaused).
File size: 20.4 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Drawing;
25using System.Linq;
26using System.Threading;
27using System.Threading.Tasks;
28using HeuristicLab.Collections;
29using HeuristicLab.Common;
30using HeuristicLab.Core;
31using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
32
33namespace HeuristicLab.Optimization {
34  /// <summary>
35  /// An experiment which contains multiple algorithms, batch runs or other experiments.
36  /// </summary>
37  [Item("Experiment", "An experiment which contains multiple algorithms, batch runs or other experiments.")]
38  [Creatable(CreatableAttribute.Categories.TestingAndAnalysis, Priority = 100)]
39  [StorableClass]
40  public sealed class Experiment : NamedItem, IOptimizer, IStorableContent {
41    public string Filename { get; set; }
42
43    public static new Image StaticItemImage {
44      get { return HeuristicLab.Common.Resources.VSImageLibrary.Event; }
45    }
46    public override Image ItemImage {
47      get {
48        if (ExecutionState == ExecutionState.Prepared) return HeuristicLab.Common.Resources.VSImageLibrary.ExperimentPrepared;
49        else if (ExecutionState == ExecutionState.Started) return HeuristicLab.Common.Resources.VSImageLibrary.ExperimentStarted;
50        else if (ExecutionState == ExecutionState.Paused) return HeuristicLab.Common.Resources.VSImageLibrary.ExperimentPaused;
51        else if (ExecutionState == ExecutionState.Stopped) return HeuristicLab.Common.Resources.VSImageLibrary.ExperimentStopped;
52        else return base.ItemImage;
53      }
54    }
55
56    [Storable]
57    private ExecutionState executionState;
58    public ExecutionState ExecutionState {
59      get { return executionState; }
60      private set {
61        if (executionState != value) {
62          executionState = value;
63          OnExecutionStateChanged();
64          OnItemImageChanged();
65        }
66      }
67    }
68
69    [Storable]
70    private TimeSpan executionTime;
71    public TimeSpan ExecutionTime {
72      get { return executionTime; }
73      private set {
74        executionTime = value;
75        OnExecutionTimeChanged();
76      }
77    }
78
79    [Storable]
80    private OptimizerList optimizers;
81    public OptimizerList Optimizers {
82      get { return optimizers; }
83    }
84
85    [Storable]
86    private RunCollection runs;
87    public RunCollection Runs {
88      get { return runs; }
89      private set {
90        if (value == null) throw new ArgumentNullException();
91        if (runs != value) {
92          if (runs != null) DeregisterRunsEvents();
93          runs = value;
94          if (runs != null) RegisterRunsEvents();
95        }
96      }
97    }
98
99    [Storable]
100    private int numberOfWorkers = 1;
101    public int NumberOfWorkers {
102      get { return numberOfWorkers; }
103      set {
104        if (value < 1) throw new ArgumentException("Number of Workers must not be lower than one.");
105        numberOfWorkers = value;
106      }
107    }
108
109    public IEnumerable<IOptimizer> NestedOptimizers {
110      get {
111        if (Optimizers == null) yield break;
112
113        foreach (IOptimizer opt in Optimizers) {
114          yield return opt;
115          foreach (IOptimizer nestedOpt in opt.NestedOptimizers)
116            yield return nestedOpt;
117        }
118      }
119    }
120
121    private bool experimentStarted = false;
122    private bool experimentStopped = false;
123
124    private ManualResetEventSlim allOptimizerFinished; // this indicates that all started optimizers have been paused or stopped
125    private SemaphoreSlim availableWorkers; // limits the number of concurrent optimizer executions
126
127    public Experiment()
128      : base() {
129      name = ItemName;
130      description = ItemDescription;
131      executionState = ExecutionState.Stopped;
132      executionTime = TimeSpan.Zero;
133      optimizers = new OptimizerList();
134      Runs = new RunCollection { OptimizerName = Name };
135      Initialize();
136    }
137    public Experiment(string name)
138      : base(name) {
139      description = ItemDescription;
140      executionState = ExecutionState.Stopped;
141      executionTime = TimeSpan.Zero;
142      optimizers = new OptimizerList();
143      Runs = new RunCollection { OptimizerName = Name };
144      Initialize();
145    }
146    public Experiment(string name, string description)
147      : base(name, description) {
148      executionState = ExecutionState.Stopped;
149      executionTime = TimeSpan.Zero;
150      optimizers = new OptimizerList();
151      Runs = new RunCollection { OptimizerName = Name };
152      Initialize();
153    }
154    [StorableConstructor]
155    private Experiment(bool deserializing) : base(deserializing) { }
156    [StorableHook(HookType.AfterDeserialization)]
157    private void AfterDeserialization() {
158      Initialize();
159    }
160    private Experiment(Experiment original, Cloner cloner)
161      : base(original, cloner) {
162      executionState = original.executionState;
163      executionTime = original.executionTime;
164      optimizers = cloner.Clone(original.optimizers);
165      runs = cloner.Clone(original.runs);
166
167      experimentStarted = original.experimentStarted;
168      experimentStopped = original.experimentStopped;
169      numberOfWorkers = original.numberOfWorkers;
170      Initialize();
171    }
172    public override IDeepCloneable Clone(Cloner cloner) {
173      if (ExecutionState == ExecutionState.Started) throw new InvalidOperationException(string.Format("Clone not allowed in execution state \"{0}\".", ExecutionState));
174      return new Experiment(this, cloner);
175    }
176
177    private void Initialize() {
178      RegisterOptimizersEvents();
179      foreach (IOptimizer optimizer in optimizers)
180        RegisterOptimizerEvents(optimizer);
181      if (runs != null) RegisterRunsEvents();
182    }
183
184    public void Prepare() {
185      Prepare(false);
186    }
187    public void Prepare(bool clearRuns) {
188      if ((ExecutionState != ExecutionState.Prepared) && (ExecutionState != ExecutionState.Paused) && (ExecutionState != ExecutionState.Stopped))
189        throw new InvalidOperationException(string.Format("Prepare not allowed in execution state \"{0}\".", ExecutionState));
190      if (Optimizers.Count == 0) return;
191
192      if (clearRuns) runs.Clear();
193
194      experimentStarted = false;
195      experimentStopped = false;
196      foreach (IOptimizer optimizer in Optimizers.Where(x => x.ExecutionState != ExecutionState.Started)) {
197        // a race-condition may occur when the optimizer has changed the state by itself in the meantime
198        try { optimizer.Prepare(clearRuns); } catch (InvalidOperationException) { }
199      }
200    }
201    public void Start() {
202      Start(CancellationToken.None);
203    }
204
205    public void Start(CancellationToken cancellationToken) {
206      if ((ExecutionState != ExecutionState.Prepared) && (ExecutionState != ExecutionState.Paused))
207        throw new InvalidOperationException(string.Format("Start not allowed in execution state \"{0}\".", ExecutionState));
208      if (Optimizers.Count == 0) return;
209
210      experimentStarted = true;
211      experimentStopped = false;
212      allOptimizerFinished = new ManualResetEventSlim(false);
213      availableWorkers = new SemaphoreSlim(NumberOfWorkers, NumberOfWorkers);
214
215      var runnableOptimizers = Optimizers.Where(o => o.ExecutionState == ExecutionState.Prepared || o.ExecutionState == ExecutionState.Paused).ToList();
216      if (!runnableOptimizers.Any()) return;
217
218      while (runnableOptimizers.Any()) {
219        try {
220          availableWorkers.Wait(cancellationToken); // an optimizer was pause/stopped previously
221          if (experimentStopped || !experimentStarted) break;
222          // some optimizers might be started manually somewhere else
223          runnableOptimizers.RemoveAll(o => !(o.ExecutionState == ExecutionState.Prepared || o.ExecutionState == ExecutionState.Paused));
224          var optimizer = runnableOptimizers.FirstOrDefault();
225          runnableOptimizers.Remove(optimizer);
226          optimizer?.StartAsync(cancellationToken);
227        } catch (InvalidOperationException) { } catch (OperationCanceledException) { }
228      }
229
230      allOptimizerFinished.Wait();
231    }
232    public async Task StartAsync() { await StartAsync(CancellationToken.None); }
233    public async Task StartAsync(CancellationToken cancellationToken) {
234      await AsyncHelper.DoAsync(Start, cancellationToken);
235    }
236    public void Pause() {
237      if (ExecutionState != ExecutionState.Started)
238        throw new InvalidOperationException(string.Format("Pause not allowed in execution state \"{0}\".", ExecutionState));
239      if (Optimizers.Count == 0) return;
240
241      experimentStarted = false;
242      experimentStopped = false;
243      foreach (IOptimizer optimizer in Optimizers.Where(x => x.ExecutionState == ExecutionState.Started)) {
244        // a race-condition may occur when the optimizer has changed the state by itself in the meantime
245        try { optimizer.Pause(); } catch (InvalidOperationException) { } catch (NotSupportedException) { }
246      }
247    }
248    public void Stop() {
249      if ((ExecutionState != ExecutionState.Started) && (ExecutionState != ExecutionState.Paused))
250        throw new InvalidOperationException(string.Format("Stop not allowed in execution state \"{0}\".", ExecutionState));
251      if (Optimizers.Count == 0) return;
252
253      experimentStarted = false;
254      experimentStopped = true;
255      if (Optimizers.Any(x => (x.ExecutionState == ExecutionState.Started) || (x.ExecutionState == ExecutionState.Paused))) {
256        foreach (var optimizer in Optimizers.Where(x => (x.ExecutionState == ExecutionState.Started) || (x.ExecutionState == ExecutionState.Paused))) {
257          // a race-condition may occur when the optimizer has changed the state by itself in the meantime
258          try { optimizer.Stop(); } catch (InvalidOperationException) { }
259        }
260      } else {
261        OnStopped();
262      }
263    }
264
265    #region Events
266    protected override void OnNameChanged() {
267      base.OnNameChanged();
268      Runs.OptimizerName = Name;
269    }
270
271    public event EventHandler ExecutionStateChanged;
272    private void OnExecutionStateChanged() {
273      EventHandler handler = ExecutionStateChanged;
274      if (handler != null) handler(this, EventArgs.Empty);
275    }
276    public event EventHandler ExecutionTimeChanged;
277    private void OnExecutionTimeChanged() {
278      EventHandler handler = ExecutionTimeChanged;
279      if (handler != null) handler(this, EventArgs.Empty);
280    }
281    public event EventHandler Prepared;
282    private void OnPrepared() {
283      ExecutionState = ExecutionState.Prepared;
284      EventHandler handler = Prepared;
285      if (handler != null) handler(this, EventArgs.Empty);
286    }
287    public event EventHandler Started;
288    private void OnStarted() {
289      ExecutionState = ExecutionState.Started;
290      EventHandler handler = Started;
291      if (handler != null) handler(this, EventArgs.Empty);
292    }
293    public event EventHandler Paused;
294    private void OnPaused() {
295      ExecutionState = ExecutionState.Paused;
296      EventHandler handler = Paused;
297      if (handler != null) handler(this, EventArgs.Empty);
298      allOptimizerFinished.Set();
299    }
300    public event EventHandler Stopped;
301    private void OnStopped() {
302      ExecutionState = ExecutionState.Stopped;
303      EventHandler handler = Stopped;
304      if (handler != null) handler(this, EventArgs.Empty);
305      allOptimizerFinished.Set();
306    }
307    public event EventHandler<EventArgs<Exception>> ExceptionOccurred;
308    private void OnExceptionOccurred(Exception exception) {
309      EventHandler<EventArgs<Exception>> handler = ExceptionOccurred;
310      if (handler != null) handler(this, new EventArgs<Exception>(exception));
311    }
312
313    private void RegisterOptimizersEvents() {
314      Optimizers.CollectionReset += new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_CollectionReset);
315      Optimizers.ItemsAdded += new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_ItemsAdded);
316      Optimizers.ItemsRemoved += new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_ItemsRemoved);
317      Optimizers.ItemsReplaced += new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_ItemsReplaced);
318    }
319    private void DeregisterOptimizersEvents() {
320      Optimizers.CollectionReset -= new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_CollectionReset);
321      Optimizers.ItemsAdded -= new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_ItemsAdded);
322      Optimizers.ItemsRemoved -= new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_ItemsRemoved);
323      Optimizers.ItemsReplaced -= new CollectionItemsChangedEventHandler<IndexedItem<IOptimizer>>(Optimizers_ItemsReplaced);
324    }
325    private void Optimizers_CollectionReset(object sender, CollectionItemsChangedEventArgs<IndexedItem<IOptimizer>> e) {
326      foreach (IndexedItem<IOptimizer> item in e.OldItems)
327        RemoveOptimizer(item.Value);
328      foreach (IndexedItem<IOptimizer> item in e.Items)
329        AddOptimizer(item.Value);
330    }
331    private void Optimizers_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IndexedItem<IOptimizer>> e) {
332      foreach (IndexedItem<IOptimizer> item in e.Items)
333        AddOptimizer(item.Value);
334    }
335    private void Optimizers_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IndexedItem<IOptimizer>> e) {
336      foreach (IndexedItem<IOptimizer> item in e.Items)
337        RemoveOptimizer(item.Value);
338    }
339    private void Optimizers_ItemsReplaced(object sender, CollectionItemsChangedEventArgs<IndexedItem<IOptimizer>> e) {
340      foreach (IndexedItem<IOptimizer> item in e.OldItems)
341        RemoveOptimizer(item.Value);
342      foreach (IndexedItem<IOptimizer> item in e.Items)
343        AddOptimizer(item.Value);
344    }
345    private void AddOptimizer(IOptimizer optimizer) {
346      RegisterOptimizerEvents(optimizer);
347      Runs.AddRange(optimizer.Runs);
348      optimizer.Prepare();
349      if (ExecutionState == ExecutionState.Stopped && optimizer.ExecutionState == ExecutionState.Prepared)
350        OnPrepared();
351    }
352    private void RemoveOptimizer(IOptimizer optimizer) {
353      DeregisterOptimizerEvents(optimizer);
354      Runs.RemoveRange(optimizer.Runs);
355      if (ExecutionState == ExecutionState.Prepared && !optimizers.Any(opt => opt.ExecutionState == ExecutionState.Prepared))
356        OnStopped();
357    }
358
359    private void RegisterOptimizerEvents(IOptimizer optimizer) {
360      optimizer.ExceptionOccurred += new EventHandler<EventArgs<Exception>>(optimizer_ExceptionOccurred);
361      optimizer.ExecutionTimeChanged += new EventHandler(optimizer_ExecutionTimeChanged);
362      optimizer.Paused += new EventHandler(optimizer_Paused);
363      optimizer.Prepared += new EventHandler(optimizer_Prepared);
364      optimizer.Started += new EventHandler(optimizer_Started);
365      optimizer.Stopped += new EventHandler(optimizer_Stopped);
366      optimizer.Runs.CollectionReset += new CollectionItemsChangedEventHandler<IRun>(optimizer_Runs_CollectionReset);
367      optimizer.Runs.ItemsAdded += new CollectionItemsChangedEventHandler<IRun>(optimizer_Runs_ItemsAdded);
368      optimizer.Runs.ItemsRemoved += new CollectionItemsChangedEventHandler<IRun>(optimizer_Runs_ItemsRemoved);
369    }
370    private void DeregisterOptimizerEvents(IOptimizer optimizer) {
371      optimizer.ExceptionOccurred -= new EventHandler<EventArgs<Exception>>(optimizer_ExceptionOccurred);
372      optimizer.ExecutionTimeChanged -= new EventHandler(optimizer_ExecutionTimeChanged);
373      optimizer.Paused -= new EventHandler(optimizer_Paused);
374      optimizer.Prepared -= new EventHandler(optimizer_Prepared);
375      optimizer.Started -= new EventHandler(optimizer_Started);
376      optimizer.Stopped -= new EventHandler(optimizer_Stopped);
377      optimizer.Runs.CollectionReset -= new CollectionItemsChangedEventHandler<IRun>(optimizer_Runs_CollectionReset);
378      optimizer.Runs.ItemsAdded -= new CollectionItemsChangedEventHandler<IRun>(optimizer_Runs_ItemsAdded);
379      optimizer.Runs.ItemsRemoved -= new CollectionItemsChangedEventHandler<IRun>(optimizer_Runs_ItemsRemoved);
380    }
381
382    private readonly object locker = new object();
383    private readonly object runsLocker = new object();
384    private void optimizer_ExceptionOccurred(object sender, EventArgs<Exception> e) {
385      lock (locker)
386        OnExceptionOccurred(e.Value);
387    }
388    private void optimizer_ExecutionTimeChanged(object sender, EventArgs e) {
389      // only wait for maximally 100ms to acquire lock, otherwise return and don't update the execution time
390      var success = Monitor.TryEnter(locker, 100);
391      if (!success) return;
392      try {
393        ExecutionTime = Optimizers.Aggregate(TimeSpan.Zero, (t, o) => t + o.ExecutionTime);
394      } finally {
395        Monitor.Exit(locker);
396      }
397    }
398    private void optimizer_Paused(object sender, EventArgs e) {
399      lock (locker) {
400        if (availableWorkers.CurrentCount < NumberOfWorkers)
401          availableWorkers.Release();
402        if (Optimizers.All(x => x.ExecutionState != ExecutionState.Started)) {
403          OnPaused();
404        }
405      }
406    }
407    private void optimizer_Prepared(object sender, EventArgs e) {
408      lock (locker)
409        if (Optimizers.All(x => x.ExecutionState == ExecutionState.Prepared)) OnPrepared();
410    }
411    private void optimizer_Started(object sender, EventArgs e) {
412      lock (locker)
413        if (ExecutionState != ExecutionState.Started) OnStarted();
414    }
415    private void optimizer_Stopped(object sender, EventArgs e) {
416      lock (locker) {
417        if (availableWorkers.CurrentCount < NumberOfWorkers)
418          availableWorkers.Release();
419        if (experimentStopped) {
420          if (Optimizers.All(x => (x.ExecutionState == ExecutionState.Stopped) || (x.ExecutionState == ExecutionState.Prepared))) {
421            OnStopped();
422          }
423        } else {
424          if (experimentStarted && Optimizers.Any(x => (x.ExecutionState == ExecutionState.Prepared) || (x.ExecutionState == ExecutionState.Paused))) return;
425          else if (Optimizers.All(x => x.ExecutionState == ExecutionState.Stopped)) {
426            OnStopped();
427          } else if (Optimizers.Any(x => (x.ExecutionState == ExecutionState.Prepared) || (x.ExecutionState == ExecutionState.Paused)) && Optimizers.All(o => o.ExecutionState != ExecutionState.Started)) {
428            OnPaused();
429          }
430        }
431      }
432    }
433    private void optimizer_Runs_CollectionReset(object sender, CollectionItemsChangedEventArgs<IRun> e) {
434      lock (runsLocker) {
435        Runs.RemoveRange(e.OldItems);
436        Runs.AddRange(e.Items);
437      }
438    }
439    private void optimizer_Runs_ItemsAdded(object sender, CollectionItemsChangedEventArgs<IRun> e) {
440      lock (runsLocker)
441        Runs.AddRange(e.Items);
442    }
443    private void optimizer_Runs_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IRun> e) {
444      lock (runsLocker)
445        Runs.RemoveRange(e.Items);
446    }
447
448    private void RegisterRunsEvents() {
449      runs.CollectionReset += new CollectionItemsChangedEventHandler<IRun>(Runs_CollectionReset);
450      runs.ItemsRemoved += new CollectionItemsChangedEventHandler<IRun>(Runs_ItemsRemoved);
451    }
452    private void DeregisterRunsEvents() {
453      runs.CollectionReset -= new CollectionItemsChangedEventHandler<IRun>(Runs_CollectionReset);
454      runs.ItemsRemoved -= new CollectionItemsChangedEventHandler<IRun>(Runs_ItemsRemoved);
455    }
456    private void Runs_CollectionReset(object sender, CollectionItemsChangedEventArgs<IRun> e) {
457      foreach (IOptimizer optimizer in Optimizers)
458        optimizer.Runs.RemoveRange(e.OldItems);
459    }
460    private void Runs_ItemsRemoved(object sender, CollectionItemsChangedEventArgs<IRun> e) {
461      foreach (IOptimizer optimizer in Optimizers)
462        optimizer.Runs.RemoveRange(e.Items);
463    }
464    #endregion
465  }
466}
Note: See TracBrowser for help on using the repository browser.