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

Bug/Error when using ParallelTaskExecutor #40

Closed
MattWolf74 opened this Issue Oct 3, 2018 · 8 comments

Comments

2 participants
@MattWolf74

MattWolf74 commented Oct 3, 2018

I just discovered that when running the optimizer within a Task/Tread/TPL Dataflow block with TaskExecutor set to ParallelTaskExecuter when instantiating GeneticAlgorithm, it blocks all other outside operations during the lifetime of the optimizer run. This does not happen when not setting the TaskExecutor option.

Most likely this is a bug? Is there a workaround? At the moment I can hence not run the objective function in parallel.

Can someone please help?

@giacomelli giacomelli self-assigned this Oct 3, 2018

@giacomelli giacomelli added the question label Oct 3, 2018

@giacomelli

This comment has been minimized.

Owner

giacomelli commented Oct 3, 2018

Yes, this is an expected behavior. The ParallTaskExecutor will run the fitness evaluation in parallel, but the call of GeneticAlgorithm.Start wait until the GeneticAlgorithm.Termination is reached.

One solution, if you want to run GeneticAlgorithm.Start in parallel of other operations in your application is running it on a separated thread.

Where are you using GeneticSharp? Console application? Unity3d?

@MattWolf74

This comment has been minimized.

MattWolf74 commented Oct 3, 2018

@giacomelli

This comment has been minimized.

Owner

giacomelli commented Oct 3, 2018

Ok, now I'm understanding better your problem. Can you provide a simple running sample with this problem?

@MattWolf74

This comment has been minimized.

MattWolf74 commented Oct 3, 2018

@MattWolf74

This comment has been minimized.

MattWolf74 commented Oct 5, 2018

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic), Serializable]
public class GeneticOptimizerVariable
{
    public string VariableName { get; set; }
    public int NumberDigitsPrecision { get; set; }
    public double MinimumValue { get; set; }
    public double MaximumValue { get; set; }

    public GeneticOptimizerVariable()
    { }

    public GeneticOptimizerVariable(string variableName, int numberDigitsPrecision, double minimumValue, double maximumValue)
    {
        VariableName = variableName;
        NumberDigitsPrecision = numberDigitsPrecision;
        MinimumValue = minimumValue;
        MaximumValue = maximumValue;
    }
}

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic), Serializable]
public class GeneticOptimizerConfiguration
{
    public int NumberThreadsToUse { get; set; }
    public string OptimizeVariableName { get; set; }
    public List<GeneticOptimizerVariable> Variables { get; set; }

    public GeneticOptimizerConfiguration()
    { }

    public GeneticOptimizerConfiguration(string optimizeVariableName, List<GeneticOptimizerVariable> variables, int numberThreadsToUse)
    {
        OptimizeVariableName = optimizeVariableName;
        Variables = variables;
        NumberThreadsToUse = numberThreadsToUse;
    }
}

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic), Serializable]
public class GeneticOptimizerResult
{
    public string OutputVariableName { get; set; }
    public List<string> InputVariableNames { get; set; }
    public List<double> BestFitInputs { get; set; }
    public double BestFitOutput { get; set; }
    public List<string> IterationArray { get; set; } //comma delimited -> values 1,...,n-1 = input values, n = output value
    
    public GeneticOptimizerResult()
    {
        InputVariableNames = new List<string>();
        BestFitInputs = new List<double>();
        IterationArray = new List<string>();
    }

    public GeneticOptimizerResult(string optimizationVariableName, List<string> variableNames)
    {
        OutputVariableName = optimizationVariableName;
        InputVariableNames = variableNames;
        BestFitInputs = new List<double>();
        IterationArray = new List<string>();
    }
}

public class GeneticOptimizer
{
    private const int MinimumNumberPopulation = 50;
    private const int MaximumNumberPopulation = 100;
    private readonly GeneticOptimizerConfiguration _configuration;
    private readonly Action<string> _generationRanCallback;
    private readonly GeneticAlgorithm _algorithm;
    private GeneticOptimizerResult _result;

