/// <summary>
        /// Gets best Solution
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="linearTolerance"></param>
        /// <param name="predict"></param>
        /// <returns></returns>
        public Solution GetBestSolution(IEnumerable <RlmIOWithValue> inputs, double linearTolerance = 0, bool predict = false)
        {
            Solution retVal = null;

            var         comparer      = new DynamicInputComparer();//Util.DynamicInputComparer;
            List <long> rneuronsFound = new List <long>();

            var rangeInfos = new Dictionary <int, InputRangeInfo>();
            int cnt        = 0;

            foreach (var item in inputs)
            {
                if (item.Type == Enums.RlmInputType.Linear)
                {
                    double val = Convert.ToDouble(item.Value);
                    double off = (item.Max - item.Min) * ((linearTolerance == 0) ? 0 : (linearTolerance / 100D));

                    rangeInfos.Add(cnt, new InputRangeInfo()
                    {
                        InputId = item.ID, InputType = item.Type, FromValue = val - off, ToValue = val + off
                    });
                }
                else
                {
                    rangeInfos.Add(cnt, new InputRangeInfo()
                    {
                        InputId = item.ID, InputType = item.Type, Value = item.Value
                    });
                }
                cnt++;
            }

            //swGetRneuron.Start();

            //RnnInputValue.RecurseInputForMatchingRneurons(DynamicInputs, rangeInfos, rneuronsFound);

            //swGetRneuron.Stop();
            //GetRneuronTimes.Add(swGetRneuron.Elapsed);
            //swGetRneuron.Reset();

            //TODO Cache Box, current implementation is slow don't know why
            if (!predict)
            {
                if (CacheBox.IsWithinRange(rangeInfos, linearTolerance))
                {
                    swGetRneuron.Start();

                    RlmInputValue.RecurseInputForMatchingRneurons(CacheBox.CachedInputs, rangeInfos, rneuronsFound);

                    swGetRneuron.Stop();
                    GetRneuronTimes.Add(swGetRneuron.Elapsed);
                    swGetRneuron.Reset();
                }
                else
                {
                    swRebuildCache.Start();

                    CacheBoxCount++;
                    CacheBox.Clear();

                    var cacheBoxRangeInfos = new Dictionary <int, InputRangeInfo>();
                    int cacheRangeCnt      = 0;
                    foreach (var item in inputs)
                    {
                        if (item.Type == Enums.RlmInputType.Linear)
                        {
                            double val     = Convert.ToDouble(item.Value);
                            double dataOff = (item.Max - item.Min) * ((linearTolerance == 0) ? 0 : (linearTolerance / 100D));
                            //double cacheMargin = (CacheBoxMargin == 0) ? 0 : ((item.Max - item.Min) * (CacheBoxMargin / 100));
                            double momentum = item.InputMomentum.MomentumDirection;
                            double toOff    = 0;
                            double fromOff  = 0;
                            double cacheOff = 0;

                            if (UseMomentumAvgValue)
                            {
                                cacheOff = item.InputMomentum.MomentumValue * MomentumAdjustment;
                            }
                            else
                            {
                                cacheOff = (item.Max - item.Min) * ((linearTolerance == 0) ? 0 : (MomentumAdjustment / 100D));
                            }


                            if (momentum > 0)
                            {
                                var offset = momentum * cacheOff;
                                toOff   = val + dataOff + (cacheOff + offset);
                                fromOff = val - dataOff - (cacheOff - offset);
                            }
                            else if (momentum < 0)
                            {
                                var offset = Math.Abs(momentum) * cacheOff;
                                toOff   = val + dataOff + (cacheOff - offset);
                                fromOff = val - dataOff - (cacheOff + offset);
                            }
                            else
                            {
                                toOff   = val + dataOff + cacheOff;
                                fromOff = val - dataOff - cacheOff;
                            }

                            double cacheMargin = (CacheBoxMargin == 0) ? 0 : (cacheOff) * (CacheBoxMargin / 100D);

                            toOff   += cacheMargin;
                            fromOff -= cacheMargin;

                            cacheBoxRangeInfos.Add(cacheRangeCnt, new InputRangeInfo()
                            {
                                InputId = item.ID, FromValue = Math.Ceiling(fromOff), ToValue = Math.Ceiling(toOff)
                            });
                        }
                        else
                        {
                            cacheBoxRangeInfos.Add(cacheRangeCnt, new InputRangeInfo()
                            {
                                InputId = item.ID, Value = item.Value
                            });
                        }
                        cacheRangeCnt++;
                    }

                    CacheBox.SetRanges(cacheBoxRangeInfos.Values);

                    CacheBox.CachedInputs = RlmInputValue.RecurseInputForMatchingRneuronsForCaching(DynamicInputs, cacheBoxRangeInfos, rangeInfos, rneuronsFound);
                    //RnnInputValue.RecurseInputForMatchingRneurons(CacheBox.CachedInputs, rangeInfos, rneuronsFound);

                    swRebuildCache.Stop();
                    RebuildCacheboxTimes.Add(swRebuildCache.Elapsed);
                    swRebuildCache.Reset();
                }
            }
            else
            {
                RlmInputValue.RecurseInputForMatchingRneurons(DynamicInputs, rangeInfos, rneuronsFound);
            }

            BestSolution currBS = null;

            foreach (var rneuronId in rneuronsFound)
            {
                Dictionary <long, BestSolution> bsDict;
                if (BestSolutions.TryGetValue(rneuronId, out bsDict))
                {
                    foreach (var bs in bsDict.Values)
                    {
                        if (!predict)
                        {
                            if (currBS != null)
                            {
                                if (bs.CycleScore > currBS.CycleScore)
                                {
                                    currBS = bs;
                                }
                                else if (bs.CycleScore == currBS.CycleScore && bs.SessionScore >= currBS.SessionScore && bs.CycleOrder >= currBS.CycleOrder)
                                {
                                    currBS = bs;
                                }
                            }
                            else
                            {
                                currBS = bs;
                            }
                        }
                        else
                        {
                            if (currBS != null)
                            {
                                if (bs.SessionScore > currBS.SessionScore)
                                {
                                    currBS = bs;
                                }
                                else if (bs.SessionScore == currBS.SessionScore && bs.CycleScore >= currBS.CycleScore && bs.CycleOrder >= currBS.CycleOrder)
                                {
                                    currBS = bs;
                                }
                            }
                            else
                            {
                                currBS = bs;
                            }
                        }
                    }
                }
            }

            if (currBS != null)
            {
                retVal = Solutions[currBS.SolutionId];
            }

            return(retVal);
        }