Beispiel #1
0
        public override void Compute(Project project, ErrorCollector warnings)
        {
            if (this.project == null)
            {
                this.project              = project;
                project.settings.changed += ProjectSettingsChanged;
            }

            var time            = Stopwatch.StartNew();
            var result          = Database.objects.CreateMapping <ulong>();
            var processing      = Database.objects.CreateMapping <ProcessingFlags>();
            var processingQueue = new Queue <FactorioId>();

            foreach (var rootAccessbile in Database.rootAccessible)
            {
                result[rootAccessbile] = 1;
                processingQueue.Enqueue(rootAccessbile.id);
                processing[rootAccessbile] = ProcessingFlags.Initial | ProcessingFlags.InQueue;
            }

            foreach (var(obj, flag) in project.settings.itemFlags)
            {
                if (flag.HasFlags(ProjectPerItemFlags.MarkedAccessible))
                {
                    result[obj] = 1;
                    processingQueue.Enqueue(obj.id);
                    processing[obj] = ProcessingFlags.Initial | ProcessingFlags.InQueue;
                }
                else if (flag.HasFlag(ProjectPerItemFlags.MarkedInaccessible))
                {
                    processing[obj] = ProcessingFlags.ForceInaccessible;
                }
            }

            var needAutoMilestones = project.settings.milestones.Count == 0;

            if (needAutoMilestones)
            {
                // Adding default milestones AND special flag to auto-order them
                currentMilestones = new FactorioObject[Database.allSciencePacks.Length];
                foreach (var milestone in Database.allSciencePacks)
                {
                    processing[milestone] |= ProcessingFlags.MilestoneNeedOrdering;
                }
            }
            else
            {
                currentMilestones = project.settings.milestones.ToArray();
                for (var i = 0; i < currentMilestones.Length; i++)
                {
                    result[currentMilestones[i]] = (1ul << (i + 1)) | 1;
                }
            }

            var dependencyList      = Dependencies.dependencyList;
            var reverseDependencies = Dependencies.reverseDependencies;
            List <FactorioObject> milestonesNotReachable = null;

            var nextMilestoneMask  = 0x2ul;
            var nextMilestoneIndex = 0;
            var accessibleObjects  = 0;

            var flagMask = 0ul;

            for (var i = 0; i <= currentMilestones.Length; i++)
            {
                flagMask |= 1ul << i;
                if (i > 0)
                {
                    var milestone = currentMilestones[i - 1];
                    if (milestone == null)
                    {
                        milestonesNotReachable = new List <FactorioObject>();
                        foreach (var pack in Database.allSciencePacks)
                        {
                            if (Array.IndexOf(currentMilestones, pack) == -1)
                            {
                                currentMilestones[nextMilestoneIndex++] = pack;
                                milestonesNotReachable.Add(pack);
                            }
                        }
                        Array.Resize(ref currentMilestones, nextMilestoneIndex);
                        break;
                    }
                    Console.WriteLine("Processing milestone " + milestone.locName);
                    processingQueue.Enqueue(milestone.id);
                    processing[milestone] = ProcessingFlags.Initial | ProcessingFlags.InQueue;
                }

                while (processingQueue.Count > 0)
                {
                    var elem  = processingQueue.Dequeue();
                    var entry = dependencyList[elem];

                    var cur       = result[elem];
                    var eflags    = cur;
                    var isInitial = (processing[elem] & ProcessingFlags.Initial) != 0;
                    processing[elem] &= ProcessingFlags.MilestoneNeedOrdering;

                    foreach (var list in entry)
                    {
                        if ((list.flags & DependencyList.Flags.RequireEverything) != 0)
                        {
                            foreach (var req in list.elements)
                            {
                                var reqFlags = result[req];
                                if (reqFlags == 0 && !isInitial)
                                {
                                    goto skip;
                                }
                                eflags |= result[req];
                            }
                        }
                        else
                        {
                            var groupFlags = 0ul;
                            foreach (var req in list.elements)
                            {
                                var acc = result[req];
                                if (acc == 0)
                                {
                                    continue;
                                }
                                if (acc < groupFlags || groupFlags == 0ul)
                                {
                                    groupFlags = acc;
                                }
                            }

                            if (groupFlags == 0 && !isInitial)
                            {
                                goto skip;
                            }
                            eflags |= groupFlags;
                        }
                    }
                    if (!isInitial)
                    {
                        if (eflags == cur || (eflags | flagMask) != flagMask)
                        {
                            continue;
                        }
                    }
                    else
                    {
                        eflags &= flagMask;
                    }

                    accessibleObjects++;
                    //var obj = Database.objects[elem];
                    //Console.WriteLine("Added object "+obj.locName+" ["+obj.GetType().Name+"] with mask "+eflags.ToString("X") + " (was "+cur.ToString("X")+")");

                    if (processing[elem] == ProcessingFlags.MilestoneNeedOrdering)
                    {
                        processing[elem]    = 0;
                        eflags             |= nextMilestoneMask;
                        nextMilestoneMask <<= 1;
                        currentMilestones[nextMilestoneIndex++] = Database.objects[elem];
                    }

                    result[elem] = eflags;
                    foreach (var revdep in reverseDependencies[elem])
                    {
                        if ((processing[revdep] & ~ProcessingFlags.MilestoneNeedOrdering) != 0 || result[revdep] != 0)
                        {
                            continue;
                        }
                        processing[revdep] |= ProcessingFlags.InQueue;
                        processingQueue.Enqueue(revdep);
                    }

                    skip :;
                }
            }

            if (needAutoMilestones)
            {
                project.settings.milestones.Clear();
                project.settings.milestones.AddRange(currentMilestones);
            }
            GetLockedMaskFromProject();

            var hasAutomatableRocketLaunch = result[Database.objectsByTypeName["Special.launch"]] != 0;

            if (accessibleObjects < Database.objects.count / 2)
            {
                warnings.Error("More than 50% of all in-game objects appear to be inaccessible in this project with your current mod list. This can have a variety of reasons like objects being accessible via scripts," +
                               MaybeBug + MilestoneAnalysisIsImportant + UseDependencyExplorer, ErrorSeverity.AnalysisWarning);
            }
            else if (!hasAutomatableRocketLaunch)
            {
                warnings.Error("Rocket launch appear to be inaccessible. This means that rocket may not be launched in this mod pack, or it requires mod script to spawn or unlock some items," +
                               MaybeBug + MilestoneAnalysisIsImportant + UseDependencyExplorer, ErrorSeverity.AnalysisWarning);
            }
            else if (milestonesNotReachable != null)
            {
                warnings.Error("There are some milestones that are not accessible: " + string.Join(", ", milestonesNotReachable.Select(x => x.locName)) + ". You may remove these from milestone list," +
                               MaybeBug + MilestoneAnalysisIsImportant + UseDependencyExplorer, ErrorSeverity.AnalysisWarning);
            }
            Console.WriteLine("Milestones calculation finished in " + time.ElapsedMilliseconds + " ms.");
            milestoneResult = result;
        }
