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