Beispiel #1
0
        private void CreateTiers()
        {
            RelevantStats.Where(bp => !CheckTiered(bp))
            .ForEach(bp => Model.AddConstraint(tieredstat[bp] == (Expression)modstat[bp]));

            foreach (var bp in RelevantStats.Where(CheckTiered))
            {
                //TODO: this can probably be optimized
                var max = CalculateUpperBound(bp);
                var pw  = XivUtils.CreateSSTiers(JobConfig.BaseCastTimes, JobConfig.CastTimeBuffs, SolverConfig.BaseStats[bp], max);
                pw.AddToModel(Model, modstat[bp], tieredstat[bp], SolverConfig.SolverSupportsSOS);
            }
        }
Beispiel #2
0
        private void CreateFoodModel()
        {
            // avoid trivial constraints
            if (!FoodChoices.Any())
            {
                return;
            }

            Model.AddConstraint(Expression.Sum(FoodChoices.Select(x => food[x])) <= 1, "use one food only");

            foreach (var itm in FoodChoices)
            {
                var fd = itm.Food;
                var fv = food[itm];

                // SIMPLIFICATION: no one cares about nq food.
                foreach (var prm in fd.Parameters)
                {
                    var bp = prm.BaseParam;
                    if (!RelevantStats.Contains(bp))
                    {
                        continue;
                    }

                    var pvals = prm.Values.Where(v => v.Type == ParameterType.Hq).ToList();

                    // easy case, these just behave like normal gear
                    // ASSUMPTION: each food provides either a fixed or relative buff for a given base param
                    foreach (var pval in pvals.OfType <ParameterValueFixed>())
                    {
                        FoodExprs.AddExprToDict(bp, pval.Amount * fv);
                    }

                    foreach (var pval in pvals.OfType <ParameterValueRelative>())
                    {
                        // add relative modifier stat[bp]
                        Model.AddConstraint(foodcap[itm, bp] <= pval.Amount * stat[bp],
                                            $"relative modifier for food {fd} in slot {bp}");

                        var limited = pval as ParameterValueRelativeLimited;
                        if (limited != null)
                        {
                            Model.AddConstraint(foodcap[itm, bp] <= limited.Maximum * fv,
                                                $"cap for relative modifier for food {fd} in slot {bp}");
                        }

                        FoodExprs.AddExprToDict(bp, foodcap[itm, bp]);
                    }
                }
            }
        }
