Click here to Skip to main content
15,887,214 members
Please Sign up or sign in to vote.
4.00/5 (1 vote)
See more:
Hi Friends,

My requirement is, I need to perform Monte Carlo analysis to find probability of success. Each year I need to run for 1000 iteration with pre simulated values. If say the analysis horizon is 20 years its taking to much time to finish the task. The complexity I feel is current year ending value should be next year starting value. My question is how can I apply task parallelism or threading to increase the performance. I will briefly explain the program logic

Year 1
Iteration 1
Starting Value: 10
//calculation logic
Ending Value: 12
Iteration 2
Starting Value is 12
//calculation logic
Ending Value is 13

like wise 1000 iterations.

Year 2
Iteration 1
Starting value : 12 ( previous year iteration 1 ending value)
//calculation logic
ending value :14
Iteration 2
Starting value : 13 ( previous year iteration 2 ending value)
//calculation logic
ending value :16
like wise 1000 iterations

Need to store all years 1000 iterations values and need to sort the values in an ascending order and take 5th 50th and 95th value from each year array.

Sample code is given below.
Please suggest some good approach which can increase the performance.

What I have tried:

Sample code
C#
    static void Main(string[] args)
    {
        List<LinearGrowth> lstlg = new List<LinearGrowth>();
        Reference.dividendP = 0.11;
        Reference.interestP = 0.07;
        Reference.feeP = 0.05;
        List<CurrentAllocation> lstca = new List<CurrentAllocation>();
        lstca.Add(new CurrentAllocation { ID = 1, aloc = 0.3 });
        lstca.Add(new CurrentAllocation { ID = 2, aloc = 0.2 });
        lstca.Add(new CurrentAllocation { ID = 3, aloc = 0.4 });
        lstca.Add(new CurrentAllocation { ID = 4, aloc = 0.1 });
        List<Probability> lstprob = new List<Probability>();

        DateTime dt = DateTime.Now;
        Console.WriteLine(dt);
        double pval = 0;
        int loopcount = -1;
        for (int i = DateTime.Now.Year; i <= DateTime.Now.Year + 20; i++)
        {
            Console.WriteLine("Year: {0}", i);
            Probability objprob = new Probability();
            lstlg = new List<LinearGrowth>();
            for (int j = 0; j <= 100; j++)
            {
                if (i == DateTime.Now.Year)
                {
                    if (i == DateTime.Now.Year && j == 0)
                        pval = 1000;
                    else
                        pval = lstlg[j- 1].endingvalue;
                }
                else
                {
                    pval = lstprob[loopcount].yearvalues[j].endingvalue;
                }


                LinearGrowth lg = new LinearGrowth();
                lg = CalculationMethods.Montecarlo(lstca, pval);
                lstlg.Add(lg);
                Console.WriteLine("Starting Value:{0} Ending Value {1}", Math.Round(lg.startingvalue,2), Math.Round(lg.endingvalue,2));
            }
            objprob.year = i;
            objprob.yearvalues = lstlg.OrderBy(o => o.endingvalue).ToList<LinearGrowth>();
            lstprob.Add(objprob);
            Console.WriteLine("Year: {0}", i);
            Console.WriteLine("5th:{0} 50th: {1}  95{2}", objprob.yearvalues[4].endingvalue, objprob.yearvalues[49].endingvalue, objprob.yearvalues[94].endingvalue);
            loopcount = loopcount + 1;
        }
        Console.WriteLine(DateTime.Now);
        TimeSpan t = DateTime.Now - dt;
        Console.WriteLine(t.Seconds);
        Console.ReadLine();
    }
}


C#
public class LinearGrowth
   {
       public double startingvalue { get; set; }
       public double fees { get; set; }
       public double tax { get; set; }
       public double endingvalue { get; set; }
   }
   public class Reference
   {
       public static double interestP { get; set; }
       public static double dividendP { get; set; }
       public static double feeP { get; set; }
   }
   public class Probability
   {
       public int year { get; set; }
       public List<LinearGrowth> yearvalues { get; set; }
   }

   public class CurrentAllocation
   {
       public int ID { get; set; }
       public double aloc { get; set; }
   }
   public class CalculationMethods
   {
       public static LinearGrowth Montecarlo(List<CurrentAllocation> lstca, double pv)
       {
           LinearGrowth lg = new LinearGrowth();
           Random rnd = new Random();
           double allocation, interest, dividend, fees, annualreturn;
           double sum_int, sum_div, sum_fee,sum_alloc, tax;
           sum_int = sum_div = sum_fee = tax =sum_alloc= 0;
           for (int i = 0; i < lstca.Count; i++)
           {
               allocation = pv * lstca[i].aloc;
               annualreturn = allocation * rnd.NextDouble();
               interest = allocation * Reference.interestP;
               dividend = allocation * Reference.dividendP;
               fees = allocation * Reference.feeP;
               sum_int += interest;
               sum_div += dividend;
               sum_fee += fees;
               sum_alloc += annualreturn;
           }
           tax = (sum_int + sum_div) * 0.15;
           lg.startingvalue = pv;
           lg.fees = sum_fee;
           lg.tax = tax;
           lg.endingvalue = pv + sum_int + sum_div - tax - sum_fee;
           return lg;
       }
   }
