/// <summary> /// Część 1. zadania - zaplanowanie produkcji telewizorów dla pojedynczego kontrahenta. /// </summary> /// <remarks> /// Do przeprowadzenia testów wyznaczających maksymalną produkcję i zysk wymagane jest jedynie zwrócenie obiektu <see cref="PlanData"/>. /// Testy weryfikujące plan wymagają przypisania tablicy z planem do parametru wyjściowego <see cref="weeklyPlan"/>. /// </remarks> /// <param name="production"> /// Tablica obiektów zawierających informacje o produkcji fabryki w kolejnych tygodniach. /// Wartości pola <see cref="PlanData.Quantity"/> oznaczają limit produkcji w danym tygodniu, /// a pola <see cref="PlanData.Value"/> - koszt produkcji jednej sztuki. /// </param> /// <param name="sales"> /// Tablica obiektów zawierających informacje o sprzedaży w kolejnych tygodniach. /// Wartości pola <see cref="PlanData.Quantity"/> oznaczają maksymalną sprzedaż w danym tygodniu, /// a pola <see cref="PlanData.Value"/> - cenę sprzedaży jednej sztuki. /// </param> /// <param name="storageInfo"> /// Obiekt zawierający informacje o magazynie. /// Wartość pola <see cref="PlanData.Quantity"/> oznacza pojemność magazynu, /// a pola <see cref="PlanData.Value"/> - koszt przechowania jednego telewizora w magazynie przez jeden tydzień. /// </param> /// <param name="weeklyPlan"> /// Parametr wyjściowy, przez który powinien zostać zwrócony szczegółowy plan sprzedaży. /// </param> /// <returns> /// Obiekt <see cref="PlanData"/> opisujący wyznaczony plan. /// W polu <see cref="PlanData.Quantity"/> powinna znaleźć się maksymalna liczba wyprodukowanych telewizorów, /// a w polu <see cref="PlanData.Value"/> - wyznaczony maksymalny zysk fabryki. /// </returns> public PlanData CreateSimplePlan(PlanData[] production, PlanData[] sales, PlanData storageInfo, out SimpleWeeklyPlan[] weeklyPlan) { weeklyPlan = null; if (!isOK(production, sales, storageInfo)) { throw new ArgumentException(); } int n = production.Length; weeklyPlan = new SimpleWeeklyPlan[n]; for (int i = 0; i < n; ++i) { weeklyPlan[i].UnitsProduced = 0; weeklyPlan[i].UnitsSold = 0; weeklyPlan[i].UnitsStored = 0; } int k = n + 2; int source = 0; int sink = n + 1; Graph g = new AdjacencyListsGraph <SimpleAdjacencyList>(true, k); Graph r = new AdjacencyListsGraph <SimpleAdjacencyList>(true, k); for (int j = 0; j < n - 1; ++j) { g.AddEdge(new Edge(source, j + 1, production[j].Quantity)); // dodawanie produkcji //produkcja+, sales-, magazynowanie- r.AddEdge(new Edge(source, j + 1, production[j].Value)); g.AddEdge(new Edge(j + 1, sink, sales[j].Quantity)); // dodawanie sprzedazy r.AddEdge(new Edge(j + 1, sink, -sales[j].Value)); g.AddEdge(new Edge(j + 1, j + 2, storageInfo.Quantity)); // dodawanie magazynu r.AddEdge(new Edge(j + 1, j + 2, storageInfo.Value)); } g.AddEdge(source, n, production[n - 1].Quantity); // ostatni tydzien produkcji r.AddEdge(source, n, production[n - 1].Value); g.AddEdge(n, sink, sales[n - 1].Quantity); // ostatni tydzien sprzedazy r.AddEdge(n, sink, -sales[n - 1].Value); (double value, double cost, Graph flow)ret = MinCostFlowGraphExtender.MinCostFlow(g, r, source, sink, false, MaxFlowGraphExtender.PushRelabelMaxFlow, null, false); int produced = 0; for (int i = 0; i < n; ++i) { produced += (int)ret.flow.GetEdgeWeight(source, i + 1); weeklyPlan[i].UnitsProduced = (int)ret.flow.GetEdgeWeight(source, i + 1); weeklyPlan[i].UnitsSold = (int)ret.flow.GetEdgeWeight(i + 1, sink); if (i != n - 1) { weeklyPlan[i].UnitsStored = (int)ret.flow.GetEdgeWeight(i + 1, i + 2); } else { weeklyPlan[i].UnitsStored = 0; } } return(new PlanData { Quantity = produced, Value = -ret.cost }); }
/// <summary> /// Część 1. zadania - zaplanowanie produkcji telewizorów dla pojedynczego kontrahenta. /// </summary> /// <remarks> /// Do przeprowadzenia testów wyznaczających maksymalną produkcję i zysk wymagane jest jedynie zwrócenie obiektu <see cref="PlanData"/>. /// Testy weryfikujące plan wymagają przypisania tablicy z planem do parametru wyjściowego <see cref="weeklyPlan"/>. /// </remarks> /// <param name="production"> /// Tablica obiektów zawierających informacje o produkcji fabryki w kolejnych tygodniach. /// Wartości pola <see cref="PlanData.Quantity"/> oznaczają limit produkcji w danym tygodniu, /// a pola <see cref="PlanData.Value"/> - koszt produkcji jednej sztuki. /// </param> /// <param name="sales"> /// Tablica obiektów zawierających informacje o sprzedaży w kolejnych tygodniach. /// Wartości pola <see cref="PlanData.Quantity"/> oznaczają maksymalną sprzedaż w danym tygodniu, /// a pola <see cref="PlanData.Value"/> - cenę sprzedaży jednej sztuki. /// </param> /// <param name="storageInfo"> /// Obiekt zawierający informacje o magazynie. /// Wartość pola <see cref="PlanData.Quantity"/> oznacza pojemność magazynu, /// a pola <see cref="PlanData.Value"/> - koszt przechowania jednego telewizora w magazynie przez jeden tydzień. /// </param> /// <param name="weeklyPlan"> /// Parametr wyjściowy, przez który powinien zostać zwrócony szczegółowy plan sprzedaży. /// </param> /// <returns> /// Obiekt <see cref="PlanData"/> opisujący wyznaczony plan. /// W polu <see cref="PlanData.Quantity"/> powinna znaleźć się maksymalna liczba wyprodukowanych telewizorów, /// a w polu <see cref="PlanData.Value"/> - wyznaczony maksymalny zysk fabryki. /// </returns> public PlanData CreateSimplePlan(PlanData[] production, PlanData[] sales, PlanData storageInfo, out SimpleWeeklyPlan[] weeklyPlan) { //Check sanity if (storageInfo.Value < 0 || storageInfo.Quantity < 0 || production.Length == 0 || production.Length != sales.Length) { throw new ArgumentException(); } //Create graphs for Capacities and Costs Graph Capacities = new AdjacencyListsGraph <SimpleAdjacencyList>(true, production.Length + 2); Graph Costs = new AdjacencyListsGraph <SimpleAdjacencyList>(true, production.Length + 2); //Hard-wire IDs special vertices int source = production.Length; int target = production.Length + 1; //Populate graphs with edges for (int i = 0; i < production.Length; i++) { //Check sanity if (production[i].Value < 0 || production[i].Quantity < 0 || sales[i].Quantity < 0 || sales[i].Value < 0) { throw new ArgumentException(); } //For Capacities Capacities.AddEdge(source, i, production[i].Quantity); Capacities.AddEdge(i, target, sales[i].Quantity); //For Costs Costs.AddEdge(source, i, production[i].Value); Costs.AddEdge(i, target, -sales[i].Value); //Edge for magazine purposes if (i < production.Length - 1) { Capacities.AddEdge(i, i + 1, storageInfo.Quantity); Costs.AddEdge(i, i + 1, storageInfo.Value); } } //Generate the flow (double value, double cost, Graph flow) = MinCostFlowGraphExtender.MinCostFlow(Capacities, Costs, source, target, false, MaxFlowGraphExtender.PushRelabelMaxFlow); //Construct weekly plan weeklyPlan = new SimpleWeeklyPlan[production.Length]; for (int i = 0; i < production.Length; i++) { int unitsStored = (int)flow.GetEdgeWeight(i, i + 1); int unitsSold = (int)flow.GetEdgeWeight(i, target); int unitsProduced = (int)flow.GetEdgeWeight(source, i); weeklyPlan[i].UnitsStored = unitsStored > 0 ?(int)flow.GetEdgeWeight(i, i + 1) : 0; weeklyPlan[i].UnitsSold = unitsSold > 0 ? (int)flow.GetEdgeWeight(i, target) : 0; weeklyPlan[i].UnitsProduced = unitsProduced > 0 ? (int)flow.GetEdgeWeight(source, i) : 0; } return(new PlanData { Quantity = (int)value, Value = -cost }); }
/// <summary> /// Część 2. zadania - zaplanowanie produkcji telewizorów dla wielu kontrahentów. /// </summary> /// <remarks> /// Do przeprowadzenia testów wyznaczających produkcję dającą maksymalny zysk wymagane jest jedynie zwrócenie obiektu <see cref="PlanData"/>. /// Testy weryfikujące plan wymagają przypisania tablicy z planem do parametru wyjściowego <see cref="weeklyPlan"/>. /// </remarks> /// <param name="production"> /// Tablica obiektów zawierających informacje o produkcji fabryki w kolejnych tygodniach. /// Wartość pola <see cref="PlanData.Quantity"/> oznacza limit produkcji w danym tygodniu, /// a pola <see cref="PlanData.Value"/> - koszt produkcji jednej sztuki. /// </param> /// <param name="sales"> /// Dwuwymiarowa tablica obiektów zawierających informacje o sprzedaży w kolejnych tygodniach. /// Pierwszy wymiar tablicy jest równy liczbie kontrahentów, zaś drugi - liczbie tygodni w planie. /// Wartości pola <see cref="PlanData.Quantity"/> oznaczają maksymalną sprzedaż w danym tygodniu, /// a pola <see cref="PlanData.Value"/> - cenę sprzedaży jednej sztuki. /// Każdy wiersz tablicy odpowiada jednemu kontrachentowi. /// </param> /// <param name="storageInfo"> /// Obiekt zawierający informacje o magazynie. /// Wartość pola <see cref="PlanData.Quantity"/> oznacza pojemność magazynu, /// a pola <see cref="PlanData.Value"/> - koszt przechowania jednego telewizora w magazynie przez jeden tydzień. /// </param> /// <param name="weeklyPlan"> /// Parametr wyjściowy, przez który powinien zostać zwrócony szczegółowy plan sprzedaży. /// </param> /// <returns> /// Obiekt <see cref="PlanData"/> opisujący wyznaczony plan. /// W polu <see cref="PlanData.Quantity"/> powinna znaleźć się optymalna liczba wyprodukowanych telewizorów, /// a w polu <see cref="PlanData.Value"/> - wyznaczony maksymalny zysk fabryki. /// </returns> public PlanData CreateComplexPlan(PlanData[] production, PlanData[,] sales, PlanData storageInfo, out WeeklyPlan[] weeklyPlan) { int c = sales.GetLength(0); //Contrahents no int n = sales.GetLength(1); //Weeks no //Check sanity if (storageInfo.Value < 0 || storageInfo.Quantity < 0 || c == 0 || n == 0 || n != production.Length) { throw new ArgumentException(); } //Create graphs for Capacities and Costs Graph Capacities = new AdjacencyListsGraph <SimpleAdjacencyList>(true, 2 * n + c + 2); Graph Costs = new AdjacencyListsGraph <SimpleAdjacencyList>(true, 2 * n + c + 2); //Vertices explanation: //n for n weeks //n for n magazines (one state for each week) //c for c contrahents //plus 2 extra vertices for source and target //Vertices definitions: //Source and targer int source = 2 * n + c + 1; int target = 2 * n + c; //Week main vertices int weeksStart = 0; //Week magazine vertices int weekMagStart = n; //Contrahents vertices int contrStart = 2 * n; //Populate edges going out of / going into week vertices for (int i = 0; i < n; i++) { //Check sanity if (production[i].Value < 0 || production[i].Quantity < 0) { throw new ArgumentException(); } int w = weeksStart + i; //current magazine week vertex int v = weekMagStart + i; //from source to v Capacities.AddEdge(source, w, production[i].Quantity); Costs.AddEdge(source, w, production[i].Value); //from v to target Capacities.AddEdge(w, target, production[i].Quantity); Costs.AddEdge(w, target, -production[i].Value); //from v to corresponding magazine Capacities.AddEdge(w, v, production[i].Quantity); Costs.AddEdge(w, v, 0); //Populate edges between magazine vertices if (i <= n - 2) { int nextV = weekMagStart + i + 1; Capacities.AddEdge(v, nextV, storageInfo.Quantity); Costs.AddEdge(v, nextV, storageInfo.Value); } //Populate edges going out of week magazine vertices into contrahents' vertices for (int j = 0; j < c; j++) { if (sales[j, i].Quantity < 0 || sales[j, i].Value < 0) { throw new ArgumentException(); } int con = contrStart + j; //Contrahent vertex int quant = sales[j, i].Quantity; double val = sales[j, i].Value; Capacities.AddEdge(v, con, quant); Costs.AddEdge(v, con, -val); } } //Populate edges going from contrahents to sink (probably could be embedded into one of the 'main' previous loops, just testing for now for (int i = 0; i < c; i++) { int v = contrStart + i; Capacities.AddEdge(v, target, int.MaxValue); Costs.AddEdge(v, target, 0); } //Generate the flow (double value, double cost, Graph flow) = MinCostFlowGraphExtender.MinCostFlow(Capacities, Costs, source, target, true, MaxFlowGraphExtender.PushRelabelMaxFlow); //Calculate quantity produced weeklyPlan = new WeeklyPlan[n]; int quantity = 0; double profit = 0; for (int i = 0; i < n; i++) { int week = weeksStart + i; int magaz = weekMagStart + i; int nextMagaz = weekMagStart + i + 1; int itemsProduced = (int)flow.GetEdgeWeight(week, magaz); double weeklyProductionCost = production[i].Value; weeklyPlan[i].UnitsProduced = itemsProduced; profit -= weeklyProductionCost * itemsProduced; int itemsStored = 0; if (i <= n - 2) { itemsStored = (int)flow.GetEdgeWeight(magaz, nextMagaz); profit -= itemsStored * storageInfo.Value; } weeklyPlan[i].UnitsSold = new int[c]; weeklyPlan[i].UnitsStored = itemsStored; for (int j = 0; j < c; j++) { int contr = contrStart + j; if (i == 0) { quantity += (int)flow.GetEdgeWeight(contr, target); } int itemsSold = (int)flow.GetEdgeWeight(magaz, contr); double price = sales[j, i].Value; profit += price * itemsSold; weeklyPlan[i].UnitsSold[j] = itemsSold; } } return(new PlanData { Quantity = quantity, Value = profit }); }
/// <summary> /// Część 2. zadania - zaplanowanie produkcji telewizorów dla wielu kontrahentów. /// </summary> /// <remarks> /// Do przeprowadzenia testów wyznaczających produkcję dającą maksymalny zysk wymagane jest jedynie zwrócenie obiektu <see cref="PlanData"/>. /// Testy weryfikujące plan wymagają przypisania tablicy z planem do parametru wyjściowego <see cref="weeklyPlan"/>. /// </remarks> /// <param name="production"> /// Tablica obiektów zawierających informacje o produkcji fabryki w kolejnych tygodniach. /// Wartość pola <see cref="PlanData.Quantity"/> oznacza limit produkcji w danym tygodniu, /// a pola <see cref="PlanData.Value"/> - koszt produkcji jednej sztuki. /// </param> /// <param name="sales"> /// Dwuwymiarowa tablica obiektów zawierających informacje o sprzedaży w kolejnych tygodniach. /// Pierwszy wymiar tablicy jest równy liczbie kontrahentów, zaś drugi - liczbie tygodni w planie. /// Wartości pola <see cref="PlanData.Quantity"/> oznaczają maksymalną sprzedaż w danym tygodniu, /// a pola <see cref="PlanData.Value"/> - cenę sprzedaży jednej sztuki. /// Każdy wiersz tablicy odpowiada jednemu kontrachentowi. /// </param> /// <param name="storageInfo"> /// Obiekt zawierający informacje o magazynie. /// Wartość pola <see cref="PlanData.Quantity"/> oznacza pojemność magazynu, /// a pola <see cref="PlanData.Value"/> - koszt przechowania jednego telewizora w magazynie przez jeden tydzień. /// </param> /// <param name="weeklyPlan"> /// Parametr wyjściowy, przez który powinien zostać zwrócony szczegółowy plan sprzedaży. /// </param> /// <returns> /// Obiekt <see cref="PlanData"/> opisujący wyznaczony plan. /// W polu <see cref="PlanData.Quantity"/> powinna znaleźć się optymalna liczba wyprodukowanych telewizorów, /// a w polu <see cref="PlanData.Value"/> - wyznaczony maksymalny zysk fabryki. /// </returns> public PlanData CreateComplexPlan(PlanData[] production, PlanData[,] sales, PlanData storageInfo, out WeeklyPlan[] weeklyPlan) { weeklyPlan = null; if (!isOK(production, sales, storageInfo)) { throw new ArgumentException(); } int count = sales.GetLength(0); // count ilosc kotrahentow int n = production.Length; // n ilosc tygodni weeklyPlan = new WeeklyPlan[n]; for (int i = 0; i < n; ++i) { weeklyPlan[i].UnitsSold = new int[count]; } int source = n; int sink = n + 1; int start = n + 2; // nr pierwszego kontrahenta int end = start + count - 1; // nr ostatniego kontrahenta int V = 2 + count + n + n; int storageStart = end + 1; int storageEnd = end + n; Graph g = new AdjacencyListsGraph <SimpleAdjacencyList>(true, V); Graph r = new AdjacencyListsGraph <SimpleAdjacencyList>(true, V); for (int i = start; i <= end; ++i) { // ujscie od kontrahenta g.AddEdge(i, sink, Double.MaxValue);//Double.MaxValue); r.AddEdge(i, sink, 0); // polaczenie sprzedazy kontrahentom int buyer = i - start; for (int tydzien = 0; tydzien < n; ++tydzien) { g.AddEdge(storageStart + tydzien, i, sales[buyer, tydzien].Quantity); r.AddEdge(storageStart + tydzien, i, -sales[buyer, tydzien].Value); } } for (int j = 0; j < n; ++j) { // dodanie produkcji (ze zrodla do tygodnia) g.AddEdge(source, j, production[j].Quantity); r.AddEdge(source, j, production[j].Value); // dodanie ujscia dla produkcji, ktorej sie nie oplaca produkowac g.AddEdge(j, sink, Double.MaxValue); r.AddEdge(j, sink, -production[j].Value); // dodanie krawedzi miedzy produkcja a wierzcholkiem kontrolujacym wyjscie do kontrahentow g.AddEdge(j, storageStart + j, production[j].Quantity); r.AddEdge(j, storageStart + j, 0); // dodanie magazynu if (j != n - 1) // bez ostatniego dnia bo wtedy nic nie magazynujemy { g.AddEdge(storageStart + j, storageStart + j + 1, storageInfo.Quantity); r.AddEdge(storageStart + j, storageStart + j + 1, storageInfo.Value); } } (double value, double cost, Graph flow)ret = MinCostFlowGraphExtender.MinCostFlow(g, r, source, sink, false, MaxFlowGraphExtender.PushRelabelMaxFlow, null, false); int produced = 0; double profit = 0; for (int i = 0; i < n; ++i) { // produkcja produced += weeklyPlan[i].UnitsProduced = production[i].Quantity - (int)ret.flow.GetEdgeWeight(i, sink); profit -= weeklyPlan[i].UnitsProduced * production[i].Value; // sprzedaz for (int j = start; j <= end; ++j) { weeklyPlan[i].UnitsSold[j - start] = (int)ret.flow.GetEdgeWeight(storageStart + i, j); profit += ret.flow.GetEdgeWeight(storageStart + i, j) * (sales[j - start, i].Value); // (weeklyPlan[i].UnitsSold[j - start] * (sales[j - start, i].Value)); } // magazyn if (i != n - 1) { weeklyPlan[i].UnitsStored = (int)ret.flow.GetEdgeWeight(storageStart + i, storageStart + i + 1); profit -= weeklyPlan[i].UnitsStored * storageInfo.Value; } } return(new PlanData { Quantity = produced, Value = Math.Ceiling(profit) }); }
/// <summary> /// Metoda zwraca największą możliwą do wyprodukowania liczbę smerfonów /// </summary> /// <param name="providers">Dostawcy</param> /// <param name="factories">Fabryki</param> /// <param name="distanceCostMultiplier">współczynnik kosztu przewozu</param> /// <param name="productionCost">Łączny koszt produkcji wszystkich smerfonów</param> /// <param name="transport">Tablica opisująca ilości transportowanych surowców miedzy poszczególnymi dostawcami i fabrykami</param> /// <param name="maximumProduction">Maksymalny rozmiar produkcji</param> public static double CalculateFlow(Provider[] providers, Factory[] factories, double distanceCostMultiplier, out double productionCost, out int[,] transport, int maximumProduction = int.MaxValue) { /* * dostawcy wejściowi: [0; providers.Length-1] * fabryki główne: [providers.Length; providers.Length+factories.Length-1] * fabryki nadgodzinne: [providers.Length+factories.Length; providers.Length+2*factories.Length-1] */ int source = providers.Length + 2 * factories.Length; int postsource = source + 1; int sink = source + 2; Graph graph = new AdjacencyMatrixGraph(true, providers.Length + 2 * factories.Length + 3); Graph costs = new AdjacencyMatrixGraph(true, providers.Length + 2 * factories.Length + 3); //Dodawanie krawędzi od 3 wierzchołków: źródła, post-źródła i ujścia graph.Add(new Edge(source, postsource, maximumProduction)); costs.Add(new Edge(source, postsource, 0.0)); for (int p = 0; p < providers.Length; p++) { graph.Add(new Edge(postsource, p, providers[p].Capacity)); costs.Add(new Edge(postsource, p, providers[p].Cost)); } for (int f = 0; f < factories.Length; f++) { graph.Add(new Edge(providers.Length + f, sink, factories[f].Limit)); graph.Add(new Edge(providers.Length + factories.Length + f, sink, int.MaxValue)); costs.Add(new Edge(providers.Length + f, sink, factories[f].LowerCost)); costs.Add(new Edge(providers.Length + factories.Length + f, sink, factories[f].HigherCost)); } //Proste łączenie dostawców fabrykami głownymi for (int p = 0; p < providers.Length; p++) { for (int f = 0; f < factories.Length; f++) { graph.Add(new Edge(p, providers.Length + f, int.MaxValue)); costs.Add(new Edge(p, providers.Length + f, Math.Ceiling(distanceCostMultiplier * Distance(providers[p], factories[f])))); } } //Łączenie głównych fabryk z fabrykami nadgodzinnymi for (int f = 0; f < factories.Length; f++) { graph.Add(new Edge(providers.Length + f, providers.Length + factories.Length + f, int.MaxValue)); costs.Add(new Edge(providers.Length + f, providers.Length + factories.Length + f, 0.0)); } Graph flows; var result = MinCostFlowGraphExtender.MinCostFlow(graph, costs, source, sink, out productionCost, out flows); transport = new int[providers.Length, factories.Length]; for (int i = 0; i < providers.Length; i++) { foreach (var e in flows.OutEdges(i)) { if (e.Weight != 0) { transport[i, e.To - providers.Length] = (int)e.Weight; } } } return(result); }