示例#1
0
        /// <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());
        }
示例#2
0
        /// <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
        }
示例#3
0
        /// <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);
        }
示例#4
0
        /// <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);
        }
示例#5
0
        /// <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);
        }
示例#6
0
        /// <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));
        }
示例#7
0
 /// <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)));
 }
示例#8
0
 /// <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)));
 }
示例#9
0
        /// <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);
        }
示例#10
0
        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!");
        }
示例#11
0
        /// <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
        }