public async Task <double> Integrate(Function f, int nodesCount, TransformableVariable x, TransformableVariable y,
                                             Boundary xBounds, Boundary yBounds)
        {
            this.RefreshCoefficients(nodesCount);
            Tuple <double[], double[]> coefficients;

            // TODO add concurrency checks
            this.coefficientsCache.TryGetValue(nodesCount, out coefficients);
            var weights = coefficients.Item2;
            var nodes   = coefficients.Item1;

            // cache this values for linear transform of gauss coefficients and nodes
            var xDiff = xBounds.High - xBounds.Low;
            var xSum  = xBounds.High + xBounds.Low;

            var yDiff = yBounds.High - yBounds.Low;
            var ySum  = yBounds.High + yBounds.Low;

            double total = 0;

            for (int i = 0; i < nodesCount; ++i)
            {
                for (int j = 0; j < nodesCount; ++j)
                {
                    double transformedX = 0.5 * xSum + 0.5 * xDiff * nodes[i];
                    double transformedY = 0.5 * ySum + 0.5 * yDiff * nodes[j];
                    double currentSum   = weights[i] * weights[j]
                                          * f.Value(x | x.Rule(transformedX, transformedY),
                                                    y | y.Rule(transformedX, transformedY));

                    Add(ref total, currentSum);
                }
            }

            return(total * 0.5 * xDiff * 0.5 * yDiff);
        }
        public async Task <Tuple <Function, Variable, Variable> > Solve(double M, int n, double cylinderRadius,
                                                                        double sphereRadius, double speedAtInfinity)
        {
            var coordFunctions      = this.coordinateFunctions.Get(n);
            var coordFunctionsCount = coordFunctions.Count;

            #region Variables

            var rr = new TransformableVariable(0);
            var zz = new TransformableVariable(1);

            var ro = new TransformableVariable(0, (a, b) =>
                                               Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2)));

            var phi = new TransformableVariable(1, (a, b) =>
            {
                if (a < 0)
                {
                    return(Math.Atan2(b, a) + Math.PI);
                }

                return(Math.Atan2(b, a));
            });

            #endregion

            #region Variable boundaries

            var xBounds = new Boundary()
            {
                Low = -M, High = M
            };
            var yBounds = new Boundary()
            {
                Low = 0, High = cylinderRadius
            };

            var roBounds = new Boundary()
            {
                Low = 0, High = sphereRadius
            };
            var phiBounds = new Boundary()
            {
                Low = 0, High = Math.PI
            };

            #endregion

            #region Functions definition

            Func <Function, Variable, Variable, Function> A =
                (Function f, Variable r, Variable z) => (-1 / r) *
                f.Derivative(z, 2) - (((1 / r) * f.Derivative(r)).Derivative(r));

            Func <Variable, Variable, Function> omega1 =
                (Variable r, Variable z) => Function.Pow(r, 2)
                * (Function.Pow(z, 2) + Function.Pow(r, 2)
                   - Math.Pow(sphereRadius, 2));

            Func <Variable, Variable, Function> omega2 =
                (Variable r, Variable z) => 1 - Function.Pow(r, 2);

            Func <Variable, Variable, Function> omega =
                (Variable r, Variable z) => omega1(r, z) * omega2(r, z)
                / Function.Pow((1 + Function.Pow(z, 2)), 2);

            Func <Variable, Variable, Function> X =
                (Variable r, Variable z) => 1 + Function.Pow(z, 2);

            // c - значение функции на Г2 - радиус трубы
            Func <Variable, Variable, double, Function> F0 =
                (Variable r, Variable z, double c) => c *omega1(r, z)
                / (omega1(r, z) + X(r, z) * omega2(r, z));

            Func <double[], Variable, Variable, Function> result =
                (double[] coeff, Variable r, Variable z) =>
            {
                return(omega(r, z) * coeff.Select((c, i) => c * coordFunctions[i](r, z))
                       .ToList()
                       .Aggregate <Function>((sum, current) => sum + current));
            };

            #endregion

            var matrix    = new double[coordFunctionsCount, coordFunctionsCount];
            var rightPart = new double[coordFunctionsCount];

            for (int i = 0; i < coordFunctionsCount; ++i)
            {
                var right = A(F0(rr, zz, cylinderRadius), rr, zz) * coordFunctions[(int)i](rr, zz);

                for (int j = 0; j < coordFunctionsCount; ++j)
                {
                    var cylinderPart = A(coordFunctions[(int)i](rr, zz), rr, zz) * coordFunctions[(int)j](rr, zz);

                    var spherePart = A(coordFunctions[(int)i](ro, phi), ro, phi) * coordFunctions[(int)j](ro, phi);

                    matrix[(int)i, (int)j] = await integrationService.Integrate(
                        cylinderPart, 10, rr, zz, xBounds, yBounds)
                                             - await integrationService.Integrate(
                        spherePart, 10, ro, phi, roBounds, phiBounds);
                }

                rightPart[i] = await integrationService.Integrate(right, 10, rr, zz, xBounds, yBounds);
            }


            int      info;
            double[] coefficients;
            alglib.densesolverreport report;

            alglib.rmatrixsolve(matrix, coordFunctionsCount, rightPart, out info, out report, out coefficients);

            return(Tuple.Create(result(coefficients, rr, zz), rr as Variable, zz as Variable));
        }