/// <summary> /// Creates the output files and writes their headers. /// This should be called exactly once during the simulation, /// before any calls to the logging functions in this class. /// </summary> /// <param name="ip">An input parameters object</param> public static void CreateOutputFiles(InputParameters ip) { // Create firm summary file string Hdr_Firm_SUM = "FirmID,g,d,numRCP,numCO,BenchmarkRevenue,BenchmarkTotCost,BenchmarkProfit,NumProdInBenchmarkMix" + Environment.NewLine; File.WriteAllText(file_Firm_SUM, Hdr_Firm_SUM); // Create firm resource consumption file StringBuilder Hdr_Firm_RESCON = new StringBuilder("FirmID,g,d"); Hdr_Firm_RESCON.Append(GenIndexedStringList("RCC_", ip.RCP)); Hdr_Firm_RESCON.Append(GenIndexedStringList("RCU_", ip.RCP)); Hdr_Firm_RESCON.Append(Environment.NewLine); File.WriteAllText(file_Firm_RESCON, Hdr_Firm_RESCON.ToString()); // Create firm product information file StringBuilder Hdr_Firm_PRODUCT = new StringBuilder("FirmID,g,d"); Hdr_Firm_PRODUCT.Append(GenIndexedStringList("MAR_", ip.CO)); Hdr_Firm_PRODUCT.Append(GenIndexedStringList("MXQ_", ip.CO)); Hdr_Firm_PRODUCT.Append(GenIndexedStringList("SP_", ip.CO)); Hdr_Firm_PRODUCT.Append(GenIndexedStringList("DECT0_", ip.CO)); Hdr_Firm_PRODUCT.Append(GenIndexedStringList("Rank_by_val_", ip.CO)); Hdr_Firm_PRODUCT.Append(GenIndexedStringList("Rank_by_mar_", ip.CO)); Hdr_Firm_PRODUCT.Append(Environment.NewLine); File.WriteAllText(file_Firm_PRODUCT, Hdr_Firm_PRODUCT.ToString()); // Create cost system summary file string Hdr_CostSys_SUM = "FirmID,CostSysID,PACP,ACP,PDR,B,D" + Environment.NewLine; File.WriteAllText(file_CostSys_SUM, Hdr_CostSys_SUM); // Create cost system product information file StringBuilder Hdr_CostSys_ERROR = new StringBuilder("FirmID,CostSysID,PACP,ACP,PDR"); Hdr_CostSys_ERROR.Append(GenIndexedStringList("startDecision_", ip.CO)); Hdr_CostSys_ERROR.Append(GenIndexedStringList("PC_B_", ip.CO)); Hdr_CostSys_ERROR.Append(GenIndexedStringList("PC_R_", ip.CO)); Hdr_CostSys_ERROR.AppendLine(",MPE"); File.WriteAllText(file_CostSys_ERROR, Hdr_CostSys_ERROR.ToString()); // Create cost system looping results file StringBuilder Hdr_CostSys_LOOP = new StringBuilder("FirmID,CostSysID,PACP,ACP,PDR"); Hdr_CostSys_LOOP.Append(GenIndexedStringList("startDecision_", ip.CO)); Hdr_CostSys_LOOP.Append(GenIndexedStringList("endingDecision_", ip.CO)); Hdr_CostSys_LOOP.AppendLine(",outcome"); File.WriteAllText(file_CostSys_LOOP, Hdr_CostSys_LOOP.ToString()); }
/// <summary> /// Creates a cost system. Assigns resources to pools and selects /// drivers for each pool. /// </summary> /// <param name="ip">An input parameters object.</param> /// <param name="firm">The firm upon which this cost system is based.</param> /// <param name="a">The number of activity cost pools to form.</param> /// <param name="p">A flag indicating method for assigning resources to cost pools. /// See input file cheat sheet for details.</param> /// <param name="r">A flag indicating which resources in the pools /// will be used to form drivers. See input file cheat sheet for details.</param> public CostSys( InputParameters ip, Firm firm, int a, int p, int r) { this.firm = firm; RowVector RCC = firm.Initial_RCC; int[] RANK = firm.Initial_RANK; SymmetricMatrix CORR = firm.PEARSONCORR; this.a = a; this.p = p; this.r = r; if (a != 1) { #region Code shared in flowchart 6.1, 6.2, and 6.3 // Segregate resources into big ones that will each // seed a pool, and miscellaneous resources. // The first (a-1) resources get their own pools. List <int> bigResources = RANK.Take(a - 1).ToList(); List <int> miscResources = RANK.Skip(a - 1).ToList(); // Create the set B and initialize the first // elements with the big pool resources. // Seeding big resources // Take each resource from bigPools, ane make it into a list // of length 1. Convert to an array of lists, and assign to B. B = bigResources.Select(elem => new List <int> { elem }).ToArray(); // Increase the length by 1, to make room for the miscellaneous // pool. Array.Resize(ref B, B.Length + 1); B[B.Length - 1] = new List <int>(); #endregion // p == 0: // Seed (a-1) pools with the largest (a-1) resources. // All remaining resources assigned to miscellaneous pool if (p == 0) { #region Flowchart 6.1 B[a - 1] = new List <int>(miscResources); #endregion } // p == 1: // Seed acp-1 pools based on size. Check to see // the highest correlation for the remaining resources. Assign the // unassigned resource with the highest correlation to // the relevant ACP. Check to see if the value of remaining // ACP > MISCPOOLSIZE. If so, continue to find the next highest // correlation, assign and check. When remaining value < 20%, // then pool everything into misc. else if (p == 1) { #region Flowchart 6.2 // This query iterates over miscResources. For each one, it // computes the correlation with every bigResource, and forms // a record {smallResourceIndex, index of big pool (in B), correlation }. // Order this list of records in descending order and keep the first one. // This first one is the pool to which the small resources will be allocated // if the correlation is sufficiently high. var query = miscResources.Select(smallRes => bigResources.Select((bigRes, i) => new { smallRes, BigPoolNum = i, correl = CORR[bigRes, smallRes] }).OrderByDescending(x => x.correl).First()); // Order the small resources by correlation with big resources. Thus, // if resource 7 is most correlated with big pool resource 0 (92%), // and resource 12 is most correlated with big pool resource 1 (83%), // 7 will be ahead of 12 in myArray. var myArray = query.OrderByDescending(x => x.correl).ToArray(); // The following block makes sure that at least one nonzero // resource is allocated to the last pool. The only time this // fails is if all miscellaneous resources are zero. int lastResourceToAllocate; { // Convert each record in myArray to the value of the resource // cost pool represented by that resource var moo = myArray.Select(x => RCC[x.smallRes]); // Convert each element of moo to the value of the remaining // resources in the array at this point. var moo2 = moo.Select((_, i) => moo.Skip(i).Sum()); List <double> ld = moo2.ToList(); // If the list contains a 0, that means there are one or // more zero resources. Find the index of the first one, // or if there isn't one, use the end of the array. if (ld.Contains(0.0)) { lastResourceToAllocate = ld.IndexOf(0.0) - 1; } else { lastResourceToAllocate = myArray.Length; } } double TR = RCC.Sum(); double notYetAllocated = miscResources.Aggregate(0.0, (acc, indx) => acc + RCC[indx]); bool cutoffReached = (notYetAllocated / TR) < ip.MISCPOOLSIZE; for (int k = 0; (k < lastResourceToAllocate) && !cutoffReached; ++k) { var q = myArray[k]; if (q.correl >= ip.CC) { B[q.BigPoolNum].Add(q.smallRes); miscResources.Remove(q.smallRes); } else { break; } notYetAllocated = miscResources.Aggregate(0.0, (acc, indx) => acc + RCC[indx]); cutoffReached = (notYetAllocated / TR) < ip.MISCPOOLSIZE; } // Check if there is anything left in miscResources // If yes, throw it in the miscellaneous pool (B.Last()). if (miscResources.Count > 0) { B.Last().AddRange(miscResources); } // If not, remove the last allocated resource (myArray.Last()) // from the pool to which it was allocated, and place it in the // miscellaneous pool. else { var q = myArray.Last(); B[q.BigPoolNum].Remove(q.smallRes); B.Last().Add(q.smallRes); } #endregion } // p == 2: // Seed each of the (a-1) cost pools with the largest resources. // Allocate the remaining resources to the (a-1) pools at random. // However, ensure that enough resources are in the last pool. // The fraction of resources in the last pool is MISCPOOLSIZE. else if (p == 2) { #region Flowchart 6.3 double TR = RCC.Sum(); // Magnitude of resources not yet allocated double notYetAllocated = miscResources.Aggregate(0.0, (acc, indx) => acc + RCC[indx]); // Fraction of resources not yet allocated double miscPoolPrct = notYetAllocated / TR; // Logic: Check if the fraction of resources in // miscResources is greater than the cap (ip.MISCPOOLSIZE). // If yes, take the first resource from miscResources // and put it in one of the big pools, chosen at random. // If the fraction of resources in miscResources is still // greater than the cap, repeat the loop. Otherwise, // stop and put the remaining resources in the last pool. // // Also stop under the following condition. Assume the head // of the miscResources list is allocated. Is the value of the // remaining resources in miscResources (the tail) greater than // zero? If not, stop. There has to be at least one non-zero // resource in the last pool. while ( (miscPoolPrct > ip.MISCPOOLSIZE) && (miscResources.Skip(1).Aggregate(0.0, (acc, indx) => acc + RCC[indx]) > 0.0) ) { // Pick a pool at random to get the next resource int poolIndx = (int)GenRandNumbers.GenUniformInt(0, a - 2); B[poolIndx].Add(miscResources.First()); miscResources.RemoveAt(0); notYetAllocated = miscResources.Aggregate(0.0, (acc, indx) => acc + RCC[indx]); miscPoolPrct = notYetAllocated / TR; } B.Last().AddRange(miscResources); #endregion } // p == 3: // Seed the first pool with the largest resource. // Iterate over the other pools. For each pool, select a seed resource: // This is the largest of the remaining, unassigned resources, and // assign it to the pool. // Form a correlation vector (a list), which is the correlation // of each resource in remainingResources with the seed resource. // If the highest correlation is greater than ip.CC, there are // enough remaining resources to fill the remaining pools, and // satisfy the constraint about the miscellaneous pool size, //assign resource with the highest correlation to the current pool. // Once there are just as many resources remaining as there are pools, // assign one resource to each remaining pool. else if (p == 3) { #region Flowchart 6.4 // Initialize B for (int i = 0; i < B.Length; ++i) { B[i] = new List <int>(); } // Seed the first pool with the largest resource B[0].Add(RANK[0]); List <int> remainingResources = RANK.Skip(1).ToList(); // Assign all zero resources to the last (miscellaneous) pool. // That way, each of the remaining pools is guaranteed to have // a nonzero resource. // This only works if there are at least as many nonzero resources // as there are pools. If not, then skip this step so that each // pool has at least one resource. int numZeroResources = remainingResources.Count(res => RCC[res] == 0.0); if (RCC.Dimension - numZeroResources >= B.Length) { while (RCC[remainingResources.Last()] == 0.0) { B.Last().Add(remainingResources.Last()); remainingResources.RemoveAt(remainingResources.Count - 1); } } // Iterate over the pools. For each pool, select a seed resource, // which is the first resource assigned to the pool. // Form a correlation vector (a list), which is the correlation // of each resource in remainingResources with the seed resource. // While max of the list is greater than ip.CC, and while // the other conditions are satisfied, assign resource with the // maximum correlation to the current pool. // Once condition 2 is no longer true, there are just as many // resources remaining as there are pools. The loop then assigns // one resource to each remaining pool. // Once condition 3 is no longer true, it assigns one resource // to each pool, and all the remaining resources to the last pool. for (int currentPool = 0; currentPool < B.Length - 1; ++currentPool) { int seedResource = B[currentPool].First(); int poolsToBeFilled = B.Length - (currentPool + 1); List <double> correlations = remainingResources.Select(res => CORR[res, seedResource]).ToList(); bool cond1 = correlations.Max() > ip.CC; bool cond2 = remainingResources.Count > poolsToBeFilled; // Magnitude of resources not yet allocated double notYetAllocated = remainingResources.Aggregate(0.0, (acc, indx) => acc + RCC[indx]); // Fraction of resources not yet allocated double TR = RCC.Sum(); double miscPoolPrct = notYetAllocated / TR; bool cond3 = miscPoolPrct > ip.MISCPOOLSIZE; while (cond1 && cond2 && cond3) { // Find the index of the resource with the maximum correlation // with the seed resource double maxCorr = correlations.Max(); int maxCorrIndx = remainingResources[correlations.IndexOf(maxCorr)]; // Add it to the current pool B[currentPool].Add(maxCorrIndx); // Remove it from the remainingResources list remainingResources.RemoveAt(correlations.IndexOf(maxCorr)); correlations.Remove(maxCorr); // Recompute loop termination conditions cond1 = correlations.Max() > ip.CC; cond2 = remainingResources.Count > poolsToBeFilled; notYetAllocated = remainingResources.Aggregate(0.0, (acc, indx) => acc + RCC[indx]); miscPoolPrct = notYetAllocated / TR; cond3 = miscPoolPrct > ip.MISCPOOLSIZE; } B[currentPool + 1].Add(remainingResources[0]); remainingResources.RemoveAt(0); } B.Last().AddRange(remainingResources); #endregion } else { throw new ApplicationException("Invalid value of p."); } } else { #region Flowchart 6.5 B = new List <int>[] { new List <int>(RANK) }; #endregion } // The fraction of RCC that is in the miscellaneous (last) // activity cost pool. double miscPoolSize = B.Last().Aggregate(0.0, (acc, i) => acc + RCC[i]) / RCC.Sum(); #region Flowchart 6.5 -- Choosing drivers // For each element of B, which is a list of resource indexes, // sort it in descending order by pool size (RCC[element]). // Technically, this is unnecessary, since elements should have // been added to the lists in B in descending order. But instead // of assuming that, since that could change in the future, // I am going to re-sort. Heck, it's only one line of code, // plus this essay of a comment that I just wrote. { var query = B.Select(list => list.OrderByDescending(indx => RCC[indx])); int numToTake; if (r == 0) { numToTake = 1; } else if (r == 1) { numToTake = ip.NUM; } else { throw new ApplicationException("Invalid value of r in FalseSys.cs."); } // This iterates over every list in query, and replaces that list // with a list containing only the first numToTake elements. var drivers = query.Select(list => list.Take(numToTake).ToList()); D = drivers.ToArray(); } #endregion }
/// <summary> /// Assuming the firm implements decision DECF0, this method computes /// reported costs for this cost system, which are used to compute the firm's updated decision. /// It iterates using the updated decision as the new starting decision until a terminal outcome /// (e.g. equilibrium, cycle) is reached. /// </summary> /// <param name="ip">The current InputParameters object</param> /// <param name="DECF0">The starting decision.</param> /// <returns>Returns the outcome of iterations and the final decision.</returns> public (CostSystemOutcomes stopCode, RowVector DECF1) EquilibriumCheck(InputParameters ip, RowVector DECF0) { #region Make local copies of firm-level variables ColumnVector MXQ = this.firm.MXQ; RowVector SP = this.firm.SP; #endregion // The initial vector of production quantities, given // starting decision DECF0 ColumnVector q0 = MXQ.ewMultiply(DECF0); // A list of past decisions made during the iteration process. // If a decision appears twice on this list, then a cycle exists. List <RowVector> pastDecisions = new List <RowVector> { DECF0 }; // The "next" decision that the firm would make. Assume the firm // starts with DECF0, computes resulting resource consumption, // and reported costs (through the cost system). Given reported // costs, it updates its decision to DECF1. RowVector DECF1; bool foundThisDecisionBefore; double MAR_DROP = 1.0 - ip.HYSTERESIS; double MAR_MAKE = 1.0 + ip.HYSTERESIS; CostSystemOutcomes stopCode = CostSystemOutcomes.Unassigned; bool done; do { RowVector PC_R = CalcReportedCosts(ip, DECF0); if (PC_R.Contains(double.NaN)) { DECF1 = PC_R.Map(x => double.NaN); } else { double[] MAR = PC_R.Zip(SP, (pc_r, sp) => sp / pc_r).ToArray(); var decf1 = MAR.Zip(q0, (mar, q) => (q > 0.0) ? ((mar <= MAR_DROP) ? 0.0 : 1.0) : ((mar > MAR_MAKE) ? 1.0 : 0.0)); DECF1 = new RowVector(decf1.ToList()); } ColumnVector q1 = MXQ.ewMultiply(DECF1); if (!(foundThisDecisionBefore = pastDecisions.Contains(DECF1))) { pastDecisions.Add(DECF1); } //double ExpectedCosts = PC_R * q1; //double TCF0 = this.firm.CalcTotCosts(q1); done = true; //if (q1 == q0) { if (DECF1 == DECF0) { stopCode = CostSystemOutcomes.Equilibrium; } else if (q1.TrueForAll(qty => qty == 0.0)) { stopCode = CostSystemOutcomes.ZeroMix; } else if (foundThisDecisionBefore) { stopCode = CostSystemOutcomes.Cycle; } else if (DECF1.Contains(double.NaN)) { stopCode = CostSystemOutcomes.NaN; } else { done = false; } if (!done) { DECF0 = DECF1; q0 = q1; } } while (!done); return(stopCode, DECF1); }
/// <summary> /// Assuming the firm implements decision DECF0, this method computes /// reported costs for this cost system. See remarks. /// </summary> /// <param name="ip">The current InputParameters object</param> /// <param name="DECF0">The starting decision. This is used to compute resource consumption.</param> /// <returns>The vector of reported costs that results from implementing DECF0. See remarks.</returns> /// <remarks>When the firm implements DECF0, it is able to observe total consumption /// of each resource needed to implement DECF0. It then uses this cost system to assign /// resources to cost pools, and then compute reported costs.</remarks> public RowVector CalcReportedCosts(InputParameters ip, RowVector DECF0) { #region Make local copies of firm-level variables RectangularMatrix res_cons_pat = this.firm.RES_CONS_PAT; #endregion #region Flowchart 7.3(a) - Compute total resource usage for this product mix // Production quantities, given DECF0 ColumnVector q0 = this.firm.MXQ.ewMultiply(DECF0); // Calculate total unit resource consumption // required to produce q0 ColumnVector TRU_F = res_cons_pat * q0; #endregion #region Flowchart 7.3(b)-(d) - Compute AC and drivers for this product mix // Resource usage (in dollars), given DECF0 RowVector RCC_F = this.firm.RCU.ewMultiply(TRU_F); // For each set of resources in B, aggregate the total cost // of those resources and put them in ac, the activity cost // pools. double[] ac = new double[this.a]; int ACPcount = ac.Length; double poolAmount; for (int i = 0; i < ACPcount; ++i) { poolAmount = 0.0; foreach (int resource in B[i]) { poolAmount += RCC_F[resource]; } ac[i] = poolAmount; } // Filter B, D, and AC to only contain lists for which the // corresponding activity cost pool is not empty. int ac2Count = ac.Length; // B_prime[i] is the set of resources (indexes) that go into // activity cost pool i, given any product mix q. Note that // B_prime will be a subset of B. List <List <int> > B_prime = new List <List <int> >(ac2Count); // D_prime[i] is the index of the resource that constitutes // the driver for activity cost pool i, given any product mix q. // Note that D_prime will be a subset of D. CURRENTLY ONLY WORKS // FOR BIGPOOL METHOD. List <List <int> > D_prime = new List <List <int> >(ac2Count); // Vector of activity cost pools that is filtered to remove // cost pools with zero resources. List <double> ac_prime = new List <double>(ac2Count); for (int i = 0; i < ac2Count; ++i) { if (ac[i] > 0.0) { B_prime.Add(new List <int>(B[i])); D_prime.Add(new List <int>(D[i])); ac_prime.Add(ac[i]); } } // Choose one resource for each activity cost pool where the // resource usage is not zero. Use the First() function to get // the driver for the largest resource with non-zero usage. // NOTE: The First() function will throw an InvalidOperationException // if it does not find any resources that match the predicate condition. // However, if our method is programmed correctly, it should ALWAYS // find at least one resource per pool that has non-zero usage in TRU_F. // If it doesn't, that means the activity cost pool is empty. List <int> possibleDrivers2 = new List <int>(B_prime.Count); foreach (List <int> l in B_prime) { foreach (int resource in l) { //possibleDrivers2.Add( l.First( resource => TRU_F[resource] > 0.0 ) ); if (TRU_F[resource] > 0.0) { possibleDrivers2.Add(resource); break; } } } // Big pool method. If there is a driver for a cost pool and that // resource has zero usage, replace that driver with one from the set // B that has positive usage. if (this.r == 0) { // I had code here for determining if an alternate driver // was selected. I deleted all references to it because // I didn't use it anywhere. // Iterate over D_prime2, which should be a list of (lists of length 1). // If the resource usage for any element in D_prime2 is zero, replace it with // the corresponding element in possibleDrivers. for (int i = 0; i < D_prime.Count; ++i) { for (int j = 0; j < D_prime[i].Count; ++j) { if (TRU_F[D_prime[i][j]] == 0.0) { D_prime[i][j] = possibleDrivers2[i]; } } } } // Indexed drivers. else if (this.r == 1) { // First, go through the lists in D and remove resources for which there // is zero usage D_prime = D_prime.Select(list => list.Where(resource => TRU_F[resource] != 0.0).ToList()).ToList(); // If any list in D_prime is empty, find a resource in the corresponding // list in B_prime that has non-zero resource usage, and add that one resource // to the empty list in D_prime. for (int i = 0; i < D_prime.Count; ++i) { if (D_prime[i].Count == 0) { D_prime[i].Add(possibleDrivers2[i]); } } } else { throw new ApplicationException("Invalid value of r (driver selection method) in consistency loop."); } #endregion #region Flowchart 7.3(e)-(f) - Compute false system rates and product costs Dictionary <int, double> rates = new Dictionary <int, double>(); for (int d = 0; d < D_prime.Count; ++d) { int dLength = D_prime[d].Count; double poolNumerator = ac_prime[d] / dLength; for (int d2 = 0; d2 < dLength; ++d2) { int resIndx = D_prime[d][d2]; rates.Add(resIndx, poolNumerator / TRU_F[resIndx]); } } RowVector PC_R = new RowVector(ip.CO); foreach (KeyValuePair <int, double> kvp in rates) { for (int co = 0; co < PC_R.Dimension; ++co) { //costs2[co] += res_cons_pat[kvp.Key, co] * kvp.Value; PC_R[co] += res_cons_pat[kvp.Key, co] * kvp.Value; } } #endregion return(PC_R); }
/// <summary> /// Randomly generates a firm object (production technology and output market parameters). /// </summary> /// <param name="ip">A pointer to the collection of input parameters.</param> /// <param name="FirmID">Unique identifier for this firm (run number)</param> public Firm(InputParameters ip, int FirmID) { // Choose random values for DISP2 (the top DISP1 resources // account for DISP2 percent of total resource costs), and // density (sparsity) of resource consumption pattern matrix this.g = GenRandNumbers.GenUniformDbl(ip.DISP2_MIN, ip.DISP2_MAX); this.d = GenRandNumbers.GenUniformDbl(ip.DNS_MIN, ip.DNS_MAX); // Generate the true product margins and the true, optimal // decision vector. Keep generating new margins until there // is at least one product in the optimal mix. RowVector MAR, DECT0; do { MAR = this.GenMargins(ip); DECT0 = MAR.Map(x => (x < 1.0) ? 0.0 : 1.0); } while (DECT0.TrueForAll(x => x == 0.0)); // Generate vector of maximum production quantities this.mxq = this.GenMXQ(ip); // And associated vector of optimal production quantities ColumnVector QT = mxq.ewMultiply(DECT0); // Flowchart 5.1 - Create resource consumption pattern matrix this.res_cons_pat = GenResConsPat(ip); // Flowchart 5.2 - Compute TRU // Calculate vector of total units of resource // consumption, by product ColumnVector TRU = this.CalcResConsumption(QT); // Flowchart 5.3 - Compute MAXRU // Calculate resource consumption under the assumption // that all products are produced at maximum quantity ColumnVector MAXRU = this.CalcResConsumption(mxq); RowVector RCC, PC_B, RCCN; double TCT0; #region Flowchart 5.4 - Generate RCC, RCU, and RCCN /* -------------------------------- */ // Flowchart 5.4(a)-(g) // Generate vector of total resource costs (RCC) RCC = GenRCC(ip); /* -------------------------------- */ // Flowchart 5.4(h) // Now generate unit resource costs (RCU) by doing element-wise // division of RCC by MAXRU this.rcu = RCC.Map((x, i) => x / MAXRU[i]); /* -------------------------------- */ // Flowchart 5.4(i) // Compute new RCC vector (RCCN) based on unit resource // costs (RCU) and true unit resource consumption (TRU) RCCN = this.rcu.ewMultiply(TRU); // Check to see if the first resource (RCCN[0]) is the largest. // If not, increase RCU[0] by just enough to make it so. if (RCCN[0] < RCCN.Skip(1).Max() + 1) { RCCN[0] = Math.Ceiling(RCCN.Max()) + 1.0; this.rcu[0] = RCCN[0] / TRU[0]; } #endregion // Flowchart 5.5 - Calculate PC_B // Calculate true unit product costs PC_B = this.CalcTrueProductCosts(); // Flowchart 5.6 - Compute total costs TCT0 // Compute total costs TCT0 = this.CalcTotCosts(QT); // Flowchart 5.7 - Rename RCCN to RCC RCC = RCCN; initial_rcc = RCC; #region Flowchart 5.8 - Calculate SP, TRV0, PROFITT0 // Calculate product selling prices, total revenue, and profit this.sp = PC_B.ewMultiply(MAR); double TRV0 = this.sp * QT; this.profitt0 = TRV0 - TCT0; #endregion // 5.9(a) Create RANK vector // Note: this method provides a stable sort. It's important to use a stable sort. // LOOKUP IN VERSION.TXT WHY IT'S IMPORTANT TO USE A STABLE SORT HERE. initial_rank = Enumerable.Range(0, RCC.Dimension).OrderByDescending(i => RCC[i]).ToArray(); #region Flowchart 5.9(b) - Create RES_CONS_PAT_PRCT this.res_cons_pat_prct = new RectangularMatrix(ip.RCP, ip.CO); for (int r = 0; r < this.res_cons_pat.RowCount; ++r) { RowVector rv = this.res_cons_pat.Row(r); if (TRU[r] != 0.0) { rv = rv.Map((alt_ij, col) => alt_ij * QT[col] / TRU[r]); if (Math.Abs(rv.Sum() - 1.0) > 0.01) { throw new ApplicationException("Sum of row of RES_CONS_PAT_PRCT not equal to 1."); } } else { rv = rv.Map(alt_ij => 0.0); } this.res_cons_pat_prct.CopyRowInto(rv, r); } #endregion #region Flowchart 5.9(c) - Create correlation matrix // Create correlation matrix for rows of RES_CONS_PAT_PRCT MultivariateSample mvs = new MultivariateSample(ip.RCP); for (int c = 0; c < this.res_cons_pat_prct.ColumnCount; ++c) { mvs.Add(this.res_cons_pat_prct.Column(c)); } this.pearsoncorr = new SymmetricMatrix(ip.RCP); for (int i = 0; i < mvs.Dimension; ++i) { for (int j = i; j < mvs.Dimension; ++j) { //PearsonCorr[i, j] = mvs.PearsonRTest( i, j ).Statistic; this.pearsoncorr[i, j] = mvs.TwoColumns(i, j).PearsonRTest().Statistic; } } #endregion // Flowchart 5.10 - Logging true system // Note: I'm deliberately passing copies of the fields MXQ, SP, etc. Output.LogFirm( ip, this, FirmID, MAR, DECT0, TRV0, TCT0, profitt0, RCC); }
/// <summary> /// Implements step 5.3 of the flowchart: Generates a [1 x RCP] vector of /// total resource costs by resource. /// </summary> /// <param name="ip">An input parameters object.</param> private RowVector GenRCC(InputParameters ip) { bool throwAway; int numThrows = 0; List <double> rcc; // repeat the following loop until a suitable vector // RCC is generated. do { /* -------------------------------- */ // Flowchart 5.4(b) // Calculate total resource cost of first DISP1 resources double topTR = G * ip.TR; // Calculate minimum allowable resource cost in first // DISP1 resources double rmin = (1.0 - G) * ip.TR / (ip.RCP - ip.DISP1); // The following is an upward adjustment of rmin. // Without this, the values of the resources in the // remaining resources have too little variance. // It checks how much room there is to adjust rmin, // and takes 2.5% of that room. The 2.5% was determined // through trial and error. double maxValOfLargestElem = topTR - ((ip.DISP1 - 1.0) * rmin); rmin += (maxValOfLargestElem - rmin) * 0.025; /* -------------------------------- */ // Flowchart 5.4(c) // Generate the first DISP1 random numbers List <double> temp1 = new List <double>(); for (int i = 1; i <= ip.DISP1 - 1; ++i) { double rmax = (topTR - temp1.Sum()) - ((ip.DISP1 - i) * rmin); if (rmax < rmin) { throw new ApplicationException("rmax less than rmin"); } double ri = GenRandNumbers.GenUniformDbl(rmin, rmax); temp1.Add(ri); } // The final element is computed to ensure that the total // in temp1 is topTR temp1.Add(topTR - temp1.Sum()); // Move the biggest resource to the front double temp1Max = temp1.Max(); if (!temp1.Remove(temp1Max)) { throw new ApplicationException("Could not remove largest element."); } temp1.Insert(0, temp1Max); // SOME CHECKS ON THE NUMBERS if (Math.Abs(temp1.Sum() - ip.TR * G) > 1.0) { throw new ApplicationException("Sum of first DISP1 resources not correct."); } if (temp1.Min() < (1 - G) * ip.TR / (ip.RCP - ip.DISP1)) { throw new ApplicationException("Min element too small."); } /* -------------------------------- */ // Flowchart 5.4(d) List <double> temp2 = new List <double>(); for (int i = 0; i < ip.RCP - ip.DISP1; ++i) { temp2.Add(GenRandNumbers.GenUniformDbl(0.05, 0.95)); } temp2.Normalize(); temp2.MultiplyBy((1.0 - G) * ip.TR); double temp1Min = temp1.Min(); while (temp2.Max() - temp1.Min() > 1.0) { // Sort the list in descending order temp2.Sort(); temp2.Reverse(); for (int i = 0; i < temp2.Count / 2; ++i) { double overage = Math.Max(temp2[i] - temp1Min, 0.0); temp2[i] -= overage; temp2[temp2.Count - 1 - i] += overage; } } temp2.Shuffle(); // SOME CHECKS if (Math.Abs(temp2.Sum() - ip.TR * (1.0 - G)) > 1.0) { throw new ApplicationException("Sum of small resources not correct."); } /* -------------------------------- */ // Flowchart 5.4(e) rcc = new List <double>(ip.RCP); rcc.AddRange(temp1); rcc.AddRange(temp2); /* -------------------------------- */ // Flowchart 5.4(f) throwAway = rcc.Exists(x => x < 1.0); // SOME CHECKS if (rcc.Min() < 0.0) { throw new ApplicationException("Negative element in RCC."); } if (throwAway) { ++numThrows; } } while (throwAway); return(new RowVector(rcc)); }
/// <summary> /// Generates a random vector of capacities (maximum production /// quantities). Each element is drawn from discrete U[10,40]. /// </summary> /// <param name="ip">The current InputParameters object</param> /// <returns>A [CO x 1] vector, each element drawn from /// the *discrete* distribution U[10,40].</returns> private ColumnVector GenMXQ(InputParameters ip) { return(new ColumnVector(ip.CO).Map(x => GenRandNumbers.GenUniformInt(10, 40))); }
/// <summary> /// Generates a vector of product margins. Each element is /// U[ip.MARLB, ip.MARUB]. Values less than (greater) than one indicate /// products that generate losses (profits). /// </summary> /// <param name="ip">The current InputParameters object</param> /// <returns>A [1 x CO] vector, each element drawn from /// the distribution U[ip.MARLB, ip.MARUB].</returns> private RowVector GenMargins(InputParameters ip) { return(new RowVector(ip.CO) .Map(x => GenRandNumbers.GenUniformDbl(ip.MARLB, ip.MARUB))); }
/// <summary> /// Generates a resource consumption pattern matrix /// </summary> /// <param name="ip">The current InputParameters object</param> private RectangularMatrix GenResConsPat(InputParameters ip) { bool throwAway; int numThrows = 0; RectangularMatrix outputMatrix; do { throwAway = false; outputMatrix = new RectangularMatrix(ip.RCP, ip.CO); // Flowchart 5.1(a): Generate vector X RowVector X = GenRandNumbers.GenStdNormalVec(ip.CO); // The following code is used in both 5.1(b) and 5.1(c): RowVector[] Y = new RowVector[ip.RCP - 1]; RowVector[] Z = new RowVector[Y.Length]; for (int i = 0; i < Y.Length; ++i) { Y[i] = GenRandNumbers.GenStdNormalVec(ip.CO); } // Flowchart 5.1(b): Generate (DISP1 - 1) vectors Y // Then create Z vectors based on X and Y double COR1 = GenRandNumbers.GenUniformDbl(ip.COR1LB, ip.COR1UB); double sqrtConstant1 = Math.Sqrt(1 - COR1 * COR1); for (int i = 0; i < ip.DISP1 - 1; ++i) { Z[i] = (COR1 * X) + (sqrtConstant1 * Y[i]); } // Flowchart 5.1(c): Generate (RCP - DISP1) vectors Y // Then create the remaining Z vectors based on X and Y double COR2 = GenRandNumbers.GenUniformDbl(ip.COR2LB, ip.COR2UB); double sqrtConstant2 = Math.Sqrt(1 - COR2 * COR2); for (int i = ip.DISP1 - 1; i < Z.Length; ++i) { Z[i] = (COR2 * X) + (sqrtConstant2 * Y[i]); } // Flowchart 5.1(d): // Take the absolute values of X and the Z's and // scale both by 10.0. X = X.Map(x => 10.0 * Math.Abs(x)); for (int i = 0; i < Z.Length; ++i) { Z[i] = Z[i].Map(z => 10.0 * Math.Abs(z)); } // Round X and the Z's to integers X = X.Map(x => Math.Ceiling(x)); for (int i = 0; i < Z.Length; ++i) { Z[i] = Z[i].Map(z => Math.Ceiling(z)); } // Flowchart 5.1(e): // Now punch out values in the Z's at random to make // the matrix sparse for (int i = 0; i < Z.Length; ++i) { Z[i] = Z[i].Map(x => ((GenRandNumbers.GenUniformDbl() < D) ? x : 0.0)); } // Flowchart 5.1(f): // Copy X into first row of outputMatrix. outputMatrix.CopyRowInto(X, 0); // Copy the Z's into the remaining rows of outputMatrix. for (int i = 0; i < Z.Length; ++i) { outputMatrix.CopyRowInto(Z[i], i + 1); } // Ensure that the first row has no zeros // There is a very small probability of getting a zero with // the Ceiling function, but given that there are a finite // number of double-precision floating point numbers, it // is not impossible to get a 0.0. double[] firstRow = outputMatrix.Row(0).ToArray(); if (Array.Exists(firstRow, x => x == 0.0)) { throwAway = true; break; } // Ensure that each *row* has at least one non-zero entry for (int i = 0; i < outputMatrix.RowCount; ++i) { double[] nextRow = outputMatrix.Row(i).ToArray(); if (Array.TrueForAll(nextRow, x => x == 0.0)) { throwAway = true; break; } } // Ensure that each *column* has at least one non-zero entry // Technically, this check is redundant, as the first row, X, // is not supposed to have any zero entries. But just to be // on the safe side... for (int j = 0; j < outputMatrix.ColumnCount; ++j) { double[] nextCol = outputMatrix.Column(j).ToArray(); if (Array.TrueForAll(nextCol, x => x == 0.0)) { string s = "There is a column with all zeros. " + "That should not happen since the first row is " + "supposed to have no zeros."; throw new ApplicationException(s); } } if (throwAway) { ++numThrows; } } while (throwAway); Console.WriteLine("RES_CONS_PAT: {0} Throw aways\n", numThrows); return(outputMatrix); }
static void Main(string[] args) { #region Console header DrawASCIIart(); #endregion #region Read input file and create InputParameters object FileInfo inputFile = new FileInfo(Environment.CurrentDirectory + @"\input.txt"); if (!inputFile.Exists) { Console.WriteLine("Could not find input file: \n{0}", inputFile.FullName); Console.WriteLine("Aborting. Press ENTER to end the program."); Console.ReadLine(); return; } InputParameters ip = new InputParameters(inputFile); #endregion #region Make a copy of the input file // We found it helpful to make a copy of the input file every time we ran the // simulation. We stamp the copy's filename with the date and time so that // we know which results files correspond to which input file. DateTime dt = DateTime.Now; string inputFileCopyName = String.Format( "input {0:D2}-{1:D2}-{2:D4} {3:D2}h {4:D2}m {5:D2}s, seed {6:G}.txt", dt.Month, dt.Day, dt.Year, dt.Hour, dt.Minute, dt.Second, GenRandNumbers.GetSeed() ); FileInfo inputFileCopy = new FileInfo(Environment.CurrentDirectory + @"\" + inputFileCopyName); inputFile.CopyTo(inputFileCopy.FullName, true); File.SetCreationTime(inputFileCopy.FullName, dt); File.SetLastWriteTime(inputFileCopy.FullName, dt); #endregion #region Create output files Output.CreateOutputFiles(ip); #endregion #region Generate Sample of Firms and their Cost Systems Firm[] sampleFirms = new Firm[ip.NUM_FIRMS]; for (int firmID = 1; firmID <= ip.NUM_FIRMS; ++firmID) { Console.WriteLine( "Starting firm {0:D3} of {1}", firmID + 1, sampleFirms.Length ); Firm f = new Firm(ip, firmID); sampleFirms[firmID - 1] = f; for (int a_indx = 0; a_indx < ip.ACP.Count; ++a_indx) { int a = ip.ACP[a_indx]; for (int p_indx = 0; p_indx < ip.PACP.Count; ++p_indx) { int p = ip.PACP[p_indx]; for (int r_indx = 0; r_indx < ip.PDR.Count; ++r_indx) { int r = ip.PDR[r_indx]; // Create a cost system CostSys costsys = new CostSys(ip, f, a, p, r); f.costSystems.Add(costsys); int costSysID = f.costSystems.Count; Output.LogCostSys(costsys, firmID, costSysID); // Generate a starting decision for the cost system. RowVector startingDecision; if (ip.STARTMIX == 0) { startingDecision = f.CalcOptimalDecision(); } else { var ones = Enumerable.Repeat(1.0, ip.CO).ToList(); startingDecision = new RowVector(ones); for (int i = 0; i < startingDecision.Dimension; ++i) { if (GenRandNumbers.GenUniformDbl() < ip.EXCLUDE) { startingDecision[i] = 0.0; } } } /* Examine error in cost from implementing this decision. * Assume the firm implements the decision startingDecision. Upon * doing so, it will observe total resource consumption. It will then * allocate resources to cost pools, as per the B parameter of the cost * system, choose drivers as per the D parameter of the cost system, * and then allocate resources to cost objects and compute reported costs. * The reported costs are returned as PC_R. The difference * between these and the true benchmark costs (PC_B) is used to compute * the mean percent error in costs. */ RowVector PC_R = costsys.CalcReportedCosts(ip, startingDecision); RowVector PC_B = f.CalcTrueProductCosts(); double MPE = PC_B.Zip(PC_R, (pc_b, pc_r) => Math.Abs(pc_b - pc_r) / pc_b).Sum() / PC_B.Dimension; Output.LogCostSysError(costsys, firmID, costSysID, startingDecision, PC_B, PC_R, MPE); /* Assume the firm implements the decision startingDecision. Upon * doing so, it will observe total resource consumption. It will then * allocate resources to cost pools, as per the B parameter of the cost * system, choose drivers as per the D parameter of the cost system, * and then allocate resources to cost objects and compute reported costs. * The reported costs are returned as PC_R. Upon observing the * reported costs, the firm may wish to update its original decision. When * it implements the updated decision, costs will change again. The outcome * of this process will either be an equilibrium decision (fixed point), or * a cycle of decisions. */ (CostSystemOutcomes stopCode, RowVector endingDecision) = costsys.EquilibriumCheck(ip, startingDecision); Output.LogCostSysLoop(costsys, firmID, costSysID, startingDecision, endingDecision, stopCode); } } } } #endregion Console.WriteLine("Writing output files..."); Output.WriteOutput(); Console.WriteLine("Done!"); }
/// <summary> /// Writes summary information about a firm. Writes RCC and RCU vectors for /// the firm in the RESCON file. Writes info about the firm's /// products (margins, capacities, selling prices). /// </summary> /// <param name="ip">An InputParameters object.</param> /// <param name="firm">The firm object whose data will be logged.</param> /// <param name="firmID">A unique identifier for this firm</param> /// <param name="MAR">Vector of product margins. Products with /// margins >= 1 are produced.</param> /// <param name="DECT0">Vector of 0 and 1's indicating which products /// are produced.</param> /// <param name="revenue">Revenue realized when producing the benchmark product mix</param> /// <param name="totalCost">Total cost incurred when producing the benchmark product mix</param> /// <param name="benchProfit">Profit realized when producing the benchmark product mix</param> /// <param name="RCC">Vector of resource costs</param> public static void LogFirm( InputParameters ip, Firm firm, int firmID, RowVector MAR, RowVector DECT0, double revenue, double totalCost, double benchProfit, RowVector RCC) { #region Log summary information for the firm int numProdInMix = DECT0.Count(x => x == 1.0); sb_Firm_SUM.AppendFormat( "{0},{1},{2},{3},{4},{5:F2},{6:F2},{7:F2},{8},{9}", firmID, firm.G, firm.D, ip.RCP, ip.CO, revenue, totalCost, benchProfit, numProdInMix, Environment.NewLine ); #endregion #region Log resource consumption information for the firm sb_Firm_RESCON.AppendFormat( "{0},{1},{2},{3},{4},{5}", firmID, firm.G, firm.D, RCC.ToCSVString(false), firm.RCU.ToCSVString(false), Environment.NewLine ); #endregion #region Log product information for the firm #region Create rank vectors // Rank the products by value (by total profit) RowVector RANK_BY_VAL; { // Some algebra: the profit of a product is // (SP - PC_B) x QT // = (SP - SP/MAR) x (MXQ x DECT0) // = SP (1 - 1/MAR) x (MXQ x DECT0) // where all operations are element-wise var unitProfit = firm.SP.Zip(MAR, (sp, mar) => sp * (1.0 - (1.0 / mar))); var productionQty = firm.MXQ.Zip(DECT0, (mxq, dect0) => mxq * dect0); var totProfit = unitProfit.Zip(productionQty, (pi, q) => pi * q); double[] PROFIT = totProfit.ToArray(); var rank = Enumerable.Range(0, ip.CO).Select(x => (double)x); // If the product is not produced, set its rank value // to ip.CO var rank2 = rank.Zip(DECT0, (r, dect0) => (dect0 == 1.0) ? r : (double)ip.CO); double[] rank_by_val = rank2.ToArray(); Array.Sort(PROFIT, rank_by_val); Array.Reverse(rank_by_val); RANK_BY_VAL = new RowVector(rank_by_val); } // Rank the products by margin RowVector RANK_BY_MAR; { double[] rank_by_mar = Enumerable.Range(0, ip.CO).Select(x => (double)x).ToArray(); double[] mar = MAR.ToArray(); Array.Sort(mar, rank_by_mar); Array.Reverse(rank_by_mar); RANK_BY_MAR = new RowVector(rank_by_mar); } #endregion sb_Firm_PRODUCT.AppendFormat( "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9}", firmID, firm.G, firm.D, MAR.ToCSVString(false), firm.MXQ.ToCSVString(true), firm.SP.ToCSVString(false), DECT0.ToCSVString(true), RANK_BY_VAL.ToCSVString(true), RANK_BY_MAR.ToCSVString(true), Environment.NewLine ); #endregion }