Beispiel #2
0
        public override void Compute(Project project, ErrorCollector warnings)
        {
            var time  = Stopwatch.StartNew();
            var state = Database.objects.CreateMapping <ProcessingState>();

            state[Database.voidEnergy] = ProcessingState.Automatable;
            var processingQueue = new Queue <FactorioId>(Database.objects.count);
            var unknowns        = 0;

            foreach (var recipe in Database.recipes.all)
            {
                var hasAutomatableCrafter = false;
                foreach (var crafter in recipe.crafters)
                {
                    if (crafter != Database.character && crafter.IsAccessible())
                    {
                        hasAutomatableCrafter = true;
                    }
                }
                if (!hasAutomatableCrafter)
                {
                    state[recipe] = ProcessingState.NotAutomatable;
                }
            }

            foreach (var obj in Database.objects.all)
            {
                if (!obj.IsAccessible())
                {
                    state[obj] = ProcessingState.NotAutomatable;
                }
                else if (state[obj] == ProcessingState.Unknown)
                {
                    unknowns++;
                    state[obj] = ProcessingState.UnknownInQueue;
                    processingQueue.Enqueue(obj.id);
                }
            }

            while (processingQueue.Count > 0)
            {
                var index           = processingQueue.Dequeue();
                var dependencies    = Dependencies.dependencyList[index];
                var automationState = ProcessingState.Automatable;
                foreach (var depGroup in dependencies)
                {
                    if (depGroup.flags.HasFlags(DependencyList.Flags.OneTimeInvestment))
                    {
                        continue;
                    }
                    if (depGroup.flags.HasFlag(DependencyList.Flags.RequireEverything))
                    {
                        foreach (var element in depGroup.elements)
                        {
                            if (state[element] < automationState)
                            {
                                automationState = state[element];
                            }
                        }
                    }
                    else
                    {
                        var localHighest = ProcessingState.NotAutomatable;
                        foreach (var element in depGroup.elements)
                        {
                            if (state[element] > localHighest)
                            {
                                localHighest = state[element];
                            }
                        }

                        if (localHighest < automationState)
                        {
                            automationState = localHighest;
                        }
                    }
                }

                if (automationState == ProcessingState.UnknownInQueue)
                {
                    automationState = ProcessingState.Unknown;
                }

                state[index] = automationState;
                if (automationState != ProcessingState.Unknown)
                {
                    unknowns--;
                    foreach (var revDep in Dependencies.reverseDependencies[index])
                    {
                        if (state[revDep] == ProcessingState.Unknown)
                        {
                            processingQueue.Enqueue(revDep);
                            state[revDep] = ProcessingState.UnknownInQueue;
                        }
                    }
                }
            }
            state[Database.voidEnergy] = ProcessingState.NotAutomatable;

            Console.WriteLine("Automation analysis (first pass) finished in " + time.ElapsedMilliseconds + " ms. Unknowns left: " + unknowns);
            if (unknowns > 0)
            {
                // TODO run graph analysis if there are any unknowns left... Right now assume they are not automatable
            }
            automatable = state.Remap((_, s) => s == ProcessingState.Automatable);
        }
