public abstract Task <string> Solve(ProjectPage page);
public override async Task <string> Solve(ProjectPage page) { var processedGoods = Database.goods.CreateMapping <Constraint>(); var processedRecipes = Database.recipes.CreateMapping <Variable>(); var processingStack = new Queue <Goods>(); var solver = DataUtils.CreateSolver("BestFlowSolver"); var rootConstraint = solver.MakeConstraint(); foreach (var root in roots) { processedGoods[root] = rootConstraint; } foreach (var goal in goals) { processedGoods[goal.item] = solver.MakeConstraint(goal.amount, double.PositiveInfinity, goal.item.name); processingStack.Enqueue(goal.item); } await Ui.ExitMainThread(); var objective = solver.Objective(); objective.SetMinimization(); processingStack.Enqueue(null); // depth marker; var depth = 0; var allRecipes = new List <Recipe>(); while (processingStack.Count > 1) { var item = processingStack.Dequeue(); if (item == null) { processingStack.Enqueue(null); depth++; continue; } var constraint = processedGoods[item]; foreach (var recipe in item.production) { if (!recipe.IsAccessibleWithCurrentMilestones()) { continue; } if (processedRecipes[recipe] is Variable var) { constraint.SetCoefficient(var, constraint.GetCoefficient(var) + recipe.GetProduction(item)); } else { allRecipes.Add(recipe); var = solver.MakeNumVar(0, double.PositiveInfinity, recipe.name); objective.SetCoefficient(var, recipe.RecipeBaseCost() * (1 + depth * 0.5)); processedRecipes[recipe] = var; foreach (var product in recipe.products) { if (processedGoods[product.goods] is Constraint constr && !processingStack.Contains(product.goods)) { constr.SetCoefficient(var, constr.GetCoefficient(var) + product.amount); } } foreach (var ingredient in recipe.ingredients) { var proc = processedGoods[ingredient.goods]; if (proc == rootConstraint) { continue; } if (processedGoods[ingredient.goods] is Constraint constr) { constr.SetCoefficient(var, constr.GetCoefficient(var) - ingredient.amount); } else { constr = solver.MakeConstraint(0, double.PositiveInfinity, ingredient.goods.name); processedGoods[ingredient.goods] = constr; processingStack.Enqueue(ingredient.goods); constr.SetCoefficient(var, -ingredient.amount); } } } } } var solverResult = solver.Solve(); Console.WriteLine("Solution completed with result " + solverResult); if (solverResult != Solver.ResultStatus.OPTIMAL && solverResult != Solver.ResultStatus.FEASIBLE) { Console.WriteLine(solver.ExportModelAsLpFormat(false)); this.tiers = null; return("Model have no solution"); } var graph = new Graph <Recipe>(); allRecipes.RemoveAll(x => { if (!(processedRecipes[x] is Variable variable)) { return(true); } if (variable.BasisStatus() != Solver.BasisStatus.BASIC || variable.SolutionValue() <= 1e-6d) { processedRecipes[x] = null; return(true); } return(false); }); foreach (var recipe in allRecipes) { foreach (var ingredient in recipe.ingredients) { foreach (var productionRecipe in ingredient.goods.production) { if (processedRecipes[productionRecipe] != null) { // TODO think about heuristics for selecting first recipe. Now chooses first (essentially random) graph.Connect(recipe, productionRecipe); //break; } } } } var subgraph = graph.MergeStrongConnectedComponents(); var allDependencies = subgraph.Aggregate(x => new HashSet <(Recipe, Recipe[])>(), (set, item, subset) =>