Posted
Updated 11-Mar-16 11:30am
Comments
Matt T Heffron 10-Mar-16 17:17pm    
Why is there a performance consideration?
I just ran your code (debug mode with 1000, not 100 iterations) and it completed in about 4 seconds!
Is there something significantly harder being performed that you aren't telling us about?
jinesh sam 10-Mar-16 19:44pm    
Yes calculation part is harder..the code which i given is just a prototype. In real the returing object (linear growth) consists of around 15 properties and that too again array of objects each contain seprate logic such as contribution,distribution,liabilities goals etc.

For starters, you have a very subtle bug that would cause incorrect results if you happened to start this late in the evening on December 31! You are calling DateTime.Now.Year in lots of places. That could change during running, leading to incorrect results! You have already captured DateTime.Now into dt near the beginning of Main(), use that everywhere else, like:
C#
for (int i = dt.Year; i <= dt.Year + 20; i++)

Since this won't help with your performance issue, I'll leave this suggestion at this point. ;-)

Performance:
This was an interesting problem so I spent (more than I should have) time on it.

It appears that for Year 1, an iteration's starting value is the previous iteration's value, while for the remaining years an iteration's starting value is the previous year's value for the same iteration #.
So, first run a Year 1 loop, chaining the starting values from iteration to iteration. As each iteration ends, then start a Task that computes the remaining years of that iteration.
For 1000 iterations, this will enable about a 1000x parallelism! NOTE: You will not get this much parallelism unless you have more than 1000 cores! But it should be able to use just about as much parallel capability as the system has to offer.
Another consideration is that any collections that are not year-specific will probably need to be changed to thread-safe collections (unless they aren't used until everything else is done).

So here's what I came up with:
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ConsoleApplication11
{
  class Program
  {
    const int Iterations = 100;
    const int Years = 20;
    const double InitialValue = 1000;

    static void Main(string[] args)
    {
      Reference.dividendP = 0.11;
      Reference.interestP = 0.07;
      Reference.feeP = 0.05;
      List<CurrentAllocation> lstca = new List<CurrentAllocation>() {
                                            new CurrentAllocation { ID = 1, aloc = 0.3 },
                                            new CurrentAllocation { ID = 2, aloc = 0.2 },
                                            new CurrentAllocation { ID = 3, aloc = 0.4 },
                                            new CurrentAllocation { ID = 4, aloc = 0.1 }
                                          };

      DateTime dt = DateTime.Now;
      Console.WriteLine(dt);
      double pval = InitialValue;
      Task<List<LinearGrowth>>[] workers = new Task<List<LinearGrowth>>[Iterations];
      List<LinearGrowth>[] growthByYear = new List<LinearGrowth>[Years];
      List<LinearGrowth> firstYearGrowth = new List<LinearGrowth>(Iterations);
      Random rand = new Random(CalculationMethods.MakeSeed());  // one, common, random number generator for this Year 1 sequence
      for (int iter = 0; iter < Iterations; iter++)
      {
        LinearGrowth lg = CalculationMethods.Montecarlo(lstca, pval, rand);
        firstYearGrowth.Add(lg);
        workers[iter] = CalculationMethods.SpinUpGrowthOverYearsTask(dt.Year + 1, Years - 1, pval, lstca);
        pval = lg.endingvalue;
      }

      Task.WaitAll(workers);
      growthByYear[0] = firstYearGrowth;
      for (int yearOffset = 1; yearOffset < Years; yearOffset++)
      {
        growthByYear[yearOffset] = new List<LinearGrowth>(Iterations);
      }

      // this basically "transposes" from iteration collections to year collections.
      foreach (var iterTask in workers)
      {
        var yearsResults = iterTask.Result;
        int yearOffset = 1;
        foreach (var result in yearsResults)
        {
          growthByYear[yearOffset++].Add(result);
        }
      }

      List<Probability> lstprob = new List<Probability>();
      int year = dt.Year;
      foreach (var yearGrowth in growthByYear)
      {
        Probability objprob = new Probability();
        objprob.year = year;
        objprob.yearvalues = yearGrowth.OrderBy(o => o.endingvalue).ToList();
        lstprob.Add(objprob);
        // TODO: The yearvalues index "magic numbers" should be replaced by named constants!
        // I'll leave that to you to choose good names!
        Console.WriteLine("Year: {0}\n5th: {1} 50th: {2}  95th: {3}",
                          year++,
                          objprob.yearvalues[4].endingvalue,
                          objprob.yearvalues[49].endingvalue,
                          objprob.yearvalues[94].endingvalue);
      }

      DateTime end = DateTime.Now;
      Console.WriteLine("Ending time: {0}", end);
      TimeSpan t = end - dt;
      Console.WriteLine("Elapsed time: {0}", t);
      Console.ReadLine();
    }
  }

  public class CalculationMethods
  {
    public static int MakeSeed()
    {
      long ticks = DateTime.Now.Ticks;
      return (int)((ticks >> 32) ^ ticks);
    }
    public static Task<List<LinearGrowth>> SpinUpGrowthOverYearsTask(int startYear, int numberOfYears, double initial, IEnumerable<CurrentAllocation> currentAllocations)
    {
      Random ran = new Random(MakeSeed());  // one, common, random number generator for this Task, because Random.NextDouble() isn't thread-safe!
      return Task.Run<List<LinearGrowth>>(() => GrowthOverYears(startYear, numberOfYears, initial, currentAllocations, ran));
    }
    public static List<LinearGrowth> GrowthOverYears(int startYear, int numberOfYears, double initial, IEnumerable<CurrentAllocation> currentAllocations, Random ran)
    {
      List<LinearGrowth> result = new List<LinearGrowth>(numberOfYears);
      for (int yearOffset = 0; yearOffset < numberOfYears; yearOffset++)
      {
        LinearGrowth lg = CalculationMethods.Montecarlo(currentAllocations, initial, ran);
        result.Add(lg);
        initial = lg.endingvalue;
      }
      return result;
    }

    // I brought the random number generator OUT so there's less chance of the same sequence on sequential calls.
    public static LinearGrowth Montecarlo(IEnumerable<CurrentAllocation> currentAllocations, double pv, Random ran)
    {
      LinearGrowth lg = new LinearGrowth();
      double allocation, interest, dividend, fees, annualreturn;
      double sum_int, sum_div, sum_fee, sum_alloc, tax;
      sum_int = sum_div = sum_fee = tax = sum_alloc = 0;
      foreach (var ca in currentAllocations)
      {
        allocation = pv * ca.aloc;
        annualreturn = allocation * ran.NextDouble();
        interest = allocation * Reference.interestP;
        dividend = allocation * Reference.dividendP;
        fees = allocation * Reference.feeP;
        sum_int += interest;
        sum_div += dividend;
        sum_fee += fees;
        sum_alloc += annualreturn;
      }
      tax = (sum_int + sum_div) * 0.15;
      lg.startingvalue = pv;
      lg.fees = sum_fee;
      lg.tax = tax;
      lg.endingvalue = pv + sum_int + sum_div - tax - sum_fee;
      return lg;
    }
  }

  public class LinearGrowth
  {
    public double startingvalue { get; set; }
    public double fees { get; set; }
    public double tax { get; set; }
    public double endingvalue { get; set; }
  }
  public class Reference
  {
    public static double interestP { get; set; }
    public static double dividendP { get; set; }
    public static double feeP { get; set; }
  }
  public class Probability
  {
    public int year { get; set; }
    public List<LinearGrowth> yearvalues { get; set; }
  }
  public class CurrentAllocation
  {
    public int ID { get; set; }
    public double aloc { get; set; }
  }
}

Note: when you divide this into multiple Tasks, there's no guarantee that the Console.WriteLine(...) from each Task, will all come out in the same sequence as sequential. Caveat emptor! (Above, I removed all of the Console.WriteLine(...) from the tasks.)

Enjoy!
 
Share this answer
 
Comments
jinesh sam 13-Mar-16 0:41am    
Thanks a lot Matt for your time and guidance.
Hi,

For optimizing the code and the time taken by your code you can go for some optimizing technics like "Parallel Programming" in place of loops.

Reference link to Parallel programming

Thanks,
Sisir Patro
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900