Beispiel #3
0
 public DeserializationContext(ErrorCollector errorCollector)
 {
     collector = errorCollector;
 }
        public override void Compute(Project project, ErrorCollector warnings)
        {
            var sciencePacks     = Database.allSciencePacks;
            var sciencePackIndex = Database.goods.CreateMapping <int>();

            for (var i = 0; i < sciencePacks.Length; i++)
            {
                sciencePackIndex[sciencePacks[i]] = i;
            }
            var sciencePackCount = new Mapping <Technology, float> [sciencePacks.Length];

            for (var i = 0; i < sciencePacks.Length; i++)
            {
                sciencePackCount[i] = Database.technologies.CreateMapping <float>();
            }

            var processing     = Database.technologies.CreateMapping <bool>();
            var requirementMap = Database.technologies.CreateMapping <Technology, bool>(Database.technologies);

            var queue = new Queue <Technology>();

            foreach (var tech in Database.technologies.all)
            {
                if (tech.prerequisites.Length == 0)
                {
                    processing[tech] = true;
                    queue.Enqueue(tech);
                }
            }
            var prerequisiteQueue = new Queue <Technology>();

            while (queue.Count > 0)
            {
                var current = queue.Dequeue();

                // Fast processing for the first prerequisite (just copy everything)
                if (current.prerequisites.Length > 0)
                {
                    var firstRequirement = current.prerequisites[0];
                    foreach (var pack in sciencePackCount)
                    {
                        pack[current] += pack[firstRequirement];
                    }
                    requirementMap.CopyRow(firstRequirement, current);
                }

                requirementMap[current, current] = true;
                prerequisiteQueue.Enqueue(current);

                while (prerequisiteQueue.Count > 0)
                {
                    var prerequisite = prerequisiteQueue.Dequeue();
                    foreach (var ingredient in prerequisite.ingredients)
                    {
                        var science = sciencePackIndex[ingredient.goods];
                        sciencePackCount[science][current] += ingredient.amount * prerequisite.count;
                    }

                    foreach (var prerequisitePrerequisite in prerequisite.prerequisites)
                    {
                        if (!requirementMap[current, prerequisitePrerequisite])
                        {
                            prerequisiteQueue.Enqueue(prerequisitePrerequisite);
                            requirementMap[current, prerequisitePrerequisite] = true;
                        }
                    }
                }

                foreach (var unlocks in Dependencies.reverseDependencies[current])
                {
                    if (Database.objects[unlocks] is Technology tech && !processing[tech])
                    {
                        foreach (var techPreq in tech.prerequisites)
                        {
                            if (!processing[techPreq])
                            {
                                goto locked;
                            }
                        }

                        processing[tech] = true;
                        queue.Enqueue(tech);

                        locked :;
                    }
                }
            }

            allSciencePacks = Database.technologies.CreateMapping(tech => sciencePackCount.Select((x, id) => x[tech] == 0 ? null : new Ingredient(sciencePacks[id], x[tech])).Where(x => x != null).ToArray());
        }
Beispiel #5
0
 public abstract void Compute(Project project, ErrorCollector warnings);
