Example #1
0
 public ThrusterSolutionMap(ThrusterMap map, double linearAccel, double rotateAccel)
 {
     this.Map = map;
     this.LinearAccel = linearAccel;
     this.RotateAccel = rotateAccel;
 }
Example #2
0
        private static ThrusterSolutionMap GetThrusterSolutionMap(ThrusterMap map, ThrustContributionModel model, MassMatrix inertia, double mass)
        {
            // Add up the forces
            Vector3D sumLinearForce = new Vector3D();
            Vector3D sumTorque = new Vector3D();
            foreach (ThrusterSetting thruster in map.UsedThrusters)
            {
                var contribution = model.Contributions.FirstOrDefault(o => o.Item1 == thruster.ThrusterIndex && o.Item2 == thruster.SubIndex);
                if (contribution == null)
                {
                    throw new ApplicationException(string.Format("Didn't find contribution for thruster: {0}, {1}", thruster.ThrusterIndex, thruster.SubIndex));
                }

                sumLinearForce += contribution.Item3.TranslationForce * thruster.Percent;
                sumTorque += contribution.Item3.Torque * thruster.Percent;
            }

            // Divide by mass
            //F=MA, A=F/M
            double accel = sumLinearForce.Length / mass;

            Vector3D projected = inertia.Inertia.GetProjectedVector(sumTorque);
            double angAccel = sumTorque.Length / projected.Length;
            if (Math1D.IsInvalid(angAccel))
            {
                angAccel = 0;       // this happens when there is no net torque
            }

            return new ThrusterSolutionMap(map, accel, angAccel);
        }
        private static double GetScore_Inefficient3(ThrusterMap map, ThrustContributionModel model)
        {
            if (map.Flattened.Length != model.Contributions.Length)
            {
                throw new ApplicationException("TODO: Handle mismatched map and model");
            }

            // Add up all the thruster forces, regardless of direction

            double retVal = Enumerable.Range(0, map.Flattened.Length).
                Select(o =>
                {
                    if (map.Flattened[o].Item1 != model.Contributions[o].Item1 || map.Flattened[o].Item2 != model.Contributions[o].Item2)
                    {
                        throw new ApplicationException("TODO: Handle mismatched map and model");
                    }

                    // The translation force is the same as the thrust produced by the thruster.  Don't want to include
                    // torque, because this method only cares about how much fuel is being used, and adding torque
                    // is an innacurate complication
                    return model.Contributions[o].Item3.TranslationForceLength * map.Flattened[o].Item3;
                }).
                Sum();

            return retVal;
        }
        // This should be called twice, once for linear and once for rotation.  It should add the force length if dot is close to zero.  May also want a smaller length if dot is close to -1
        private static double GetScore_Inefficient4(ThrusterMap map, ThrustContributionModel model, Vector3D objectiveUnit, bool isLinear)
        {
            const double DOT_MAX = .3;      // when dot is >= this, there is no error
            const double PERCENT_NEG = .5;      // when dot is -1, this is the returned percent error

            if (map.Flattened.Length != model.Contributions.Length)
            {
                throw new ApplicationException("TODO: Handle mismatched map and model");
            }

            double retVal = Enumerable.Range(0, map.Flattened.Length).
                Select(o =>
                {
                    if (map.Flattened[o].Item1 != model.Contributions[o].Item1 || map.Flattened[o].Item2 != model.Contributions[o].Item2)
                    {
                        throw new ApplicationException("TODO: Handle mismatched map and model");
                    }

                    // Get the actual vector
                    Vector3D actualVect;
                    double actualVal;
                    if (isLinear)
                    {
                        actualVect = model.Contributions[o].Item3.TranslationForceUnit;
                        actualVal = model.Contributions[o].Item3.TranslationForceLength;
                    }
                    else
                    {
                        actualVect = model.Contributions[o].Item3.TorqueUnit;
                        actualVal = model.Contributions[o].Item3.TorqueLength;
                    }

                    // Use dot to see how in line this actual vector is with the ideal
                    double dot = Vector3D.DotProduct(objectiveUnit, actualVect);

                    // Convert dot into a percent
                    //      - If dot is near zero, error should be maximum, because this doesn't contribute to ideal
                    //      - Can't penalize positive and negative dots equally, or it will try to find a minimum fuel useage
                    //        that is still balanced, instead of maximum thrust that is balanced (the goal is maximum thrust
                    //        without venting fuel out the sides)
                    double percent;
                    if (dot > 0)
                    {
                        if(dot > DOT_MAX)
                        {
                            percent = 0;
                        }
                        else
                        {
                            percent = UtilityCore.GetScaledValue(1, 0, 0, DOT_MAX, dot);
                        }
                    }
                    else
                    {
                        percent = UtilityCore.GetScaledValue(1, PERCENT_NEG, 0, 1, -dot);
                    }

                    return actualVal * map.Flattened[o].Item3 * percent;
                }).
                Sum();

            return retVal;
        }
        private static double GetScore_Inefficient(ThrusterMap map, ThrustContributionModel model)
        {
            // Only focus on linear balance.  Torque balance is desired

            //TODO: This first version only looks a single thrusters.  Make a future version that looks a more combinations

            double retVal = 0;

            var byThruster = model.Contributions.
                ToLookup(o => o.Item1).
                Where(o => o.Count() > 1);

            foreach (var thruster in byThruster)
            {
                var arr = thruster.ToArray();

                foreach (var pair in UtilityCore.GetPairs(arr.Length))
                {
                    double dot = Vector3D.DotProduct(arr[pair.Item1].Item3.TranslationForceUnit, arr[pair.Item2].Item3.TranslationForceUnit);

                    if (dot < -.95)
                    {
                        // They are directly opposing each other.  Figure out the force they are firing, based on the thruster percents
                        double force1 = arr[pair.Item1].Item3.TranslationForceLength * map.Flattened.First(o => o.Item1 == arr[pair.Item1].Item1 && o.Item2 == arr[pair.Item1].Item2).Item3;
                        double force2 = arr[pair.Item2].Item3.TranslationForceLength * map.Flattened.First(o => o.Item1 == arr[pair.Item2].Item1 && o.Item2 == arr[pair.Item2].Item2).Item3;

                        // The min represents waste (because if the weaker thruster were turned off, the net force won't change)
                        double min = Math.Min(force1, force2);

                        // Square it
                        retVal += min * min;
                    }
                }
            }

            return retVal;
        }
        private static double GetScore_Inefficient2(ThrusterMap map, ThrustContributionModel model, double maxPossibleLinear, double maxPossibleRotate)
        {
            if (map.Flattened.Length != model.Contributions.Length)
            {
                throw new ApplicationException("TODO: Handle mismatched map and model");
            }

            // Add up all of the thruster forces.  Don't worry about direction, just get the sum of the magnitude
            var forces = Enumerable.Range(0, map.Flattened.Length).
                Select(o =>
                {
                    if (map.Flattened[o].Item1 != model.Contributions[o].Item1 || map.Flattened[o].Item2 != model.Contributions[o].Item2)
                    {
                        throw new ApplicationException("TODO: Handle mismatched map and model");
                    }

                    return new
                    {
                        Linear = model.Contributions[o].Item3.TranslationForceLength * map.Flattened[o].Item3,
                        Rotate = model.Contributions[o].Item3.TorqueLength * map.Flattened[o].Item3,
                    };
                }).
                ToArray();

            double linear = forces.Sum(o => o.Linear);
            double rotate = forces.Sum(o => o.Rotate);

            // Subtract max possible
            linear -= maxPossibleLinear;
            rotate -= maxPossibleRotate;

            //linear = Math.Max(linear, 0);       // don't let scores go negative
            //rotate = Math.Max(rotate, 0);
            linear = Math.Abs(linear);       // negative scores are also bad
            rotate = Math.Abs(rotate);

            // Return remainder squared
            return (linear * linear) + (rotate * rotate);
        }
        public static double[] GetThrustMapScore3(ThrusterMap map, ThrustContributionModel model, Vector3D? linear, Vector3D? rotation, double maxPossibleLinear = 0, double maxPossibleRotate = 0)
        {
            if (linear == null && rotation == null)
            {
                throw new ApplicationException("Objective linear and rotate can't both be null");
            }

            Tuple<Vector3D, Vector3D> forces = ApplyThrust(map, model);

            double[] retVal = new double[2];

            if (forces.Item1.LengthSquared.IsNearZero() && forces.Item2.LengthSquared.IsNearZero())
            {
                // When there is zero contribution, give a large error.  Otherwise the winning strategy is not to play :)
                retVal[0] = MAXERROR;        // Balance
                retVal[1] = MAXERROR;        // Inefficient
            }
            else
            {
                // Balance
                double linearScore = GetScore_Balance(linear, forces.Item1);
                double rotateScore = GetScore_Balance(rotation, forces.Item2);
                retVal[0] = linearScore + rotateScore;

                // Inefficient
                linearScore = linear == null ? 0 : GetScore_Inefficient4(map, model, linear.Value, true);
                rotateScore = rotation == null ? 0 : GetScore_Inefficient4(map, model, rotation.Value, false);
                retVal[1] = linearScore + rotateScore;
            }

            return retVal;
        }
        public static Tuple<Vector3D, Vector3D> ApplyThrust(ThrusterMap map, ThrustContributionModel model)
        {
            if (map.Flattened.Length != model.Contributions.Length)
            {
                throw new ApplicationException("TODO: Handle mismatched map and model");
            }

            var combined = Enumerable.Range(0, map.Flattened.Length).
                Select(o =>
                {
                    if (map.Flattened[o].Item1 != model.Contributions[o].Item1 || map.Flattened[o].Item2 != model.Contributions[o].Item2)
                    {
                        throw new ApplicationException("TODO: Handle mismatched map and model");
                    }

                    return new
                    {
                        Index = map.Flattened[o].Item1,
                        SubIndex = map.Flattened[o].Item2,
                        Percent = map.Flattened[o].Item3,
                        Contribution = model.Contributions[o].Item3,
                    };
                }).
                ToArray();

            Vector3D linear = Math3D.GetSum(combined.Select(o => Tuple.Create(o.Contribution.TranslationForce, o.Percent)).ToArray());
            Vector3D rotation = Math3D.GetSum(combined.Select(o => Tuple.Create(o.Contribution.Torque, o.Percent)).ToArray());

            return Tuple.Create(linear, rotation);
        }
        public static SolutionError GetThrustMapScore(ThrusterMap map, ThrustContributionModel model, Vector3D? linear, Vector3D? rotation, double maxPossibleLinear = 0, double maxPossibleRotate = 0)
        {
            if (linear == null && rotation == null)
            {
                throw new ApplicationException("Objective linear and rotate can't both be null");
            }

            Tuple<Vector3D, Vector3D> forces = ApplyThrust(map, model);

            double[] error = new double[3];

            if (forces.Item1.LengthSquared.IsNearZero() && forces.Item2.LengthSquared.IsNearZero())
            {
                // When there is zero contribution, give a large error.  Otherwise the winning strategy is not to play :)
                error[0] = MAXERROR;        // Balance
                error[1] = MAXERROR;        // UnderPowered
            }
            else
            {
                // Balance
                double linearScore = GetScore_Balance(linear, forces.Item1);
                double rotateScore = GetScore_Balance(rotation, forces.Item2);
                error[0] = linearScore + rotateScore;

                // UnderPowered
                linearScore = GetScore_UnderPowered(linear, forces.Item1, maxPossibleLinear);
                rotateScore = GetScore_UnderPowered(rotation, forces.Item2, maxPossibleRotate);
                error[1] = linearScore + rotateScore;
            }

            // Inneficient
            error[2] = GetScore_Inefficient(map, model);

            // Total
            // It's important to include the maximize thrust, but just a tiny bit.  Without it, the ship will be happy to
            // fire thrusters in all directions.  But if maximize thrust is any stronger, the solution will have too much
            // unbalance
            double total = error[0] + (error[1] * .01) + (error[2] * .1);

            return new SolutionError(error, total);
        }
        private const double MAXERROR = 100000000000;       // need something excessively large, but less than double.max

        #endregion

        /// <summary>
        /// This is a wrapper of UtilityAI.DiscoverSolution
        /// </summary>
        public static void DiscoverSolutionAsync(Bot bot, Vector3D? idealLinear, Vector3D? idealRotation, CancellationToken? cancel = null, ThrustContributionModel model = null, Action<ThrusterMap> newBestFound = null, Action<ThrusterMap> finalFound = null, DiscoverSolutionOptions<Tuple<int, int, double>> options = null)
        {
            long token = bot.Token;

            // Cache Thrusters
            Thruster[] allThrusters = bot.Thrusters;
            if (allThrusters == null || allThrusters.Length == 0)
            {
                throw new ArgumentException("This bot has no thrusters");
            }

            // Ensure model is created (if the caller wants to find solutions for several directions at the same time, it would be
            // more efficient to calculate the model once, and pass to all the solution finders)
            model = model ?? new ThrustContributionModel(bot.Thrusters, bot.PhysicsBody.CenterOfMass);

            // Figure out how much force can be generated in the ideal directions
            double maxForceLinear = idealLinear == null ? 0d : ThrustControlUtil.GetMaximumPossible_Linear(model, idealLinear.Value);
            double maxForceRotate = idealRotation == null ? 0d : ThrustControlUtil.GetMaximumPossible_Rotation(model, idealRotation.Value);

            // Mutate 2% of the elements, with a 10% value drift
            MutateUtility.MuateArgs mutateArgs = new MutateUtility.MuateArgs(false, .02, new MutateUtility.MuateFactorArgs(MutateUtility.FactorType.Distance, .1));

            #region delegates

            //NOTE: While breeding/mutating, they don't get normalized.  But error calculation and returned maps need them normalized

            var delegates = new DiscoverSolutionDelegates<Tuple<int, int, double>>()
            {
                GetNewSample = new Func<Tuple<int, int, double>[]>(() =>
                {
                    ThrusterMap map = ThrustControlUtil.GenerateRandomMap(allThrusters, token);
                    return map.Flattened;
                }),

                GetError = new Func<Tuple<int, int, double>[], SolutionError>(o =>
                {
                    ThrusterMap map = new ThrusterMap(ThrustControlUtil.Normalize(o), allThrusters, token);
                    return ThrustControlUtil.GetThrustMapScore(map, model, idealLinear, idealRotation, maxForceLinear, maxForceRotate);
                }),

                Mutate = new Func<Tuple<int, int, double>[], Tuple<int, int, double>[]>(o =>
                {
                    ThrusterMap map = new ThrusterMap(o, allThrusters, token);
                    return ThrustControlUtil.Mutate(map, mutateArgs, false).Flattened;
                }),
            };

            if (cancel != null)
            {
                delegates.Cancel = cancel.Value;
            }

            if (newBestFound != null)
            {
                delegates.NewBestFound = new Action<SolutionResult<Tuple<int, int, double>>>(o =>
                {
                    ThrusterMap map = new ThrusterMap(ThrustControlUtil.Normalize(o.Item), allThrusters, token);
                    newBestFound(map);
                });
            }

            if (finalFound != null)
            {
                delegates.FinalFound = new Action<SolutionResult<Tuple<int, int, double>>>(o =>
                {
                    ThrusterMap map = new ThrusterMap(ThrustControlUtil.Normalize(o.Item), allThrusters, token);
                    finalFound(map);
                });
            }

            #endregion

            // Do it
            //NOTE: If options.ThreadShare is set, then there's no reason to do this async, but there's no harm either
            Task.Run(() => UtilityAI.DiscoverSolution(delegates, options));
        }
 /// <summary>
 /// This scales all thrusters so that they fire as strong as possible
 /// </summary>
 /// <remarks>
 /// When generating a random mapping, the strongest thruster may only be firing at 50%.  This method would
 /// make that thruster fire at 100% (and scale all other thrusters to compensate)
 /// </remarks>
 public static ThrusterMap Normalize(ThrusterMap map)
 {
     return new ThrusterMap(Normalize(map.Flattened), map.AllThrusters, map.BotToken);
 }
        public static ThrusterMap Mutate(ThrusterMap map, MutateUtility.MuateArgs args, bool shouldNormalize = true)
        {
            var flattened = map.Flattened.ToArray();        // ensure it's a clone

            int changeCount = args.GetChangeCount(flattened.Length);
            if (changeCount == 0 && flattened.Length > 0)
            {
                changeCount = 1;
            }

            foreach (int index in UtilityCore.RandomRange(0, flattened.Length, changeCount))
            {
                // Mutate the value
                double newValue = MutateUtility.Mutate(flattened[index].Item3, args.DefaultFactor);

                // Cap it
                if (newValue < 0)
                {
                    newValue = 0;
                }
                else if (newValue > 1)
                {
                    newValue = 1;
                }

                // Store the mutated value
                flattened[index] = Tuple.Create(flattened[index].Item1, flattened[index].Item2, newValue);
            }

            if (shouldNormalize)
            {
                flattened = Normalize(flattened);
            }

            return new ThrusterMap(flattened, map.AllThrusters, map.BotToken);
        }
        public static ThrusterMap Mutate(ThrusterMap map, bool shouldNormalize = true)
        {
            // Modify 2% of the properties
            // Each property should drift up to 10% each direction (the values get capped between 0 and 100%)
            MutateUtility.MuateArgs args = new MutateUtility.MuateArgs(false, .02, new MutateUtility.MuateFactorArgs(MutateUtility.FactorType.Distance, .1));

            return Mutate(map, args);
        }