3
\$\begingroup\$

I have implemented the Simpson's rule for numerical integration.

Check this video for the implemented function.

namespace Simpsons_method_of_integration
{
    //https://www.youtube.com/watch?v=ns3k-Lz7qWU
    using System;

    public class Simpson
    {
        private double Function(double x)
        {
            return 1.0 / (1.0 + Math.Pow(x, 5)); //Define the function f(x)
        }

        public double Compute(double a, double b, int n)
        {
            double[] x = new double[n + 1];

            double delta_x = (b - a) / n;

            x[0] = a;

            for (int j = 1; j <= n; j++)//calculate the values of x1, x2, ...xn
            {
                x[j] = a + delta_x * j;
            }

            double sum = Function(x[0]);

            for (int j = 1; j < n; j++)
            {
                if (j % 2 != 0)
                {
                    sum += 4 * Function(x[j]);
                }
                else
                {
                    sum += 2 * Function(x[j]);
                }
            }

            sum += Function(x[n]);

            double integration = sum * delta_x / 3;

            return integration;
        }
    }

    public class MainClass
    {
        public static void Main()
        {
            Simpson simpson = new Simpson();
            double a = 0d;//lower limit a
            double b = 3d;//upper limit b
            int n = 6;//Enter step-length n

            if (n % 2 == 0)//n must be even
            {
                Console.WriteLine(simpson.Compute(a, b, n));
            }
            else
            {
                Console.WriteLine("n should be an even number");
            }

            Console.ReadLine();
        }
    }
}

Output:

1.07491527775614

How can I make this source code more efficient?

\$\endgroup\$
2
\$\begingroup\$

This:

    if (j % 2 != 0)
    {
      sum += 4 * Function(x[j]);
    }
    else
    {
      sum += 2 * Function(x[j]);
    }

can be expressed as:

    sum += (2 << (j % 2)) * Function(x);

In order to make your algorithm more useful, you should inject the Function as a delegate parameter to the method, and also make it static:

public static double Compute(Func<double, double> fx, double a, double b, int n)
{
  double h = (b - a) / n;

  double sum = fx(a) + fx(b);
  double x = a;
  for (int j = 1; j < n; j++)
  {
    x += h;
    sum += (2 << (j % 2)) * fx(x);
  }

  return sum * h / 3;
}

Used as:

Simpson.Compute(x => 1 / (1 + Math.Pow(x, 5)), a, b, n);
| improve this answer | |
\$\endgroup\$
5
\$\begingroup\$
  • There is no reason to allocate the double[] x array. It is better to compute the argument as you go: initialize double x = a; and increment it x += delta_x; in the loop body.

  • if in the loop is usually a performance killer. Consider incrementing j by 2:

        for (int j = 1; j < n; j += 2)
        {
            sum += 2 * Function(x);
            x += delta_x;
            sum += 4 * Function(x);
            x += delta_x;
        }
    

    Now depending on the parity of n the very last addition could be wrong, and you need to compensate for it.

  • Function shall not be a Simpson's method. The Simpson shall not care what it integrates. Change the signature of Compute to accept the callable.

  • The choice of \$\dfrac{1}{1 + x^5}\$ for an integrand looks strange. It is hard to verify that the result is correct. I recommend to test against some more friendly integrands first.

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

You don't need the double[].

Example implementation that only loops once and discards the array:

public static double ComputeV2(double a, double b, int n)
{
    double deltaX = DeltaX(a, b, n);

    // start with a sum of x_0 and x_n:
    double sum = Function(a) + Function(a + deltaX * n);

    for (int j = 1; j < n; j++)
    {
        sum += Function(a + deltaX * j) * (j % 2 == 0 ? 2 : 4);
    }

    return Integrate(sum, deltaX);
}

And if you're into LINQ, something like this also works:

public static double ComputeLinq(double a, double b, int n)
{
    double deltaX = DeltaX(a, b, n);

    double sum =
        Enumerable
        .Range(1, n - 1)
        .Select(j => Function(a + deltaX * j) * (j % 2 == 0 ? 2 : 4))
        .Aggregate(
            seed: Function(a) + Function(a + deltaX * n),
            func: (acc, v) => acc + v);

    return Integrate(sum, deltaX);
}

As for performance, here are some micro benchmarks:


BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.720 (1909/November2018Update/19H2)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=2.2.301
  [Host]     : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), X64 RyuJIT
  DefaultJob : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), X64 RyuJIT

|      Method |     Mean |    Error |   StdDev |   Median |
|------------ |---------:|---------:|---------:|---------:|
|   SimpsonV1 | 286.6 ns |  5.53 ns | 11.30 ns | 285.9 ns |
|   SimpsonV2 | 270.1 ns |  5.52 ns | 14.04 ns | 267.0 ns |
| SimpsonLinq | 510.8 ns | 11.67 ns | 34.23 ns | 500.1 ns |

As you can see, using LINQ is clearly slower, but is often more readable (not really in this case, in my opinion).

"My" version is a bit faster than yours, but it's very negligible. The biggest gain is readability and succinctness, but I bet some will find it to be "too" short for its own good.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

In regards to performance or efficiency, I don't see anything that begs for attention.

I would suggest Simpson class and its methods be static. You really are not saving any properties or state between invocations, so static makes more sense.

The method named Function is a horrible name. Far too generic.

I'm not even keen on the method name Compute, though it is an action verb. I'd be partial to Integrate which is also an action verb but more descriptive. The parameter names are decent. Since Simpson's Rule uses the non-descript a and b, it's okay that your method does as well. Perhaps n could be given a more descriptive name.

Your use of braces and indentation looks good. Also good that Function is private and Compute is public. However, you do not provide any validation checks on public methods. What if someone entered -999 as the value for n?

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.