Beispiel #6
0
        public override void Compute(Project project, ErrorCollector warnings)
        {
            var solver    = DataUtils.CreateSolver("WorkspaceSolver");
            var objective = solver.Objective();

            objective.SetMaximization();
            var time = Stopwatch.StartNew();

            var variables   = Database.goods.CreateMapping <Variable>();
            var constraints = Database.recipes.CreateMapping <Constraint>();

            var sciencePackUsage = new Dictionary <Goods, float>();

            foreach (var technology in Database.technologies.all)
            {
                if (technology.IsAccessible())
                {
                    foreach (var ingredient in technology.ingredients)
                    {
                        if (ingredient.goods.IsAutomatable())
                        {
                            sciencePackUsage.TryGetValue(ingredient.goods, out var prev);
                            sciencePackUsage[ingredient.goods] = prev + ingredient.amount * technology.count;
                        }
                    }
                }
            }


            foreach (var goods in Database.goods.all)
            {
                if (!goods.IsAutomatable())
                {
                    continue;
                }
                var mapGeneratedAmount = 0f;
                foreach (var src in goods.miscSources)
                {
                    if (src is Entity ent && ent.mapGenerated)
                    {
                        foreach (var product in ent.loot)
                        {
                            if (product.goods == goods)
                            {
                                mapGeneratedAmount += product.amount;
                            }
                        }
                    }
                }
                var variable     = solver.MakeVar(CostLowerLimit, CostLimitWhenGeneratesOnMap / mapGeneratedAmount, false, goods.name);
                var baseItemCost = (goods.usages.Length + 1) * 0.01f;
                if (goods is Item item && (item.factorioType != "item" || item.placeResult != null))
                {
                    baseItemCost += 0.1f;
                }
                if (goods.fuelValue > 0f)
                {
                    baseItemCost += goods.fuelValue * 0.0001f;
                }
                objective.SetCoefficient(variable, baseItemCost);
                variables[goods] = variable;
            }

            foreach (var(item, count) in sciencePackUsage)
            {
                objective.SetCoefficient(variables[item], count / 1000f);
            }

            var export = Database.objects.CreateMapping <float>();
            var recipeProductionCost = Database.recipesAndTechnologies.CreateMapping <float>();

            recipeCost = Database.recipes.CreateMapping <float>();
            flow       = Database.objects.CreateMapping <float>();
            var lastVariable = Database.goods.CreateMapping <Variable>();

            foreach (var recipe in Database.recipes.all)
            {
                if (!recipe.IsAutomatable())
                {
                    continue;
                }
                var logisticsCost = (CostPerIngredient * recipe.ingredients.Length + CostPerProduct * recipe.products.Length + CostPerSecond) * recipe.time;

                // TODO incorporate fuel selection. Now just select fuel if it only uses 1 fuel
                Goods singleUsedFuel       = null;
                var   singleUsedFuelAmount = 0f;
                var   minEmissions         = 100f;
                foreach (var crafter in recipe.crafters)
                {
                    minEmissions = MathF.Min(crafter.energy.emissions, minEmissions);
                    if (crafter.energy.usesHeat)
                    {
                        break;
                    }
                    foreach (var fuel in crafter.energy.fuels)
                    {
                        if (!fuel.IsAutomatable())
                        {
                            continue;
                        }
                        var amount = (recipe.time * crafter.power) / (crafter.energy.effectivity * fuel.fuelValue);
                        if (singleUsedFuel == null)
                        {
                            singleUsedFuel       = fuel;
                            singleUsedFuelAmount = amount;
                        }
                        else if (singleUsedFuel == fuel)
                        {
                            singleUsedFuelAmount = MathF.Min(singleUsedFuelAmount, amount);
                        }
                        else
                        {
                            singleUsedFuel = null;
                            break;
                        }
                    }
                    if (singleUsedFuel == null)
                    {
                        break;
                    }
                }

                if (singleUsedFuel == Database.electricity || singleUsedFuel == Database.voidEnergy)
                {
                    singleUsedFuel = null;
                }

                var constraint = solver.MakeConstraint(double.NegativeInfinity, 0, recipe.name);
                constraints[recipe] = constraint;

                foreach (var product in recipe.products)
                {
                    var var    = variables[product.goods];
                    var amount = product.amount;
                    constraint.SetCoefficientCheck(var, amount, ref lastVariable[product.goods]);
                    if (product.goods is Item)
                    {
                        logisticsCost += amount * CostPerItem;
                    }
                    else if (product.goods is Fluid)
                    {
                        logisticsCost += amount * CostPerFluid;
                    }
                }

                if (singleUsedFuel != null)
                {
                    var var = variables[singleUsedFuel];
                    constraint.SetCoefficientCheck(var, -singleUsedFuelAmount, ref lastVariable[singleUsedFuel]);
                }

                foreach (var ingredient in recipe.ingredients)
                {
                    var var = variables[ingredient.goods];
                    constraint.SetCoefficientCheck(var, -ingredient.amount, ref lastVariable[ingredient.goods]);
                    if (ingredient.goods is Item)
                    {
                        logisticsCost += ingredient.amount * CostPerItem;
                    }
                    else if (ingredient.goods is Fluid)
                    {
                        logisticsCost += ingredient.amount * CostPerFluid;
                    }
                }

                if (recipe.sourceEntity != null && recipe.sourceEntity.mapGenerated)
                {
                    var totalMining = 0f;
                    foreach (var product in recipe.products)
                    {
                        totalMining += product.amount;
                    }
                    var miningPenalty = MiningPenalty;
                    var totalDensity  = recipe.sourceEntity.mapGenDensity / totalMining;
                    if (totalDensity < MiningMaxDensityForPenalty)
                    {
                        var extraPenalty = MathF.Log(MiningMaxDensityForPenalty / totalDensity);
                        miningPenalty += Math.Min(extraPenalty, MiningMaxExtraPenaltyForRarity);
                    }

                    logisticsCost *= miningPenalty;
                }

                if (minEmissions >= 0f)
                {
                    logisticsCost += minEmissions * CostPerPollution * recipe.time;
                }
                else
                {
                    logisticsCost = MathF.Max(logisticsCost * 0.5f, logisticsCost + minEmissions * CostPerPollution * recipe.time);  // only allow cut logistics cost by half with negative emissions
                }
                constraint.SetUb(logisticsCost);
                export[recipe]     = logisticsCost;
                recipeCost[recipe] = logisticsCost;
            }

            // TODO this is temporary fix for strange item sources
            foreach (var goods in Database.goods.all)
            {
                if (goods is Item item && item.IsAutomatable())
                {
                    foreach (var source in item.miscSources)
                    {
                        if (source is Goods g && g.IsAutomatable())
                        {
                            var constraint = solver.MakeConstraint(double.NegativeInfinity, 0, "source-" + item.locName);
                            constraint.SetCoefficient(variables[g], -1);
                            constraint.SetCoefficient(variables[item], 1);
                        }
                    }
                }
            }

            var result = solver.TrySolvewithDifferentSeeds();

            Console.WriteLine("Cost analysis completed in " + time.ElapsedMilliseconds + " ms. with result " + result);
            var sumImportance = 1f;
            var totalRecipes  = 0;

            if (result == Solver.ResultStatus.OPTIMAL || result == Solver.ResultStatus.FEASIBLE)
            {
                var objectiveValue = (float)objective.Value();
                Console.WriteLine("Estimated modpack cost: " + DataUtils.FormatAmount(objectiveValue * 1000f, UnitOfMeasure.None));
                foreach (var g in Database.goods.all)
                {
                    if (!g.IsAutomatable())
                    {
                        continue;
                    }
                    var value = (float)variables[g].SolutionValue();
                    export[g] = value;
                }

                foreach (var recipe in Database.recipes.all)
                {
                    var productCost = 0f;
                    if (!recipe.IsAutomatable())
                    {
                        continue;
                    }
                    var recipeFlow = (float)constraints[recipe].DualValue();
                    if (recipeFlow > 0f)
                    {
                        totalRecipes++;
                        sumImportance += recipeFlow;
                        flow[recipe]   = recipeFlow;
                        foreach (var product in recipe.products)
                        {
                            flow[product.goods] += recipeFlow * product.amount;
                        }
                    }
                }

                flowRecipeScaleCoef = (1e2f * totalRecipes) / (sumImportance * MathF.Sqrt(MathF.Sqrt(objectiveValue)));
            }
            foreach (var o in Database.objects.all)
            {
                if (!o.IsAutomatable())
                {
                    export[o] = float.PositiveInfinity;
                    continue;
                }

                if (o is RecipeOrTechnology recipe)
                {
                    foreach (var ingredient in recipe.ingredients)
                    {
                        export[o] += export[ingredient.goods] * ingredient.amount;
                    }
                    foreach (var product in recipe.products)
                    {
                        recipeProductionCost[recipe] += product.amount * export[product.goods];
                    }
                }
                else if (o is Entity entity)
                {
                    var minimal = float.PositiveInfinity;
                    foreach (var item in entity.itemsToPlace)
                    {
                        if (export[item] < minimal)
                        {
                            minimal = export[item];
                        }
                    }
                    export[o] = minimal;
                }
            }
            cost = export;
            recipeProductCost = recipeProductionCost;

            recipeWastePercentage = Database.recipes.CreateMapping <float>();
            if (result == Solver.ResultStatus.OPTIMAL || result == Solver.ResultStatus.FEASIBLE)
            {
                foreach (var(recipe, constraint) in constraints)
                {
                    if (constraint == null)
                    {
                        continue;
                    }
                    var productCost = 0f;
                    foreach (var product in recipe.products)
                    {
                        productCost += product.amount * product.goods.Cost();
                    }
                    recipeWastePercentage[recipe] = 1f - productCost / cost[recipe];
                }
            }
            else
            {
                warnings.Error("Cost analysis was unable to process this modpack. This may mean YAFC bug.", ErrorSeverity.AnalysisWarning);
            }

            importantItems = Database.goods.all.Where(x => x.usages.Length > 1).OrderByDescending(x => flow[x] * cost[x] * x.usages.Count(y => y.IsAutomatable() && recipeWastePercentage[y] == 0f)).ToArray();

            solver.Dispose();
        }