Beispiel #3
0
        private void CreateRelicModel(EquipSlot s, Equipment e)
        {
            var gv = gear[s, e];

            RelicConfig config = null;

            SolverConfig.RelicConfigs.TryGetValue(e.ItemLevel.Key, out config);

            if (config == null || !config.Items.Contains(e.Key))
            {
                return;
            }

            var isNonSurjective = config.ConversionFunction != null;

            var statCap = config.StatCapOverrides.ContainsKey(Job) && config.StatCapOverrides[Job].ContainsKey(s)
                ? config.StatCapOverrides[Job][s]
                : config.StatCap;

            Model.AddConstraint(
                Expression.Sum(RelevantStats.Select(bp => isNonSurjective ? relicBase[s, e, bp] : cap[s, e, bp])) <=
                statCap * gv,
                $"total relic cap for {e} in slot {s}");

            foreach (var bp in RelevantStats)
            {
                var remCap = e.GetMateriaMeldCap(bp, true);
                if (remCap == 0)
                {
                    continue;
                }

                var cv = cap[s, e, bp];

                var func = config.ConversionFunction;

                if (config.ConversionOverride != null && config.ConversionOverride.ContainsKey(bp))
                {
                    func = config.ConversionOverride[bp];
                }

                if (isNonSurjective)
                {
                    func.AddToModel(Model, relicBase[s, e, bp], cv, SolverConfig.SolverSupportsSOS);
                }

                Model.AddConstraint(cv <= remCap * gv, $"upper stat cap for {bp} of relic {e} in slot {s}");
                StatExprs.AddExprToDict(bp, cv);
            }
        }
        private void CreateGearModel()
        {
            foreach (var grp in GearChoices.GroupBy(g => g.EquipSlotCategory))
            {
                // if gear is unique, equip it once only.
                grp.Where(e => e.IsUnique)
                .ForEach(
                    e =>
                    Model.AddConstraint(Expression.Sum(grp.Key.PossibleSlots.Select(s => gear[s, e])) <= 1,
                                        $"ensure gear uniqueness for {e}"));

                // SIMPLIFICATION: we ignore blocked slots
                foreach (var s in grp.Key.PossibleSlots)
                {
                    // choose at most one gear per slot
                    Model.AddConstraint(Expression.Sum(grp.Select(e => gear[s, e])) <= 1,
                                        $"choose at most one item for slot {s}");
                    foreach (var e in grp)
                    {
                        var gv = gear[s, e];
                        // ASSUMPTION: all gear choices have fixed parameters
                        e.AllParameters.Where(p => RelevantStats.Contains(p.BaseParam))
                        .ForEach(
                            p =>
                            AddExprToDict(StatExprs, p.BaseParam,
                                          p.Values.Sum(v => ((ParameterValueFixed)v).Amount) * gv));

                        // ASSUMPTION: all meldable items have at least one materia slot
                        // ASSUMPTION: customisable relics are unmeldable
                        if (e.FreeMateriaSlots > 0)
                        {
                            CreateMateriaModel(s, e);
                        }
                        else if (RelicCaps != null && RelicCaps.ContainsKey(e))
                        {
                            CreateRelicModel(s, e);
                        }
                    }
                }
            }
        }
        private void CreateRelicModel(EquipSlot s, Equipment e)
        {
            var gv   = gear[s, e];
            var eCap = RelicCaps[e];

            Model.AddConstraint(Expression.Sum(RelevantStats.Select(bp => cap[s, e, bp])) <= eCap * gv,
                                $"total relic cap for {e} in slot {s}");
            foreach (var bp in RelevantStats)
            {
                var remCap = e.GetMateriaMeldCap(bp, true);
                if (remCap == 0)
                {
                    continue;
                }

                var cv = cap[s, e, bp];

                Model.AddConstraint(cv <= remCap * gv, $"upper stat cap for {bp} of relic {e} in slot {s}");
                AddExprToDict(StatExprs, bp, cv);
                // SIMPLIFICATION: impossible-to-reach stat values are ignored. Can be handled by using Model.AddAlternativeConstraint(cv <= badVal - 1, cv >= badVal +1, bigM)
            }
        }
Beispiel #6
0
        /// <summary>
        ///     Creates a new BiS solver model.
        /// </summary>
        /// <param name="solverConfig">Solver configuration</param>
        /// <param name="job">Job to solve for</param>
        /// <param name="gearChoices">Gear items to choose from. Keep this small.</param>
        /// <param name="foodChoices">List of food choices. This can be fairly large, it doesn't add much complexity.</param>
        /// <param name="materiaChoices">
        ///     Dictionary of materia choices; set value to true if the materia is allowed for advanced
        ///     melding. The materia with the highest eligible stat value is chosen. (Note that Tier is 0-indexed)
        /// </param>
        //TODO: this is getting out of hand. need config object asap.
        //TODO: make model parts pluggable if possible
        public BisModel(SolverConfig solverConfig, ClassJob job,
                        IEnumerable <Equipment> gearChoices, IEnumerable <FoodItem> foodChoices,
                        IDictionary <MateriaItem, bool> materiaChoices)
        {
            Model = new Model();

            SolverConfig = solverConfig;
            Job          = job;
            JobConfig    = SolverConfig.JobConfigs[Job];

            GearChoices = gearChoices.ToList();
            FoodChoices = foodChoices.ToList();

            // collect stats we care about
            RelevantStats = JobConfig.Weights.Keys.Union(JobConfig.StatRequirements.Keys).ToList();


            // we don't care about materia which affect unneeded stats
            MateriaChoices = materiaChoices.Where(m => RelevantStats.Contains(m.Key.BaseParam))
                             .ToDictionary(k => k.Key, k => k.Value);

            var allEquipSlots = GearChoices.SelectMany(g => g.EquipSlotCategory.PossibleSlots).ToList();

            gear = new VariableCollection <EquipSlot, Equipment>(Model, allEquipSlots, GearChoices,
                                                                 type: VariableType.Binary,
                                                                 debugNameGenerator: (s, e) => new StringBuilder().AppendFormat("{0}_{1}", s, e),
                                                                 lowerBoundGenerator: (s, e) => CheckRequired(e) ? 1 : 0);
            food = new VariableCollection <FoodItem>(Model, FoodChoices,
                                                     type: VariableType.Binary,
                                                     debugNameGenerator: e => new StringBuilder().AppendFormat("{0}", e.Item),
                                                     lowerBoundGenerator: i => CheckRequired(i.Item) ? 1 : 0);
            foodcap = new VariableCollection <FoodItem, BaseParam>(Model, FoodChoices, RelevantStats,
                                                                   type: VariableType.Integer,
                                                                   debugNameGenerator: (i, bp) => new StringBuilder().AppendFormat("{0}_{1}_cap", i.Item, bp),
                                                                   lowerBoundGenerator: (x, b) => 0);
            materia = new VariableCollection <EquipSlot, Equipment, MateriaItem>(Model, allEquipSlots, GearChoices, MateriaChoices.Keys,
                                                                                 type: VariableType.Integer,
                                                                                 debugNameGenerator: (s, e, m) => new StringBuilder().AppendFormat("{2}_{0}_{1}", s, e, m),
                                                                                 lowerBoundGenerator: (s, e, bp) => 0,
                                                                                 upperBoundGenerator: (s, e, b) => e.TotalMateriaSlots());
            cap = new VariableCollection <EquipSlot, Equipment, BaseParam>(Model, allEquipSlots, GearChoices, RelevantStats,
                                                                           type: VariableType.Integer,
                                                                           debugNameGenerator: (s, e, bp) => new StringBuilder().AppendFormat("{2}_cap_{0}_{1}", s, e, bp),
                                                                           lowerBoundGenerator: (s, e, b) => 0);
            relicBase = new VariableCollection <EquipSlot, Equipment, BaseParam>(Model, allEquipSlots, GearChoices, RelevantStats,
                                                                                 type: VariableType.Integer,
                                                                                 debugNameGenerator: (s, e, bp) => new StringBuilder().AppendFormat("{2}_relic_base_{0}_{1}", s, e, bp),
                                                                                 lowerBoundGenerator: (s, e, b) => 0);

            stat = new VariableCollection <BaseParam>(Model, RelevantStats,
                                                      type: VariableType.Integer,
                                                      debugNameGenerator: bp => new StringBuilder().AppendFormat("gear_materia__{0}", bp),
                                                      lowerBoundGenerator: x => 0);
            modstat = new VariableCollection <BaseParam>(Model, RelevantStats,
                                                         type: VariableType.Integer,
                                                         debugNameGenerator: bp => new StringBuilder().AppendFormat("added_food_{0}", bp),
                                                         lowerBoundGenerator: x => 0);
            allocstat = new VariableCollection <BaseParam>(Model, RelevantStats,
                                                           type: VariableType.Integer,
                                                           debugNameGenerator: bp => new StringBuilder().AppendFormat("allocated_{0}", bp),
                                                           lowerBoundGenerator: x => 0, upperBoundGenerator: x => SolverConfig.AllocatedStatsCap);
            tieredstat = new VariableCollection <BaseParam>(Model, RelevantStats,
                                                            type: VariableType.Integer,
                                                            debugNameGenerator: bp => new StringBuilder().AppendFormat("tiered_{0}", bp),
                                                            lowerBoundGenerator: x => 0);

            Model.AddConstraint(
                Expression.Sum(RelevantStats.Where(bp => MainStats.Contains(bp.Name)).Select(bp => allocstat[bp])) <=
                SolverConfig.AllocatedStatsCap,
                "cap allocatable stats");

            StatExprs = RelevantStats.ToDictionary(bp => bp, bp => Expression.EmptyExpression);
            FoodExprs = RelevantStats.ToDictionary(bp => bp, bp => (Expression)stat[bp]);
            SolverConfig.BaseStats.ForEach(kv => StatExprs[kv.Key] = kv.Value + Expression.EmptyExpression);

            CreateGearModel();

            CreateMateriaOrdering();

            CreateFoodModel();

            CreateTiers();

            CreateObjective();
        }
