Example #1
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();
        }
        /// <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();
        }