Beispiel #7
0
        public static T Copy <T>(T obj, ModelObject newOwner, ErrorCollector collector) where T : ModelObject
        {
            var ms = SaveToJson(obj);

            return(LoadFromJson <T>(ms.GetBuffer(), newOwner, collector, (int)ms.Length));
        }
Beispiel #8
0
        public static T LoadFromJson <T>(ReadOnlySpan <byte> buffer, ModelObject owner, ErrorCollector collector) where T : ModelObject
        {
            var reader = new Utf8JsonReader(buffer);

            reader.Read();
            var context = new DeserializationContext(collector);
            var result  = SerializationMap <T> .DeserializeFromJson(owner, ref reader, context);

            context.Notify();
            return(result);
        }
Beispiel #9
0
 public static T Copy <T>(T obj, ModelObject newOwner, ErrorCollector collector) where T : ModelObject
 {
     using (var ms = SaveToJson(obj))
         return(LoadFromJson <T>(new ReadOnlySpan <byte>(ms.GetBuffer(), 0, (int)ms.Length), newOwner, collector));
 }
Beispiel #10
0
        public override void Compute(Project project, ErrorCollector warnings)
        {
            var solver    = DataUtils.CreateSolver("WorkspaceSolver");
            var objective = solver.Objective();

            objective.SetMaximization();
            var time = Stopwatch.StartNew();

            var variables   = Database.goods.CreateMapping <Variable>();
            var constraints = Database.recipes.CreateMapping <Constraint>();

            var sciencePackUsage = new Dictionary <Goods, float>();

            foreach (var technology in Database.technologies.all)
            {
                if (technology.IsAccessible())
                {
                    foreach (var ingredient in technology.ingredients)
                    {
                        if (ingredient.goods.IsAutomatable())
                        {
                            if (onlyCurrentMilestones && !Milestones.Instance.IsAccessibleAtNextMilestone(ingredient.goods))
                            {
                                continue;
                            }
                            sciencePackUsage.TryGetValue(ingredient.goods, out var prev);
                            sciencePackUsage[ingredient.goods] = prev + ingredient.amount * technology.count;
                        }
                    }
                }
            }



            foreach (var goods in Database.goods.all)
            {
                if (!ShouldInclude(goods))
                {
                    continue;
                }
                var mapGeneratedAmount = 0f;
                foreach (var src in goods.miscSources)
                {
                    if (src is Entity ent && ent.mapGenerated)
                    {
                        foreach (var product in ent.loot)
                        {
                            if (product.goods == goods)
                            {
                                mapGeneratedAmount += product.amount;
                            }
                        }
                    }
                }
                var variable = solver.MakeVar(CostLowerLimit, CostLimitWhenGeneratesOnMap / mapGeneratedAmount, false, goods.name);
                objective.SetCoefficient(variable, 1e-3); // adding small amount to each object cost, so even objects that aren't required for science will get cost calculated
                variables[goods] = variable;
            }

            foreach (var(item, count) in sciencePackUsage)
            {
                objective.SetCoefficient(variables[item], count / 1000f);
            }

            var export = Database.objects.CreateMapping <float>();
            var recipeProductionCost = Database.recipesAndTechnologies.CreateMapping <float>();

            recipeCost = Database.recipes.CreateMapping <float>();
            flow       = Database.objects.CreateMapping <float>();
            var lastVariable = Database.goods.CreateMapping <Variable>();

            foreach (var recipe in Database.recipes.all)
            {
                if (!ShouldInclude(recipe))
                {
                    continue;
                }
                if (onlyCurrentMilestones && !recipe.IsAccessibleWithCurrentMilestones())
                {
                    continue;
                }

                // TODO incorporate fuel selection. Now just select fuel if it only uses 1 fuel
                Goods singleUsedFuel       = null;
                var   singleUsedFuelAmount = 0f;
                var   minEmissions         = 100f;
                var   minSize  = 15;
                var   minPower = 1000f;
                foreach (var crafter in recipe.crafters)
                {
                    minEmissions = MathF.Min(crafter.energy.emissions, minEmissions);
                    if (crafter.energy.type == EntityEnergyType.Heat)
                    {
                        break;
                    }
                    if (crafter.size < minSize)
                    {
                        minSize = crafter.size;
                    }
                    var power = crafter.energy.type == EntityEnergyType.Void ? 0f : recipe.time * crafter.power / (crafter.craftingSpeed * crafter.energy.effectivity);
                    if (power < minPower)
                    {
                        minPower = power;
                    }
                    foreach (var fuel in crafter.energy.fuels)
                    {
                        if (!ShouldInclude(fuel))
                        {
                            continue;
                        }
                        if (fuel.fuelValue <= 0f)
                        {
                            singleUsedFuel = null;
                            break;
                        }
                        var amount = power / fuel.fuelValue;
                        if (singleUsedFuel == null)
                        {
                            singleUsedFuel       = fuel;
                            singleUsedFuelAmount = amount;
                        }
                        else if (singleUsedFuel == fuel)
                        {
                            singleUsedFuelAmount = MathF.Min(singleUsedFuelAmount, amount);
                        }
                        else
                        {
                            singleUsedFuel = null;
                            break;
                        }
                    }
                    if (singleUsedFuel == null)
                    {
                        break;
                    }
                }

                if (minPower < 0f)
                {
                    minPower = 0f;
                }
                var size          = Math.Max(minSize, (recipe.ingredients.Length + recipe.products.Length) / 2);
                var sizeUsage     = CostPerSecond * recipe.time * size;
                var logisticsCost = sizeUsage * (1f + CostPerIngredientPerSize * recipe.ingredients.Length + CostPerProductPerSize * recipe.products.Length) + CostPerMj * minPower;

                if (singleUsedFuel == Database.electricity || singleUsedFuel == Database.voidEnergy || singleUsedFuel == Database.heat)
                {
                    singleUsedFuel = null;
                }

                var constraint = solver.MakeConstraint(double.NegativeInfinity, 0, recipe.name);
                constraints[recipe] = constraint;

                foreach (var product in recipe.products)
                {
                    var var    = variables[product.goods];
                    var amount = product.amount;
                    constraint.SetCoefficientCheck(var, amount, ref lastVariable[product.goods]);
                    if (product.goods is Item)
                    {
                        logisticsCost += amount * CostPerItem;
                    }
                    else if (product.goods is Fluid)
                    {
                        logisticsCost += amount * CostPerFluid;
                    }
                }

                if (singleUsedFuel != null)
                {
                    var var = variables[singleUsedFuel];
                    constraint.SetCoefficientCheck(var, -singleUsedFuelAmount, ref lastVariable[singleUsedFuel]);
                }

                foreach (var ingredient in recipe.ingredients)
                {
                    var var = variables[ingredient.goods]; // TODO split cost analysis
                    constraint.SetCoefficientCheck(var, -ingredient.amount, ref lastVariable[ingredient.goods]);
                    if (ingredient.goods is Item)
                    {
                        logisticsCost += ingredient.amount * CostPerItem;
                    }
                    else if (ingredient.goods is Fluid)
                    {
                        logisticsCost += ingredient.amount * CostPerFluid;
                    }
                }

                if (recipe.sourceEntity != null && recipe.sourceEntity.mapGenerated)
                {
                    var totalMining = 0f;
                    foreach (var product in recipe.products)
                    {
                        totalMining += product.amount;
                    }
                    var miningPenalty = MiningPenalty;
                    var totalDensity  = recipe.sourceEntity.mapGenDensity / totalMining;
                    if (totalDensity < MiningMaxDensityForPenalty)
                    {
                        var extraPenalty = MathF.Log(MiningMaxDensityForPenalty / totalDensity);
                        miningPenalty += Math.Min(extraPenalty, MiningMaxExtraPenaltyForRarity);
                    }

                    logisticsCost *= miningPenalty;
                }

                if (minEmissions >= 0f)
                {
                    logisticsCost += minEmissions * CostPerPollution * recipe.time;
                }

                constraint.SetUb(logisticsCost);
                export[recipe]     = logisticsCost;
                recipeCost[recipe] = logisticsCost;
            }

            // TODO this is temporary fix for strange item sources (make the cost of item not higher than the cost of its source)
            foreach (var item in Database.items.all)
            {
                if (ShouldInclude(item))
                {
                    foreach (var source in item.miscSources)
                    {
                        if (source is Goods g && ShouldInclude(g))
                        {
                            var constraint = solver.MakeConstraint(double.NegativeInfinity, 0, "source-" + item.locName);
                            constraint.SetCoefficient(variables[g], -1);
                            constraint.SetCoefficient(variables[item], 1);
                        }
                    }
                }
            }

            // TODO this is temporary fix for fluid temperatures (make the cost of fluid with lower temp not higher than the cost of fluid with higher temp)
            foreach (var(name, fluids) in Database.fluidVariants)
            {
                var prev = fluids[0];
                for (var i = 1; i < fluids.Count; i++)
                {
                    var cur        = fluids[i];
                    var constraint = solver.MakeConstraint(double.NegativeInfinity, 0, "fluid-" + name + "-" + prev.temperature);
                    constraint.SetCoefficient(variables[prev], 1);
                    constraint.SetCoefficient(variables[cur], -1);
                    prev = cur;
                }
            }

            var result = solver.TrySolvewithDifferentSeeds();

            Console.WriteLine("Cost analysis completed in " + time.ElapsedMilliseconds + " ms. with result " + result);
            var sumImportance = 1f;
            var totalRecipes  = 0;

            if (result == Solver.ResultStatus.OPTIMAL || result == Solver.ResultStatus.FEASIBLE)
            {
                var objectiveValue = (float)objective.Value();
                Console.WriteLine("Estimated modpack cost: " + DataUtils.FormatAmount(objectiveValue * 1000f, UnitOfMeasure.None));
                foreach (var g in Database.goods.all)
                {
                    if (variables[g] == null)
                    {
                        continue;
                    }
                    var value = (float)variables[g].SolutionValue();
                    export[g] = value;
                }

                foreach (var recipe in Database.recipes.all)
                {
                    if (constraints[recipe] == null)
                    {
                        continue;
                    }
                    var recipeFlow = (float)constraints[recipe].DualValue();
                    if (recipeFlow > 0f)
                    {
                        totalRecipes++;
                        sumImportance += recipeFlow;
                        flow[recipe]   = recipeFlow;
                        foreach (var product in recipe.products)
                        {
                            flow[product.goods] += recipeFlow * product.amount;
                        }
                    }
                }
            }
            foreach (var o in Database.objects.all)
            {
                if (!ShouldInclude(o))
                {
                    export[o] = float.PositiveInfinity;
                    continue;
                }

                if (o is RecipeOrTechnology recipe)
                {
                    foreach (var ingredient in recipe.ingredients) // TODO split
                    {
                        export[o] += export[ingredient.goods] * ingredient.amount;
                    }
                    foreach (var product in recipe.products)
                    {
                        recipeProductionCost[recipe] += product.amount * export[product.goods];
                    }
                }
                else if (o is Entity entity)
                {
                    var minimal = float.PositiveInfinity;
                    foreach (var item in entity.itemsToPlace)
                    {
                        if (export[item] < minimal)
                        {
                            minimal = export[item];
                        }
                    }
                    export[o] = minimal;
                }
            }
            cost = export;
            recipeProductCost = recipeProductionCost;

            recipeWastePercentage = Database.recipes.CreateMapping <float>();
            if (result == Solver.ResultStatus.OPTIMAL || result == Solver.ResultStatus.FEASIBLE)
            {
                foreach (var(recipe, constraint) in constraints)
                {
                    if (constraint == null)
                    {
                        continue;
                    }
                    var productCost = 0f;
                    foreach (var product in recipe.products)
                    {
                        productCost += product.amount * export[product.goods];
                    }
                    recipeWastePercentage[recipe] = 1f - productCost / export[recipe];
                }
            }
            else
            {
                if (!onlyCurrentMilestones)
                {
                    warnings.Error("Cost analysis was unable to process this modpack. This may mean YAFC bug.", ErrorSeverity.AnalysisWarning);
                }
            }

            importantItems = Database.goods.all.Where(x => x.usages.Length > 1).OrderByDescending(x => flow[x] * cost[x] * x.usages.Count(y => ShouldInclude(y) && recipeWastePercentage[y] == 0f)).ToArray();

            solver.Dispose();
        }