Beispiel #7
0
        private void CreateGearModel()
        {
            foreach (var grp in GearChoices.GroupBy(g => g.EquipSlotCategory))
            {
                // if gear is unique, equip it once only.
                if (grp.Key.PossibleSlots.Count() > 1)
                {
                    grp.Where(e => e.IsUnique)
                    .ForEach(
                        e =>
                        Model.AddConstraint(Expression.Sum(grp.Key.PossibleSlots.Select(s => gear[s, e])) <= 1,
                                            $"ensure gear uniqueness for {e}"));
                }

                // SIMPLIFICATION: we ignore blocked slots
                foreach (var s in grp.Key.PossibleSlots)
                {
                    // choose at most one gear per slot
                    Model.AddConstraint(Expression.Sum(grp.Select(e => gear[s, e])) <= 1,
                                        $"choose at most one item for slot {s}");
                    foreach (var e in grp)
                    {
                        var gv = gear[s, e];

                        // ASSUMPTION: all gear choices have fixed parameters
                        var stats =
                            e.AllParameters.Where(p => RelevantStats.Contains(p.BaseParam))
                            .ToDictionary(p => p.BaseParam,
                                          p => p.Values.OfType <ParameterValueFixed>().Select(val => val.Amount).Sum());

                        if (SolverConfig.EquipmentOverrides?.ContainsKey(e) ?? false)
                        {
                            var statsOverride = SolverConfig.EquipmentOverrides[e];

                            if (statsOverride == null)
                            {
                                continue;
                            }

                            foreach (var kv in statsOverride)
                            {
                                stats[kv.Key] = kv.Value;
                            }
                        }

                        // sanity check stats
                        foreach (var kv in stats)
                        {
                            if (e.GetMaximumParamValue(kv.Key) < kv.Value)
                            {
                                Console.Error.WriteLine($"{kv.Key} => {kv.Value} for {e} is out of range");
                            }
                        }

                        stats.ForEach(p => StatExprs.AddExprToDict(p.Key, p.Value * gv));

                        // ASSUMPTION: all meldable items have at least one materia slot
                        // ASSUMPTION: customisable relics are unmeldable
                        if (e.FreeMateriaSlots > 0)
                        {
                            CreateMateriaModel(s, e);
                        }
                        else
                        {
                            CreateRelicModel(s, e);
                        }
                    }
                }
            }
        }
        /// <summary>
        ///     Creates a new BiS solver model.
        /// </summary>
        /// <param name="weights">Stat weights. The higher the weight, the more a stat is desirable</param>
        /// <param name="statReqs">Minimum amount of stats that must be present in a solution</param>
        /// <param name="baseStats">Stats of a character without any gear</param>
        /// <param name="gearChoices">Gear items to choose from. Keep this small.</param>
        /// <param name="foodChoices">List of food choices. This can be fairly large, it doesn't add much complexity.</param>
        /// <param name="materiaChoices">
        ///     Dictionary of materia choices; set value to true if the materia is allowed for advanced
        ///     melding. The materia with the highest eligible stat value is chosen. (Note that Tier is 0-indexed)
        /// </param>
        /// <param name="relicCaps">Designates customizable relics. Value of an entry determines the total stat cap.</param>
        /// <param name="overmeldThreshold">
        ///     Extend the overmelding threshold --- i.e. if you set overmeldThreshold to n, materia
        ///     from materiaChoices that isn't normally allowed in advanced melds can be used up to n times in advanced meld
        /// </param>
        /// <param name="allocStatCap">Cap for allocatable stats. Default is 35</param>
        /// <param name="maximizeUnweightedValues">Maximize unweighted values with a small weight (1e-5)</param>
        //TODO: this is getting out of hand. need config object asap.
        //TODO: make model parts pluggable if possible
        public BisModel(IDictionary <BaseParam, double> weights, IDictionary <BaseParam, int> statReqs,
                        IDictionary <BaseParam, int> baseStats, IEnumerable <Equipment> gearChoices, IEnumerable <FoodItem> foodChoices,
                        IDictionary <MateriaItem, bool> materiaChoices, IDictionary <Equipment, int> relicCaps = null,
                        int overmeldThreshold = 0, int allocStatCap = 35, bool maximizeUnweightedValues = true)
        {
            Model = new Model();

            StatRequirements         = statReqs;
            Weights                  = weights;
            GearChoices              = gearChoices.ToList();
            FoodChoices              = foodChoices.ToList();
            RelicCaps                = relicCaps;
            OvermeldThreshold        = overmeldThreshold;
            MaximizeUnweightedValues = maximizeUnweightedValues;

            // collect stats we care about
            RelevantStats = Weights.Keys.Union(StatRequirements.Keys).ToList();


            // we don't care about materia which affect unneeded stats
            MateriaChoices = materiaChoices.Where(m => RelevantStats.Contains(m.Key.BaseParam))
                             .ToDictionary(k => k.Key, k => k.Value);

            var allEquipSlots = GearChoices.SelectMany(g => g.EquipSlotCategory.PossibleSlots).ToList();

            gear = new VariableCollection <EquipSlot, Equipment>(Model, allEquipSlots, GearChoices,
                                                                 type: VariableType.Binary);
            food    = new VariableCollection <FoodItem>(Model, FoodChoices, type: VariableType.Binary);
            foodcap = new VariableCollection <FoodItem, BaseParam>(Model, FoodChoices, RelevantStats,
                                                                   type: VariableType.Integer,
                                                                   lowerBoundGenerator: (x, b) => 0);
            materia = new VariableCollection <EquipSlot, Equipment, MateriaItem>(Model, allEquipSlots, GearChoices,
                                                                                 MateriaChoices.Keys,
                                                                                 type: VariableType.Integer, lowerBoundGenerator: (s, e, bp) => 0);
            cap = new VariableCollection <EquipSlot, Equipment, BaseParam>(Model, allEquipSlots, GearChoices,
                                                                           RelevantStats, type: VariableType.Integer, lowerBoundGenerator: (s, e, b) => 0);

            stat = new VariableCollection <BaseParam>(Model, RelevantStats, type: VariableType.Integer,
                                                      lowerBoundGenerator: x => 0);
            modstat = new VariableCollection <BaseParam>(Model, RelevantStats, type: VariableType.Integer,
                                                         lowerBoundGenerator: x => 0);
            allocstat = new VariableCollection <BaseParam>(Model, RelevantStats, type: VariableType.Integer,
                                                           lowerBoundGenerator: x => 0);

            Model.AddConstraint(
                Expression.Sum(RelevantStats.Where(bp => MainStats.Contains(bp.Name)).Select(bp => allocstat[bp])) <=
                allocStatCap,
                "cap allocatable stats");

            StatExprs = RelevantStats.ToDictionary(bp => bp, bp => Expression.EmptyExpression);
            FoodExprs = RelevantStats.ToDictionary(bp => bp, bp => (Expression)stat[bp]);
            baseStats.ForEach(kv => StatExprs[kv.Key] = kv.Value + Expression.EmptyExpression);

            var bigM = 50 *
                       GearChoices.Select(
                g =>
                g.AllParameters.Select(
                    p => p.Values.OfType <ParameterValueFixed>().Select(v => v.Amount).Max())
                .Max()).Max();

            CreateGearModel();

            CreateFoodModel();

            CreateObjective();
        }