    public GeneticOptimizer(GeneticOptimizerConfiguration configuration, Func<double[], double> objectiveFunction, Action<string> generationRanCallback = null)
    {
        //store configuration
        _configuration = configuration;
        _generationRanCallback = generationRanCallback;

        //set min/max/precision of input variables
        var minValues = new double[_configuration.Variables.Count];
        var maxValues = new double[_configuration.Variables.Count];
        var fractionDigits = new int[_configuration.Variables.Count];

        for (int index = 0; index < _configuration.Variables.Count; index++)
        {
            minValues[index] = _configuration.Variables[index].MinimumValue;
            maxValues[index] = _configuration.Variables[index].MaximumValue;
            fractionDigits[index] = _configuration.Variables[index].NumberDigitsPrecision;
        }

        //total bits
        var totalBits = new int[] { 64 };

        //chromosome
        var chromosome = new FloatingPointChromosome(minValues, maxValues, totalBits, fractionDigits);

        //population
        var population = new Population(MinimumNumberPopulation, MaximumNumberPopulation, chromosome);
        
        //set fitness function
        var fitnessFunction = new FuncFitness(c =>
        {
            var fc = c as FloatingPointChromosome;
            var inputs = fc.ToFloatingPoints();
            var result = objectiveFunction(inputs);
            
            //add to results
            if (!Double.IsNaN(result))
            {
                var list = inputs.ToList();
                list.Add(result);

                _result.IterationArray.Add(string.Join(",", list));
            }
        
            return result;
        });

        //other variables
        var selection = new EliteSelection();
        var crossover = new UniformCrossover(0.5f);
        var mutation = new FlipBitMutation();
        var termination = new FitnessThresholdTermination();

        _algorithm = new GeneticAlgorithm(population, fitnessFunction, selection, crossover, mutation)
        {
            Termination = termination,
        };

        //task parallelism
        var taskExecutor = new ParallelTaskExecutor();
        taskExecutor.MinThreads = 1;
        taskExecutor.MaxThreads = _configuration.NumberThreadsToUse;
        _algorithm.TaskExecutor = taskExecutor;

        //if (_configuration.NumberThreadsToUse > 1)
        //{
        //    var taskExecutor = new ParallelTaskExecutor();
        //    taskExecutor.MinThreads = 1;
        //    taskExecutor.MaxThreads = _configuration.NumberThreadsToUse;
        //    _algorithm.TaskExecutor = taskExecutor;
        //}
        
        //register generation ran callback
        _algorithm.GenerationRan += AlgorithmOnGenerationRan;

    }

    public void Start()
    {
        //define result
        _result = new GeneticOptimizerResult(_configuration.OptimizeVariableName, _configuration.Variables.Select(x => x.VariableName).ToList());

        //start optimizer
        _algorithm.Start();
    }

    public void Stop()
    {
        _algorithm.Stop();
    }

    public GeneticOptimizerResult GetResults()
    {
        return _result;
    }

    private void AlgorithmOnGenerationRan(object sender, EventArgs e)
    {
        var bestChromosome = _algorithm.BestChromosome as FloatingPointChromosome;
        if (bestChromosome == null || bestChromosome.Fitness == null)
            return;

        var phenotype = bestChromosome.ToFloatingPoints();

        //update results with best fit
        _result.BestFitInputs = phenotype.ToList();
        _result.BestFitOutput = bestChromosome.Fitness.Value;

        //invoke callback to update
        if (_generationRanCallback != null)
        {
            var variables = string.Join(" - ", _configuration.Variables.Select((item, index) => $"{item.VariableName} = {phenotype[index]}"));
            var updateString = $"Optimizer Generation: {_algorithm.GenerationsNumber} - Fitness: {bestChromosome.Fitness.Value} - Variables: {variables}";
            _generationRanCallback(updateString);
        }
    }
}

Then running the following code in a Console Application:

class Program
{
    static void Main(string[] args)
    {
        var progressTimer = new System.Timers.Timer(1000);
        progressTimer.AutoReset = true;
        progressTimer.Elapsed += (sender, arg) =>
        {
            //do something
            Console.WriteLine("Hello from progress timer");
        
        };
        
        //start timer
        progressTimer.Start();

        Task.Run(() =>
        {
            RunGeneticOptimizer();

        }).Wait();

        Console.WriteLine("All tasks inside actionblock completed");

        Console.WriteLine($"Press Key to quit");
        Console.ReadLine();

    }

    private static void GenerationRanCallback(string obj)
    {
        Console.WriteLine(obj);
    }

    private static void RunGeneticOptimizer()
    {
        //optimizer variables
        var variables = new List<GeneticOptimizerVariable>()
        {
            new GeneticOptimizerVariable("x", 4, -10, 10)
        };

        //optimizer configuration
        var configuration = new GeneticOptimizerConfiguration("y", variables, 1);

        //objective function
        var objectiveFunction = new Func<double[], double>(inputs =>
        {
            Thread.Sleep(1000);

            var objectiveFunctionResult = Math.Pow(inputs[0], 3) / Math.Exp(Math.Pow(inputs[0], 0.8));
            return objectiveFunctionResult;
        });

        //optimizer
        var optimizer = new GeneticOptimizer(configuration, objectiveFunction, GenerationRanCallback);

        var watch = new Stopwatch();
        watch.Start();

        optimizer.Start();

        watch.Stop();

        Console.WriteLine($"Number milliseconds: {watch.ElapsedMilliseconds}");

        
        Console.WriteLine($"Press Key to quit");
        Console.ReadLine();
    }
}

You will notice that the timer thread is blocked. The reason is that the GeneticAlgorithm is configured with taskExecutor = new ParallelTaskExecutor() where the MaxThreads is set to 1 (as specified in the console app). Even when requesting the algorithm to run with >1 threads initially blocks the timer thread.

@giacomelli

This comment has been minimized.

Owner

giacomelli commented Oct 5, 2018

Thanks, I will investigate that.

giacomelli pushed a commit that referenced this issue Oct 6, 2018

@giacomelli

This comment has been minimized.

Owner

giacomelli commented Oct 6, 2018

The behavior you described (and I saw with your sample code) was caused by those lines on ParallelTaskExecutor's Start method:

ThreadPool.GetMinThreads(out int minWorker, out int minIOC);
ThreadPool.SetMinThreads(MinThreads, minIOC);

ThreadPool.GetMaxThreads(out int maxWorker, out int maxIOC);
ThreadPool.SetMaxThreads(MaxThreads, maxIOC);

When the MaxThreads is set to just 1, there are no others threads to attend the progressTimer.

To solve this, I changed that lines to:

// Do not change values if the new values to min and max threads are lower than already configured on ThreadPool.
ThreadPool.GetMinThreads(out minWorker, out minIOC);

if (MinThreads > minWorker)
{
    ThreadPool.SetMinThreads(MinThreads, minIOC);
}

ThreadPool.GetMaxThreads(out maxWorker, out maxIOC);

if (MaxThreads > maxWorker)
{
    ThreadPool.SetMaxThreads(MaxThreads, maxIOC);
}

I added your sample to this branch issue-40, could you please checkout it and run the /src/Samples/Issue9Sample/Issue9Sample.sln to validate if there is no other problem about this subject?

@giacomelli giacomelli added bug and removed question labels Oct 6, 2018

@MattWolf74

This comment has been minimized.

MattWolf74 commented Oct 21, 2018

I checked out the branch and verified that the issue is resolved. Thanks

@MattWolf74 MattWolf74 closed this Oct 21, 2018

giacomelli pushed a commit that referenced this issue Oct 30, 2018

giacomelli
Merge tag 'v2.1.0' into develop
* Features
   * Add geneValues parameter to FloatingPointChromosome #34

* Bug fix
   * Bug/Error when using ParallelTaskExecutor #40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment